<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>紫升</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://blog.zisheng.pro/</id>
  <link href="https://blog.zisheng.pro/" rel="alternate"/>
  <link href="https://blog.zisheng.pro/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, 紫升</rights>
  <subtitle>怕什么真理无穷，进一寸有一寸的欢喜。</subtitle>
  <title>紫升的博客</title>
  <updated>2026-07-12T09:21:36.907Z</updated>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="AI" scheme="https://blog.zisheng.pro/categories/AI/"/>
    <category term="Agent" scheme="https://blog.zisheng.pro/tags/Agent/"/>
    <category term="macOS" scheme="https://blog.zisheng.pro/tags/macOS/"/>
    <category term="Swift" scheme="https://blog.zisheng.pro/tags/Swift/"/>
    <category term="Owlet" scheme="https://blog.zisheng.pro/tags/Owlet/"/>
    <category term="SwiftPM" scheme="https://blog.zisheng.pro/tags/SwiftPM/"/>
    <content>
      <![CDATA[<p>Owlet 是我用 Claude Code + Codex vibe 出来的常驻 menu-bar app，统计每天 Claude Code 和 Codex 的 Token 用量、排名、实时会话活动。它是个<strong>纯 SPM 项目</strong>——没有 <code>.xcodeproj</code>，靠脚本 <code>swift build</code> 出可执行文件，再手动组装成 <code>Owlet.app</code>。这个「纯 SPM + 手动打包」的选择很轻量，但也埋了一个坑：这次我收到两份用户 crash report，症状是最吓人的那种——<strong>第一次安装，一打开就崩，连界面都没露脸</strong>。</p><p>更迷惑的是：我自己机器上从来没崩过。开发、构建、<code>swift run</code>，一路顺畅。但用户装上就崩，而且两台完全不同的机器（macOS 26.5 和 26.3.1、不同硬件、不同用户）崩得一模一样。这种「开发机好好的，用户机必崩」的 bug，往往指向同一类问题：<strong>运行环境的资源布局，和你开发时不一样。</strong></p><p><img src="/images/swiftpm-bundle-module-crash.webp" alt="打包后 Bundle.module 直接崩"></p><h2 id="一句话总结"><a href="#一句话总结" class="headerlink" title="一句话总结"></a>一句话总结</h2><p>SwiftPM 会为带资源的 target 自动合成一个 <code>Bundle.module</code> 访问器，它在找不到资源包时<strong>不是返回 nil，而是直接 <code>fatalError</code> 崩掉进程</strong>。纯 SPM 的 app 手动组装成 <code>.app</code> 后，资源包位置和 <code>.build/</code> 目录里不一样，<code>Bundle.module</code> 的默认定位逻辑就失效了。开发时它能在可执行文件旁边找到资源包所以不崩，用户首装时找不到就必崩。</p><h2 id="崩溃现场：栈顶是一句-fatalError"><a href="#崩溃现场：栈顶是一句-fatalError" class="headerlink" title="崩溃现场：栈顶是一句 fatalError"></a>崩溃现场：栈顶是一句 fatalError</h2><p>两份 crash report 关键部分完全一致。信号是 <code>EXC_BREAKPOINT (SIGTRAP)</code>——这不是野指针越界那种 <code>SIGSEGV</code>，而是代码里主动触发的断言&#x2F;<code>fatalError</code>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">Process:             Owlet [21723]</span><br><span class="line">Version:             0.4.2 (28)</span><br><span class="line">OS Version:          macOS 26.5 (25F71)</span><br><span class="line"></span><br><span class="line">Exception Type:      EXC_BREAKPOINT (SIGTRAP)</span><br><span class="line">Termination Reason:  Namespace SIGNAL, Code 5, Trace/BPT trap: 5</span><br><span class="line"></span><br><span class="line">Thread 0 Crashed::  Dispatch queue: com.apple.main-thread</span><br><span class="line">0   libswiftCore.dylib   _assertionFailure(_:_:file:line:flags:) + 172</span><br><span class="line">1   Owlet                closure #1 in variable initialization expression of static NSBundle.module + 584</span><br><span class="line">2   Owlet                one-time initialization function for module + 12</span><br><span class="line">3   libdispatch.dylib    _dispatch_client_callout + 16</span><br><span class="line">4   libdispatch.dylib    _dispatch_once_callout + 32</span><br><span class="line">5   Owlet                TokenRank.iconImage.getter + 500</span><br><span class="line">6   Owlet                RankIconView.body.getter + 304</span><br><span class="line">7   SwiftUICore          ViewBodyAccessor.updateBody(of:changed:) ...</span><br></pre></td></tr></table></figure><p>这条栈从下往上读，故事非常清楚：</p><ol><li><strong>frame 6-5</strong>：SwiftUI 要渲染 <code>RankIconView</code>，读它的 <code>body</code>，进而调用 <code>TokenRank.iconImage</code> 这个 getter——也就是「加载排名图标」。</li><li><strong>frame 4-2</strong>：<code>iconImage</code> 里访问了 <code>Bundle.module</code>。<code>Bundle.module</code> 是个 <code>static let</code>，第一次访问会触发 <code>_dispatch_once</code> 一次性初始化（<code>one-time initialization function for module</code>）。</li><li><strong>frame 1-0</strong>：这个初始化闭包里执行了 <code>_assertionFailure</code>——<strong>直接崩</strong>。</li></ol><p>栈顶的 <code>_assertionFailure</code> + 中间的 <code>static NSBundle.module</code> 初始化，是这类 bug 的经典指纹。它对应的其实就是 SwiftPM 生成代码里那句：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Fatal error: unable to find bundle named Owlet_Owlet</span><br></pre></td></tr></table></figure><h2 id="为什么-Bundle-module-会-fatalError"><a href="#为什么-Bundle-module-会-fatalError" class="headerlink" title="为什么 Bundle.module 会 fatalError"></a>为什么 Bundle.module 会 fatalError</h2><p>如果你的 SPM target 声明了 <code>resources</code>，Swift Package Manager 会在编译期<strong>自动合成</strong>一段 <code>Bundle.module</code> 的代码，逻辑大致是：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SwiftPM 自动生成，简化版</span></span><br><span class="line"><span class="keyword">extension</span> <span class="title class_">Foundation</span>.<span class="title class_">Bundle</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">let</span> module: <span class="type">Bundle</span> <span class="operator">=</span> &#123;</span><br><span class="line">        <span class="keyword">let</span> bundleName <span class="operator">=</span> <span class="string">&quot;Owlet_Owlet&quot;</span></span><br><span class="line">        <span class="comment">// 依次在几个候选路径里找 Owlet_Owlet.bundle</span></span><br><span class="line">        <span class="keyword">let</span> candidates <span class="operator">=</span> [</span><br><span class="line">            <span class="type">Bundle</span>.main.resourceURL,</span><br><span class="line">            <span class="type">Bundle</span>.main.bundleURL,</span><br><span class="line">            <span class="comment">// .build 相关路径……</span></span><br><span class="line">        ]</span><br><span class="line">        <span class="keyword">for</span> candidate <span class="keyword">in</span> candidates &#123;</span><br><span class="line">            <span class="keyword">if</span> <span class="keyword">let</span> path <span class="operator">=</span> candidate<span class="operator">?</span>.appendingPathComponent(bundleName <span class="operator">+</span> <span class="string">&quot;.bundle&quot;</span>),</span><br><span class="line">               <span class="keyword">let</span> bundle <span class="operator">=</span> <span class="type">Bundle</span>(url: path) &#123;</span><br><span class="line">                <span class="keyword">return</span> bundle</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 找不到就直接崩</span></span><br><span class="line">        <span class="built_in">fatalError</span>(<span class="string">&quot;unable to find bundle named <span class="subst">\(bundleName)</span>&quot;</span>)</span><br><span class="line">    &#125;()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注意最后一行：<strong>找不到资源包，它不给你 fallback，直接 <code>fatalError</code>。</strong> 这是 SwiftPM 的刻意设计——在它的世界观里，资源包应该总是跟着可执行文件走，找不到就是构建&#x2F;打包出错，属于「不该发生」的致命错误。</p><p>问题在于，它内置的候选路径是为 <code>swift build</code>&#x2F;<code>swift test</code> 那套 <code>.build/</code> 目录布局准备的。当你<strong>手动</strong>把可执行文件塞进 <code>Owlet.app/Contents/MacOS/</code>、把资源包塞进 <code>Contents/Resources/</code> 时，布局对不上，<code>Bundle(url:)</code> 在它预设的候选路径里全部返回 nil，于是走到最后一行崩掉。</p><p>这就解释了那个最迷惑的现象：</p><table><thead><tr><th>场景</th><th>可执行文件位置</th><th>资源包位置</th><th>Bundle.module 结果</th></tr></thead><tbody><tr><td>开发机 <code>swift run</code></td><td><code>.build/debug/Owlet</code></td><td><code>.build/debug/Owlet_Owlet.bundle</code>（就在旁边）</td><td>✅ 找到，不崩</td></tr><tr><td>用户首装 <code>.app</code></td><td><code>.../MacOS/Owlet</code></td><td><code>.../Resources/Owlet_Owlet.bundle</code>（布局不同）</td><td>❌ 找不到 → fatalError</td></tr></tbody></table><p><strong>开发时永远复现不出来</strong>，因为开发时资源包就躺在可执行文件旁边，SwiftPM 的默认路径一找一个准。</p><p><img src="/images/swiftpm-bundle-module-crash-1.webp" alt="同一份代码，开发机布局和 .app 布局不同：一个能找到资源包，一个找不到"></p><h2 id="我早就知道这个坑——但漏了一处"><a href="#我早就知道这个坑——但漏了一处" class="headerlink" title="我早就知道这个坑——但漏了一处"></a>我早就知道这个坑——但漏了一处</h2><p>最扎心的是：这个坑我<strong>早就踩过并且修过</strong>。项目里专门有一个封装，就是为了绕开 <code>Bundle.module</code> 在 <code>.app</code> 里定位失败的问题：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> <span class="title class_">AppResourceBundle</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">let</span> module: <span class="type">Bundle</span> <span class="operator">=</span> &#123;</span><br><span class="line">        <span class="keyword">let</span> fm <span class="operator">=</span> <span class="type">FileManager</span>.default</span><br><span class="line">        <span class="comment">// 1. 按 .app 的真实布局找：Contents/Resources/Owlet_Owlet.bundle</span></span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">let</span> resourcesURL <span class="operator">=</span> <span class="type">Bundle</span>.main.resourceURL &#123;</span><br><span class="line">            <span class="keyword">let</span> url <span class="operator">=</span> resourcesURL.appendingPathComponent(<span class="string">&quot;Owlet_Owlet.bundle&quot;</span>)</span><br><span class="line">            <span class="keyword">if</span> fm.fileExists(atPath: url.path), <span class="keyword">let</span> b <span class="operator">=</span> <span class="type">Bundle</span>(url: url) &#123; <span class="keyword">return</span> b &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 2. 兼容另一种布局</span></span><br><span class="line">        <span class="keyword">let</span> legacy <span class="operator">=</span> <span class="type">Bundle</span>.main.bundleURL.appendingPathComponent(<span class="string">&quot;Owlet_Owlet.bundle&quot;</span>)</span><br><span class="line">        <span class="keyword">if</span> fm.fileExists(atPath: legacy.path), <span class="keyword">let</span> b <span class="operator">=</span> <span class="type">Bundle</span>(url: legacy) &#123; <span class="keyword">return</span> b &#125;</span><br><span class="line">        <span class="comment">// 3. 实在找不到才回落到 SwiftPM 的 Bundle.module</span></span><br><span class="line">        <span class="keyword">return</span> <span class="type">Bundle</span>.module</span><br><span class="line">    &#125;()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>它的核心就是：<strong>先用 <code>fileExists</code> + <code>Bundle(url:)</code> 按 <code>.app</code> 的真实布局显式查找</strong>，命中就返回；只有全都落空时，才把烫手山芋丢回给 <code>Bundle.module</code>。这样在 <code>.app</code> 里能稳定命中第 1 步，根本走不到会 <code>fatalError</code> 的那一行。</p><p><img src="/images/swiftpm-bundle-module-crash-3.webp" alt="八处资源加载里七处都有护栏，唯独一处的护盾缺失，漏出红色告警"></p><p>全项目<strong>一共 8 处加载资源</strong>，其中 7 处都规规矩矩用了 <code>AppResourceBundle.module</code>：hook 脚本、app 图标、菜单栏猫头鹰、Dashboard 图片、本地化 bundle……唯独排名图标这一处漏了网：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TokenRankIcon.swift —— 出事的那一行</span></span><br><span class="line"><span class="keyword">extension</span> <span class="title class_">TokenRank</span> &#123;</span><br><span class="line">    <span class="keyword">var</span> iconImage: <span class="type">NSImage</span>? &#123;</span><br><span class="line">        <span class="keyword">let</span> name <span class="operator">=</span> <span class="string">&quot;rank-<span class="subst">\(persistenceValue)</span>&quot;</span></span><br><span class="line">        <span class="keyword">guard</span> <span class="keyword">let</span> url <span class="operator">=</span> <span class="type">Bundle</span>.module.url(          <span class="comment">// ❌ 直接用了 Bundle.module</span></span><br><span class="line">            forResource: name, withExtension: <span class="string">&quot;png&quot;</span>, subdirectory: <span class="string">&quot;RankIcons&quot;</span></span><br><span class="line">        ) <span class="keyword">else</span> &#123; <span class="keyword">return</span> <span class="literal">nil</span> &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="type">NSImage</span>(contentsOf: url)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>修复只有一行：</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="deletion">-        guard let url = Bundle.module.url(</span></span><br><span class="line"><span class="addition">+        guard let url = AppResourceBundle.module.url(</span></span><br><span class="line">             forResource: name, withExtension: &quot;png&quot;, subdirectory: &quot;RankIcons&quot;</span><br><span class="line">         ) else &#123; return nil &#125;</span><br></pre></td></tr></table></figure><h2 id="为什么这种「只漏一处」的-bug-最难防"><a href="#为什么这种「只漏一处」的-bug-最难防" class="headerlink" title="为什么这种「只漏一处」的 bug 最难防"></a>为什么这种「只漏一处」的 bug 最难防</h2><p>这行代码有几个特别阴的地方，值得单独拎出来说：</p><p><strong>第一，<code>guard let ... else { return nil }</code> 给了你虚假的安全感。</strong> 这行写得很「防御」——找不到资源就返回 nil，调用方还有 SF Symbol 兜底。看起来无懈可击。但崩溃发生在 <code>Bundle.module</code> 这个表达式<strong>被求值的瞬间</strong>，压根轮不到 <code>.url(...)</code> 返回 nil。你的 <code>else</code> 分支再周全也拦不住，因为进程在进入 <code>guard</code> 之前就已经 <code>fatalError</code> 了。</p><p><img src="/images/swiftpm-bundle-module-crash-2.webp" alt="Bundle.module 是一枚 lazy 定时炸弹，menu bar、灵动卡、排名弹窗三条路都会引爆它"></p><p><strong>第二，它是 lazy 的定时炸弹。</strong> <code>Bundle.module</code> 是 <code>static let</code> + <code>dispatch_once</code>，只在<strong>第一次被访问</strong>时初始化。只要 app 有任何一条路径会渲染排名图标（menu bar 状态图标、灵动卡、排名弹窗都会），首启就必然触发。而恰恰这个 app 的核心功能就是「显示排名」，等于把炸弹放在了必经之路上。</p><p><strong>第三，编译器完全帮不上忙。</strong> <code>Bundle.module</code> 和 <code>AppResourceBundle.module</code> 类型一样、API 一样、调用方式一样，编译期没有任何区别。少写四个字符 <code>AppResource</code>，代码照样编译通过、开发机照样跑得欢，只在用户机上炸。这种「语义上错、语法上对、且只有一处」的 bug，靠 code review 用肉眼扫也极容易滑过去。</p><p>真正能防住它的其实只有一招：<strong>从源头上让「危险的那个 API」不可直接访问。</strong> 如果我能把 <code>Bundle.module</code> 的可见性收窄、逼所有资源加载都必须走 <code>AppResourceBundle</code>，这个 bug 从一开始就写不出来。可惜 SwiftPM 合成的 <code>Bundle.module</code> 是 internal 可见的，没法轻易禁掉——退而求其次，至少可以加一条 lint &#x2F; grep 规则，在 CI 里扫「除了 <code>AppResourceBundle.swift</code> 之外任何文件出现裸的 <code>Bundle.module</code>」直接 fail。</p><h2 id="要不要用-我的判断框架"><a href="#要不要用-我的判断框架" class="headerlink" title="要不要用 &#x2F; 我的判断框架"></a>要不要用 &#x2F; 我的判断框架</h2><p>这次踩坑给「纯 SPM 打 macOS app」这条路线补了几条经验，供同样在 vibe macOS app 的人参考：</p><ul><li><p><strong>值得上</strong>：纯 SPM + 脚本打包对小工具型 app 依然是划算的，省掉整个 Xcode 工程的心智负担。但你必须清楚地知道——<strong>一旦你手动组装 <code>.app</code>，SwiftPM 那套「资源就在可执行文件旁边」的假设就不成立了</strong>，所有 <code>Bundle.module</code> 都得换成按 <code>.app</code> 真实布局查找的封装。这不是可选优化，是必修课。</p></li><li><p><strong>重点关注</strong>：凡是「开发机好好的、用户机崩」的 bug，第一反应就去查<strong>运行环境差异</strong>——资源布局、签名方式、路径假设、权限。这次是资源包路径，上次是 codesign identity 漂移。它们的共同点是：你的开发环境「太顺了」，顺到掩盖了打包产物里的问题。<strong>能在装好的 <code>.app</code> 上做一次冷启动冒烟测试，胜过在 <code>.build/</code> 里跑一百次。</strong></p></li><li><p><strong>可以再等 &#x2F; 顺手做</strong>：给危险 API 加护栏这件事，值得排进 backlog。<code>fatalError</code> 类的「找不到就崩」API（<code>Bundle.module</code>、强解包、<code>try!</code>、数组越界下标）在常驻 app 里都是首启崩溃的高发点。与其靠自觉每次都记得用封装，不如让「裸用」在 CI 里直接编不过或过不了 lint——把「记得」变成「想漏都漏不掉」。</p></li></ul><p>一句话收尾：<strong>这个 bug 的技术根因五分钟就能讲清，但它能上线，靠的是「开发机永远复现不出」+「编译器不报错」+「八处只漏一处」三重巧合。防它的关键不在于更聪明地写这一行，而在于让这一行根本没机会写错。</strong></p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/bce102b76701/</id>
    <link href="https://blog.zisheng.pro/posts/bce102b76701/"/>
    <published>2026-07-10T14:30:00.000Z</published>
    <summary>这篇文章复盘我在 Owlet 上遇到的一个「用户第一次装、一打开就崩」的 bug。两台不同机器、不同用户、不同 macOS 版本，crash log 一模一样，崩溃点是 SwiftPM 合成的 Bundle.module 在触发 _assertionFailure。根因是纯 SPM 的 macOS app 手动组装成 .app 之后，Swift Package Manager 默认生成的 Bundle.module 资源定位逻辑找不到资源包，会直接 fatalError；而我在 8 处加载资源里，唯独一处漏用了自己写的兜底封装。文章从 crash log 反推根因，讲清楚 Bundle.module 为什么在开发时不崩、用户首装必崩，以及为什么这种「只有一处漏网」的 bug 最难在本地复现。</summary>
    <title>打包后 Bundle.module 直接崩：一个 SPM app 首启必崩的资源陷阱</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="AI" scheme="https://blog.zisheng.pro/categories/AI/"/>
    <category term="Agent" scheme="https://blog.zisheng.pro/tags/Agent/"/>
    <category term="macOS" scheme="https://blog.zisheng.pro/tags/macOS/"/>
    <category term="Swift" scheme="https://blog.zisheng.pro/tags/Swift/"/>
    <category term="并发" scheme="https://blog.zisheng.pro/tags/%E5%B9%B6%E5%8F%91/"/>
    <category term="Owlet" scheme="https://blog.zisheng.pro/tags/Owlet/"/>
    <content>
      <![CDATA[<p>Owlet 是我用 Claude Code + Codex vibe 出来的一个常驻 menu-bar app，用来统计每天 Claude Code 和 Codex 的 Token 用量、排名、实时会话活动。常驻类 app 最怕两件事：一是空闲耗电，二是莫名其妙崩。这次一天之内，我连着修了两个崩溃——而且它们像俄罗斯套娃：修好外面那个，里面那个才露出来。</p><p>第一个崩得相当有迷惑性——崩溃栈指向的那一行代码本身没有任何问题。真正的凶手，是一个我亲手写下 <code>@unchecked Sendable</code>、然后就再也没管过它线程安全的普通 <code>Dictionary</code>。第二个则相反，是个藏在一行 <code>0</code> 里、只要用起来就必崩的确定性 bug。两个坑的共同点是：它们都利用了 Swift 里某个”看起来无害”的默认行为。</p><p><img src="/images/swift-unchecked-sendable-dictionary-crash.webp" alt="Swift 并发字典崩溃复盘"></p><h2 id="一句话总结"><a href="#一句话总结" class="headerlink" title="一句话总结"></a>一句话总结</h2><p><code>@unchecked Sendable</code> 不是”让这个类型线程安全”，而是”我向编译器保证它已经安全了，你别查了”。如果你写下它却没做任何同步，普通 <code>Dictionary</code> 在并发写下会直接损坏底层存储、破坏整个进程的堆内存，崩溃点还会飘到无辜的其他线程上。</p><h2 id="崩溃现场：两个线程站在同一个栈上"><a href="#崩溃现场：两个线程站在同一个栈上" class="headerlink" title="崩溃现场：两个线程站在同一个栈上"></a>崩溃现场：两个线程站在同一个栈上</h2><p>先看崩溃日志最关键的部分。信号是 <code>EXC_BAD_ACCESS (SIGSEGV)</code>，访问了一个明显非法的地址 <code>0x8000000000000028</code>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">Thread 4 Crashed:</span><br><span class="line">0   libswiftCore.dylib   __RawDictionaryStorage.find&lt;A&gt;(_:) + 36</span><br><span class="line">1   libswiftCore.dylib   _NativeDictionary.setValue(_:forKey:isUnique:) + 192</span><br><span class="line">2   libswiftCore.dylib   Dictionary._Variant.setValue(_:forKey:) + 196</span><br><span class="line">3   libswiftCore.dylib   Dictionary.subscript.setter + 604</span><br><span class="line">4   Owlet                ClaudeCodeAdapter.discoverSessions() (ClaudeCodeAdapter.swift:200)</span><br></pre></td></tr></table></figure><p>崩在 <code>__RawDictionaryStorage.find</code> &#x2F; <code>setValue</code>——也就是往字典里写一个 key 的时候，字典的内部哈希存储已经是坏的了。这不是”key 不存在”这种业务问题，是底层内存结构被破坏。</p><p>再往下看，会发现一个决定性的线索：<strong>另一个线程也停在同一个函数里。</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Thread 2:</span><br><span class="line">5   Owlet   ClaudeCodeAdapter.discoverSessions() (ClaudeCodeAdapter.swift:199)</span><br></pre></td></tr></table></figure><p>两个后台线程，同一时刻，都在执行 <code>discoverSessions()</code>，都在读写同一个 <code>Dictionary</code>。一个在 199 行读，一个在 200 行写。到这里，数据竞争（data race）已经基本实锤了。</p><p>还有一个更迷惑人的细节：崩溃报告里第三个线程停在完全不相干的地方——</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Thread 7 (GRDB.DatabasePool.reader):</span><br><span class="line">   key path getter for Character.isNewline</span><br><span class="line">   Collection.split(whereSeparator:)</span><br><span class="line">   CodexDataReader.rolloutTokenUsageEvents(path:)</span><br></pre></td></tr></table></figure><p><code>CodexDataReader</code> 是读 Codex 数据的另一套代码，跟 Claude 的 adapter 八竿子打不着，凭什么它也崩？</p><p>答案是：<strong>堆内存被写坏之后，整个进程都在雷区里跑。</strong> Thread 4 把字典的底层存储写坏，破坏的是共享的堆。此后任何线程在任何位置做一次内存访问，都可能踩到那片被污染的区域，然后随机崩溃。所以排查并发内存崩溃时，有一个重要原则:</p><blockquote><p>崩溃日志里”触发信号的那个线程”（<code>triggered: true</code>）才是凶手，其他线程往往只是受害者。</p></blockquote><p>如果你盯着 Thread 7 的 <code>CodexDataReader</code> 去查，会彻底跑偏。</p><h2 id="unchecked-Sendable：一句你亲手写下的空头承诺"><a href="#unchecked-Sendable：一句你亲手写下的空头承诺" class="headerlink" title="@unchecked Sendable：一句你亲手写下的空头承诺"></a>@unchecked Sendable：一句你亲手写下的空头承诺</h2><p>顺着凶手 <code>ClaudeCodeAdapter</code> 看类型声明：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">ClaudeCodeAdapter</span>: <span class="title class_">AgentAdapter</span>, @<span class="title class_">unchecked</span> <span class="title class_">Sendable</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">var</span> sessionEntryCache: [<span class="type">String</span>: <span class="type">CachedSessionEntry</span>] <span class="operator">=</span> [:]</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>问题全在这两行里。</p><p><code>Sendable</code> 是 Swift 并发模型里的一个协议，意思是”这个类型的值可以安全地跨并发域传递”。对于 <code>class</code> 这种引用类型，编译器默认不认为它是 <code>Sendable</code>——因为多个线程可能同时持有同一个引用、改同一块状态。</p><p>而 <code>@unchecked Sendable</code> 是一个逃生舱：它告诉编译器”这个类型我人工审计过了，线程安全我自己保证，你不用检查”。它常用于两种情况：一种是内部用了锁&#x2F;串行队列做同步，只是编译器看不出来；另一种是……写的人当时觉得”应该没事吧”。</p><p>我这次就是第二种。<code>sessionEntryCache</code> 是一个普通的 <code>Dictionary</code>，没有锁、没有串行队列、没有 actor，什么保护都没有。<code>@unchecked Sendable</code> 关掉了编译器的并发检查，于是这个隐患一路绿灯上了线。</p><p>这里要记住一件事：<strong><code>@unchecked Sendable</code> 不提供任何运行时保护，它只是关掉编译期的报错。</strong> 你写下它的那一刻，线程安全就从”编译器帮你保证”变成了”你自己口头保证”。</p><p>那为什么会有两个线程并发进来？看调用方：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// AppState.refresh() 里</span></span><br><span class="line"><span class="keyword">_</span> <span class="operator">=</span> <span class="keyword">try</span> <span class="keyword">await</span> <span class="type">Task</span>.detached(priority: .utility) &#123;</span><br><span class="line">    <span class="keyword">for</span> runtime <span class="keyword">in</span> runtimesToImport &#123;</span><br><span class="line">        imported <span class="operator">+=</span> <span class="keyword">try</span> <span class="keyword">await</span> capturedIngestion.importSessions(from: runtime.adapter)</span><br><span class="line">        <span class="comment">// -&gt; 最终调用 adapter.discoverSessions()</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;.value</span><br></pre></td></tr></table></figure><p><code>refresh()</code> 会在多个时机被触发——app 启动 bootstrap、文件监听 debounce 后的自动刷新、手动刷新。每次触发都开一个 <code>Task.detached</code> 跑 import。当两次刷新时间上挨得很近（比如启动时 bootstrap 和文件监听几乎同时发生），两个 detached task 就并发进入了同一个 adapter 实例的 <code>discoverSessions()</code>，一起写那个没有任何保护的字典。</p><h2 id="为什么不能用细粒度锁：read-modify-write-的原子性"><a href="#为什么不能用细粒度锁：read-modify-write-的原子性" class="headerlink" title="为什么不能用细粒度锁：read-modify-write 的原子性"></a>为什么不能用细粒度锁：read-modify-write 的原子性</h2><p>找到根因，很多人第一反应是”给字典加个锁不就行了”。但加在哪、加多粗，是这次真正值得记下来的点。</p><p>先看 <code>discoverSessions()</code> 里对 cache 的访问模式：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> <span class="keyword">let</span> entry <span class="operator">=</span> sessionEntryCache[cacheKey] &#123;        <span class="comment">// ① 读旧 entry</span></span><br><span class="line">    <span class="keyword">if</span> fileSize <span class="operator">&gt;</span> entry.fileSize &#123;</span><br><span class="line">        <span class="comment">// 只读新增的字节，从 entry.offset 处 seek</span></span><br><span class="line">        parseIncremental(file, entry: entry, <span class="operator">...</span>)    <span class="comment">// ② 用 entry.offset seek 文件</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">sessionEntryCache[cacheKey] <span class="operator">=</span> newEntry               <span class="comment">// ③ 写回带新 offset 的 entry</span></span><br></pre></td></tr></table></figure><p>这是一个典型的 <strong>read-modify-write</strong>：读出旧的 <code>entry</code>（里面记着”上次读到文件的哪个字节偏移”），基于这个 offset 只读文件新追加的部分（增量解析，避免每次全量重读几十 MB），再把新的 offset 写回去。</p><p>如果只给字典的每次下标访问加一把细粒度锁（读一次锁一次、写一次锁一次），会发生什么？</p><blockquote><p>线程 A 读到 offset&#x3D;1000，线程 B 也读到 offset&#x3D;1000。两个线程都从 1000 开始读文件、各自解析、各自把”新 offset”写回。其中一个的结果被覆盖，offset 错乱，同一段 Token 用量被重复计入。</p></blockquote><p>字典本身不崩了，但数据错了。细粒度锁保护了”每一次字典操作”的原子性，却没保护”读—改—写”这个序列整体的原子性。这是并发编程里最容易栽的一类坑：<strong>锁的粒度对齐的应该是不变量（invariant），而不是单个操作。</strong></p><p>这里的不变量是”cache 里的 offset 必须和已经解析进去的字节严格对应”。要维持它，整个 <code>discoverSessions()</code> pass 必须串行执行。所以正确的修法是一把粗粒度锁，锁住整趟：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">let</span> discoverLock <span class="operator">=</span> <span class="type">NSLock</span>()</span><br><span class="line"></span><br><span class="line"><span class="keyword">func</span> <span class="title function_">discoverSessions</span>() <span class="keyword">async</span> <span class="keyword">throws</span> -&gt; [<span class="type">AgentSessionDraft</span>] &#123;</span><br><span class="line">    discoverLock.withLock &#123; discoverSessionsLocked() &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">func</span> <span class="title function_">discoverSessionsLocked</span>() -&gt; [<span class="type">AgentSessionDraft</span>] &#123;</span><br><span class="line">    <span class="comment">// 原来那一整段读目录、读文件、增量解析、写 cache 的逻辑</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>有人会担心：持锁期间还在做磁盘 I&#x2F;O，第二个并发调用不就干等着吗？会。但这恰恰是最优解——第二个调用等第一个做完之后拿到锁，此时 cache 已经是热的，绝大多数文件的 <code>mtime</code> &#x2F; <code>fileSize</code> 都没变，直接命中缓存、零 I&#x2F;O 秒回。串行化在这里几乎没有额外代价，反而消除了两个 task 各跑一遍的重复劳动。</p><h2 id="Swift-6-的暗礁：async-上下文里不能-lock"><a href="#Swift-6-的暗礁：async-上下文里不能-lock" class="headerlink" title="Swift 6 的暗礁：async 上下文里不能 lock()"></a>Swift 6 的暗礁：async 上下文里不能 lock()</h2><p>按上面思路，我一开始是直接在 <code>discoverSessions()</code> 里写的：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">func</span> <span class="title function_">discoverSessions</span>() <span class="keyword">async</span> <span class="keyword">throws</span> -&gt; [<span class="type">AgentSessionDraft</span>] &#123;</span><br><span class="line">    discoverLock.lock()</span><br><span class="line">    <span class="keyword">defer</span> &#123; discoverLock.unlock() &#125;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>编译能过，但�报了个 warning，而且明说了在 Swift 6 语言模式下会直接变成 error：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">warning: instance method &#x27;lock&#x27; is unavailable from asynchronous contexts;</span><br><span class="line">Use async-safe scoped locking instead;</span><br><span class="line">this is an error in the Swift 6 language mode</span><br></pre></td></tr></table></figure><p>原因很本质：<code>async</code> 函数可能在任意 <code>await</code> 处挂起，挂起之后恢复执行的线程<strong>不一定是原来那个线程</strong>。而 <code>NSLock</code> 是要求”谁上锁谁解锁、同一线程”的。如果你在 <code>lock()</code> 和 <code>unlock()</code> 之间跨了一个 <code>await</code>，挂起前后换了线程，就会变成”A 线程上锁、B 线程解锁”，直接是未定义行为。</p><p>编译器无法证明你两者之间没有 <code>await</code>，于是干脆禁止在 async 上下文里裸用 <code>lock()</code>&#x2F;<code>unlock()</code>。</p><p>正解是用 scoped 的 <code>withLock { }</code>：把需要保护的逻辑抽成一个<strong>同步</strong>函数（我这里是 <code>discoverSessionsLocked()</code>），闭包体内不可能出现 <code>await</code>，锁的获取和释放必然在同一个同步执行段里完成。这也顺便逼你把”要保护的临界区”想清楚、圈干净，而不是散着写一堆 lock&#x2F;unlock。</p><h2 id="加餐：同一天的第二个崩溃，藏在一行-0-里"><a href="#加餐：同一天的第二个崩溃，藏在一行-0-里" class="headerlink" title="加餐：同一天的第二个崩溃，藏在一行 0 里"></a>加餐：同一天的第二个崩溃，藏在一行 <code>0</code> 里</h2><p>修完并发那个崩溃、重新打包上线，没过多久又收到一份新的崩溃日志。这次信号不一样，是 <code>EXC_BREAKPOINT (SIGTRAP)</code>——Swift 运行时主动触发的陷阱，通常意味着数组或 <code>Data</code> 越界。</p><p>崩溃点在一个监听 JSONL 会话文件、检测”用户中断”的后台组件里：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">func</span> <span class="title function_">scanBufferForInterrupt</span>() &#123;</span><br><span class="line">    <span class="keyword">var</span> scanStart <span class="operator">=</span> <span class="number">0</span></span><br><span class="line">    <span class="keyword">while</span> scanStart <span class="operator">&lt;</span> leftoverData.count &#123;</span><br><span class="line">        <span class="keyword">guard</span> <span class="keyword">let</span> index <span class="operator">=</span> leftoverData[scanStart<span class="operator">...</span>].firstIndex(of: newline) <span class="keyword">else</span> &#123; <span class="keyword">break</span> &#125;</span><br><span class="line">        <span class="comment">// ... 处理一行 ...</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> consumed <span class="operator">&gt;</span> <span class="number">0</span> &#123;</span><br><span class="line">        leftoverData.removeFirst(consumed)   <span class="comment">// ← 雷埋在这</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>leftoverData</code> 是一个不断追加、扫描完就把已处理部分 <code>removeFirst</code> 掉的 <code>Data</code> buffer。看起来天经地义。但它踩中了 <code>Data</code> 一个反直觉的设定：</p><blockquote><p><strong>Swift <code>Data</code> 的 index 是绝对的。<code>removeFirst(n)</code> 之后，它的 <code>startIndex</code> 会前移到 n，而不是回到 0。</strong></p></blockquote><p>第一次扫描时 <code>startIndex == 0</code>，<code>leftoverData[0...]</code> 一切正常。扫完执行 <code>removeFirst(consumed)</code>，<code>startIndex</code> 变成了 consumed（比如 87）。等文件第二次增长、再进来扫描时，<code>scanStart</code> 还是硬编码的 <code>0</code>，而 <code>leftoverData[0...]</code> 里的这个 <code>0</code> 已经落在 <code>startIndex</code> 之前——越界，SIGTRAP。</p><p>这不是偶发的并发时序问题，是个<strong>确定性 bug</strong>：只要有一个 Claude 会话在跑、它的 JSONL 被追加写入两次，就必崩。之所以之前一直没暴露，是因为上一个并发崩溃发生得更早，先把进程带走了；修掉前一个，这个就顶到了最前面。</p><p>修复本身很简单，但记性要长——别用字面量 <code>0</code> 当 <code>Data</code> 的索引，一切从 <code>startIndex</code> 起：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> lineStart <span class="operator">=</span> leftoverData.startIndex</span><br><span class="line"><span class="keyword">while</span> lineStart <span class="operator">&lt;</span> leftoverData.endIndex &#123;</span><br><span class="line">    <span class="keyword">guard</span> <span class="keyword">let</span> newlineIndex <span class="operator">=</span> leftoverData[lineStart<span class="operator">...</span>].firstIndex(of: newline) <span class="keyword">else</span> &#123; <span class="keyword">break</span> &#125;</span><br><span class="line">    <span class="keyword">let</span> lineData <span class="operator">=</span> leftoverData.subdata(in: lineStart<span class="operator">..&lt;</span>newlineIndex)</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    lineStart <span class="operator">=</span> leftoverData.index(after: newlineIndex)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>Collection</code> 的下标是绝对索引，这件事在 <code>Array</code> 上很少咬到你——因为 <code>Array</code> 的 index 永远从 0 开始。但 <code>Data</code>、<code>ArraySlice</code>、<code>Substring</code> 这些”可能是别人一部分”的类型，<code>startIndex</code> 随时可能不是 0。<strong>凡是切片类型，就别信 0。</strong></p><h2 id="我的判断框架：actor-锁-unchecked-Sendable-怎么选"><a href="#我的判断框架：actor-锁-unchecked-Sendable-怎么选" class="headerlink" title="我的判断框架：actor &#x2F; 锁 &#x2F; @unchecked Sendable 怎么选"></a>我的判断框架：actor &#x2F; 锁 &#x2F; @unchecked Sendable 怎么选</h2><p>这次踩坑之后，我给自己定了一套在 Swift 并发里选同步手段的判断顺序：</p><table><thead><tr><th>场景</th><th>首选</th><th>理由</th></tr></thead><tbody><tr><td>新写的、可以 async 化的共享可变状态</td><td><code>actor</code></td><td>编译器强制隔离，从根上杜绝数据竞争，不用自己管锁</td></tr><tr><td>已有同步 API、不方便全改 async，临界区清晰</td><td><code>NSLock</code> + <code>withLock</code></td><td>改动最小，粗粒度锁住不变量即可</td></tr><tr><td>高频只读、极少写</td><td>读写锁 &#x2F; 原子类型</td><td>读不互斥，避免锁竞争成为热点</td></tr><tr><td>确实由底层机制保证了安全</td><td><code>@unchecked Sendable</code> + <strong>注释写清凭什么安全</strong></td><td>逃生舱，但必须留下审计依据</td></tr></tbody></table><p>几条给自己的硬规矩：</p><ul><li><strong>看到 <code>@unchecked Sendable</code>，就当成一个待验证的 TODO。</strong> 它旁边必须有一句注释说明”靠什么保证线程安全”（哪把锁、哪个串行队列、哪个 actor）。说不出来，就是隐患。</li><li><strong>锁的粒度对齐不变量，不对齐单个操作。</strong> 遇到 read-modify-write，先问自己”这三步之间的中间状态能不能被别人看到”，不能就整段串行。</li><li><strong>排查并发内存崩溃，先认准 <code>triggered</code> 线程。</strong> 堆一旦被写坏，崩溃点会飘，别被无辜的受害线程带偏。</li><li><strong>async 里要锁，用 <code>withLock</code>，别用裸 <code>lock()</code>。</strong> 顺手把临界区抽成同步函数，思路和编译器都会更清楚。</li><li><strong>切片类型的索引从 <code>startIndex</code> 起，别信字面量 <code>0</code>。</strong> <code>Data</code>、<code>ArraySlice</code>、<code>Substring</code> 的 <code>startIndex</code> 随时可能不是 0，尤其在 <code>removeFirst</code> &#x2F; 切片之后。遍历用原生 index，不用 <code>Int</code> 硬编码。</li></ul><p><code>@unchecked Sendable</code> 这类逃生舱不是不能用——<code>actor</code> 化整个 adapter 也要改协议、改一堆调用点，工程上未必划算。真正的问题是：我当初写下它的时候,把”我保证安全”当成了”它已经安全”。这两者之间隔着的，正是一次会飘到无辜线程上的堆内存崩溃。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/8215610282bf/</id>
    <link href="https://blog.zisheng.pro/posts/8215610282bf/"/>
    <published>2026-07-10T13:30:00.000Z</published>
    <summary>这篇文章复盘我在 Owlet 里一天内连修的两次 Swift 崩溃。第一次是并发数据竞争：一个被 @unchecked Sendable 骗过编译器的普通 Dictionary，在两个并发 import 里被同时写坏内存，崩溃点还飘到毫不相干的第三个线程上。第二次是确定性的 Data 下标越界：removeFirst 之后 Data 的 startIndex 不归零，用字面量 0 当索引直接越界 SIGTRAP。文章从崩溃日志反推根因，讲清楚为什么细粒度锁不够、read-modify-write 为什么必须整段串行、Swift 6 里 async 上下文为什么不让你裸用 NSLock.lock()，以及切片类型的索引为什么不能信 0。</summary>
    <title>@unchecked Sendable 骗过了编译器：一次 Swift 并发字典崩溃复盘</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="AI" scheme="https://blog.zisheng.pro/categories/AI/"/>
    <category term="LLM" scheme="https://blog.zisheng.pro/tags/LLM/"/>
    <category term="Agent" scheme="https://blog.zisheng.pro/tags/Agent/"/>
    <category term="Claude Code" scheme="https://blog.zisheng.pro/tags/Claude-Code/"/>
    <category term="Context Engineering" scheme="https://blog.zisheng.pro/tags/Context-Engineering/"/>
    <category term="知识管理" scheme="https://blog.zisheng.pro/tags/%E7%9F%A5%E8%AF%86%E7%AE%A1%E7%90%86/"/>
    <category term="vibe coding" scheme="https://blog.zisheng.pro/tags/vibe-coding/"/>
    <content>
      <![CDATA[<p>今天我干了三件看起来不相干的小事：把”怎么把一张图做成 macOS 应用图标”写进了一个 Claude Code skill，把”博客每篇至少配 3 张插图”写进了全局 <code>CLAUDE.md</code>，还顺手把一次踩坑记成了博客。</p><p>写到第三件的时候我突然停下来：这三件事，本质上是同一件事——<strong>我在把”怎么做”这件知识，沉淀成 AI 下次能直接读、直接用的形式。</strong></p><p>换句话说，我在给自己建一个 wiki。只不过这个 wiki 的读者不是人，是 LLM。</p><p><img src="/images/llm-wiki.webp" alt="LLM wiki 主视觉"></p><h2 id="一句话总结"><a href="#一句话总结" class="headerlink" title="一句话总结"></a>一句话总结</h2><p><strong>传统 wiki 是写给人查的，LLM wiki 是写给模型执行的。</strong> 前者追求”读得懂”，后者追求”能复用”——要可执行、有触发条件、能被真实结果校验。当你开始有意识地为 AI 沉淀知识，你的个人生产力会从”一次性完成任务”变成”每次完成都让下一次更快”。</p><h2 id="我是怎么撞见这个想法的"><a href="#我是怎么撞见这个想法的" class="headerlink" title="我是怎么撞见这个想法的"></a>我是怎么撞见这个想法的</h2><p>起因很具体。我在 vibe coding 一个 macOS menu-bar 应用，需要把一张 AI 生成的猫头鹰图变成真正的 app 图标。这中间有一堆非显而易见的坑：macOS 不会帮你自动圆角、AI 出的图常常自带一圈 padding 和阴影、直接用会变成”方块里套圆角块”、squircle 的圆角半径大概是边长的 0.2237 倍……</p><p>我踩完这些坑、跑通整条管线之后，做了一个选择：<strong>不是把结果留在这次对话里，而是把方法写进 skill。</strong></p><p>于是下次我（或者任何一个读这个 skill 的 AI）再遇到”把图做成 mac 图标”，不用重新踩一遍——自动裁 padding、套 squircle、生成 iconset、打包 <code>.icns</code>，一条龙。</p><p>那一刻我意识到：我做的不是”完成一个任务”，是”给未来的 AI 写了一页可执行的文档”。这页文档，就是 LLM wiki 的一条词条。</p><h2 id="LLM-wiki-的三种载体"><a href="#LLM-wiki-的三种载体" class="headerlink" title="LLM wiki 的三种载体"></a>LLM wiki 的三种载体</h2><p>我目前的知识沉淀，落在三个地方，各有分工：</p><p><img src="/images/llm-wiki-1.webp" alt="三种沉淀载体：规则、技能、文章"></p><table><thead><tr><th>载体</th><th>存什么</th><th>何时被读</th><th>类比</th></tr></thead><tbody><tr><td><code>CLAUDE.md</code> &#x2F; <code>AGENTS.md</code></td><td>全局约束、偏好、红线</td><td>每次会话自动加载</td><td>wiki 的”编辑守则”</td></tr><tr><td>Claude Code skill</td><td>可执行的方法论、管线</td><td>命中触发词时按需加载</td><td>wiki 的”操作手册词条”</td></tr><tr><td>博客文章</td><td>判断框架、踩坑复盘、观点</td><td>我或别人主动检索</td><td>wiki 的”深度专题页”</td></tr></tbody></table><p><strong><code>CLAUDE.md</code> 是常驻规则。</strong> 它每次都进上下文，所以只放最高优先级、最稳定的东西：比如”博客每篇至少 3 张插图””性能规则不能回退””发布流程怎么走”。它贵，所以要克制——不该把操作细节全塞进来。</p><p><strong>skill 是按需加载的操作手册。</strong> 它平时不占上下文，只有当任务命中触发词（”做 mac 应用图标”、”.icns”）才被拉进来。这意味着 skill 可以写得很详细、很长，因为它不是常驻成本。我那条 macOS 图标管线就适合放这儿。</p><p><strong>博客是给人也给 AI 的深度页。</strong> 它存的是”为什么这么做”的判断，而不只是”怎么做”的步骤。而且它是公开的——别人的 AI 也能检索到、学到。这层是前两层的”公共版”。</p><h2 id="LLM-wiki-和传统-wiki-差在哪"><a href="#LLM-wiki-和传统-wiki-差在哪" class="headerlink" title="LLM wiki 和传统 wiki 差在哪"></a>LLM wiki 和传统 wiki 差在哪</h2><p>关键区别只有一个：<strong>读者变了。</strong></p><p>传统 wiki 写给人，人会脑补、会容错、会举一反三。所以传统文档可以模糊——“把图标处理一下”，人能懂。</p><p>LLM wiki 写给模型，你得假设读者<strong>不会脑补、只会照做</strong>。所以三条硬要求：</p><ol><li><strong>可执行，不是描述。</strong> 别写”记得处理透明背景”，直接给能跑的脚本 + 参数 + 兜底分支。我那条图标 skill 里直接嵌了一段 Python，模型抽出来就能运行。</li><li><strong>有触发条件。</strong> 人会自己判断该翻哪一页；模型需要你明确写”什么时候用我”。skill 的 description 和触发词就是干这个的——写不好，该被加载时它不出现。</li><li><strong>能被 ground truth 校验。</strong> 这是我今天最深的一课。</li></ol><p><img src="/images/llm-wiki-2.webp" alt="Ground truth：以真实结果为准"></p><h2 id="最痛的一课：不要相信”我以为做了”"><a href="#最痛的一课：不要相信”我以为做了”" class="headerlink" title="最痛的一课：不要相信”我以为做了”"></a>最痛的一课：不要相信”我以为做了”</h2><p>今天我在提交一个 skill 改动时，中间的工具输出显示”已提交、已推送”，我就信了、往下走了。后来用 <code>git rev-parse HEAD</code> 和 <code>git show</code> 一核对——<strong>根本没提交成功，那几步是我基于未经核验的中间状态脑补出来的。</strong></p><p>这件事放在 LLM wiki 的语境里特别有意思：<strong>模型（包括我这个 AI）会基于”我以为的状态”往前跑，而不是”真实的状态”。</strong> 所以一条好的 LLM wiki 词条，必须内建”用真值校验”这一步。</p><p>我后来把这条也固化了：涉及提交推送，一律以 <code>git rev-parse</code>、<code>git status</code>、<code>git show</code> 的真实输出为准，绝不用中间回显下结论。这就是一条为 AI 写的”自我校验规则”。</p><p>传统 wiki 不需要教人”提交完要确认真的提交了”——人有直觉。但给 AI 写的 wiki 需要，因为模型的失败模式和人不一样：它不是不会，是会一本正经地跑偏。</p><h2 id="怎么让沉淀真的被复用"><a href="#怎么让沉淀真的被复用" class="headerlink" title="怎么让沉淀真的被复用"></a>怎么让沉淀真的被复用</h2><p>沉淀不等于复用。我踩过的坑：</p><ul><li><strong>写了没触发词 &#x3D; 白写。</strong> skill 描述里不写”mac 应用图标 &#x2F; .icns”，下次问”帮我做个应用图标”时它根本不会被加载。触发词是 LLM wiki 的”索引”。</li><li><strong>写太细放错层 &#x3D; 变负担。</strong> 把一次性的操作细节塞进 <code>CLAUDE.md</code>，每次会话都要为它付上下文成本。分层很重要：常驻的极简、按需的详尽。</li><li><strong>只存结果不存判断 &#x3D; 不可迁移。</strong> “这次用了方案 A”没价值；”因为 X 约束所以选 A、代价是 Y”才能迁移到下一个场景。这也是为什么博客这层不可替代——它存的是判断框架。</li></ul><h2 id="要不要这么干-我的判断框架"><a href="#要不要这么干-我的判断框架" class="headerlink" title="要不要这么干 &#x2F; 我的判断框架"></a>要不要这么干 &#x2F; 我的判断框架</h2><table><thead><tr><th>场景</th><th>判断</th></tr></thead><tbody><tr><td>你反复让 AI 做同类任务（生图、部署、某种重构）</td><td><strong>值得上</strong>：沉淀成 skill，一次写、无限复用</td></tr><tr><td>你有稳定的个人偏好和红线</td><td><strong>值得上</strong>：写进 <code>CLAUDE.md</code>，别每次重复交代</td></tr><tr><td>一次性的、不会再遇到的任务</td><td><strong>可以再等</strong>：沉淀成本大于收益</td></tr><tr><td>你享受”每次做完都让下次更快”的复利感</td><td><strong>值得上</strong></td></tr><tr><td>你还在纯手动、每次从零跟 AI 解释背景</td><td><strong>重点关注</strong>：你在重复付一笔本可以免掉的税</td></tr></tbody></table><p>我越来越确信一件事：在 Agent &#x2F; AI Infra 时代，超级个体的护城河不是”会用 AI”，而是<strong>有没有为 AI 持续建一个高质量的知识库</strong>。工具谁都能用，但你喂给它的上下文、你沉淀的判断框架，是别人复制不走的。</p><p>判断框架负责决定”做什么、不做什么、底线在哪”，AI 负责把执行力拉满。而 LLM wiki，就是把你的判断框架，翻译成 AI 每次都能读到的形式。</p><p>写到这我就明白了：这篇博客本身，也是我 LLM wiki 里的一页。它遵守我今天刚写进 <code>CLAUDE.md</code> 的规则——至少配 3 张插图。</p><p>挺自洽的，也挺酷的。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/b612b935f3f6/</id>
    <link href="https://blog.zisheng.pro/posts/b612b935f3f6/"/>
    <published>2026-07-10T02:30:00.000Z</published>
    <summary>这篇文章想聊一个我最近才想明白的事：当我把&quot;怎么做一件事&quot;沉淀进 CLAUDE.md 规则、Claude Code skill、还有博客文章时，我其实是在为 AI 建一个知识库——一个 LLM wiki。它和传统给人看的 wiki 不一样：读者是 LLM，所以要可执行、有触发词、能被 ground truth 校验。文章结合我这半年从前端转 Agent 的实践，讲清这三种沉淀载体各自的定位，以及怎么写才能真正被复用。</summary>
    <title>我在给自己建一个 LLM wiki</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="AI" scheme="https://blog.zisheng.pro/categories/AI/"/>
    <category term="Agent" scheme="https://blog.zisheng.pro/tags/Agent/"/>
    <category term="Claude Code" scheme="https://blog.zisheng.pro/tags/Claude-Code/"/>
    <category term="macOS" scheme="https://blog.zisheng.pro/tags/macOS/"/>
    <category term="Swift" scheme="https://blog.zisheng.pro/tags/Swift/"/>
    <category term="Owlet" scheme="https://blog.zisheng.pro/tags/Owlet/"/>
    <category term="vibe coding" scheme="https://blog.zisheng.pro/tags/vibe-coding/"/>
    <category term="Codex" scheme="https://blog.zisheng.pro/tags/Codex/"/>
    <content>
      <![CDATA[<p>过去一个月，我的 Mac 菜单栏里多了一只小猫头鹰。</p><p>它叫 Owlet，是我用 Claude Code 和 Codex 一起 vibe 出来的 macOS 小工具：常驻菜单栏，实时显示 Claude Code 和 Codex 的 token 消耗，附带一个贴合刘海的灵动岛面板、一个桌面灵动卡、还有一个看历史用量和排名的 dashboard。</p><p>做这个 app 的动机很私人。今年我开始从前端往 Agent &#x2F; AI Infra 方向转，每天大部分时间都在跟 Claude Code 和 Codex 泡在一起。泡着泡着就产生了一个很朴素的疑问：<strong>我每天到底烧了多少 token？钱花哪儿了？哪个项目最费钱？</strong> 现有的工具都不直接回答这个问题，那我就自己做一个。</p><p>于是就有了 Owlet。</p><p><img src="/images/owlet-vibe-coding-cover.webp" alt="Owlet 主视觉"></p><h2 id="一句话总结"><a href="#一句话总结" class="headerlink" title="一句话总结"></a>一句话总结</h2><p><strong>Claude Code 负责架构、长上下文和需要判断力的重构；Codex 负责快速试错和小范围实现；我负责拍板、验收和把家乡青山湖的颜色塞进 UI。</strong> 三者分工明确时，vibe coding 真的能把一个 idea 从 0 推到能用。</p><h2 id="为什么是一只猫头鹰"><a href="#为什么是一只猫头鹰" class="headerlink" title="为什么是一只猫头鹰"></a>为什么是一只猫头鹰</h2><p>Owlet 的英文意思是“小猫头鹰”。图标是一只像素风猫头鹰，缩在菜单栏里，眼睛一眨一眨地观察你的 AI 会话。</p><p>选它有两个原因。一是气质贴合：这个 app 做的就是“盯着你用 AI 的过程”，猫头鹰那种夜间警觉、悄无声息的感觉，比什么“机器人”“仪表盘”都更对味。二是可爱——macOS 菜单栏天天见的东西，不能太硬核，得让人愿意让它一直待着。</p><p>定下名字那晚我对 Claude 说：</p><blockquote><p>“根据现在帧动画里猫头鹰 icon 的形象重新命名 app。”</p></blockquote><p>最后从几个候选里挑了 Owlet。短小、好读、不容易撞名，而且一听就知道跟 owl 有关。</p><p>这就是独立开发的好处：审美和命名这种在别人公司要开三天会的决定，自己一个人一晚上就能拍板。</p><h2 id="Claude-Code-是架构师，Codex-是快枪手"><a href="#Claude-Code-是架构师，Codex-是快枪手" class="headerlink" title="Claude Code 是架构师，Codex 是快枪手"></a>Claude Code 是架构师，Codex 是快枪手</h2><p><img src="/images/owlet-vibe-coding-workbench.webp" alt="Claude Code + Codex 双开工作台"></p><p>这个项目里我同时开了两条 AI 线：Claude Code 和 Codex。</p><p><strong>Claude Code 是我的主架构师。</strong> 它的长上下文能力太适合这种代码量不大但跨模块关联多的项目。比如设计实时 session 监控链路时，我需要同时理解：</p><ul><li>Claude Code 的 hook 机制（<code>owlet-hook.py</code> 怎么装、怎么往 Unix socket 发事件）</li><li>macOS 上 <code>NSPanel</code> 和 SwiftUI 的边界行为</li><li>事件驱动的文件监听该怎么 debounce</li><li>SQLite 里 session、hourly、daily 三张表怎么 upsert 不丢数据</li></ul><p>这种时候我会把问题抛给 Claude Code，让它先给方案，我再挑毛病。</p><p><strong>Codex 则是我的快枪手。</strong> 有些改动范围很小、很具体，不需要长篇讨论，直接让 Codex 上。比如：</p><blockquote><p>“Codex 判断了有没有刘海的逻辑，删了。”</p><p>“这个黑框是为啥，这也是 Codex 今天下午加的。”</p></blockquote><p>Codex 的特点是快，但偶尔会留下一些“看起来能动，但边界没处理干净”的痕迹。那个黑框就是典型：它在刘海屏设备上加了一层黑色背景，意图是隐藏刘海，结果在非刘海屏上反而露了馅。最后还是我拉着 Claude Code 一起复盘，把透明 panel 的圆角和 masksToBounds 重新理了一遍才解决。</p><p>这件事让我明确了一条分工：<strong>需要理解系统边界和做取舍的，找 Claude Code；需要快速验证一个想法的，找 Codex。</strong></p><h2 id="一个-idea-长出四块屏幕"><a href="#一个-idea-长出四块屏幕" class="headerlink" title="一个 idea 长出四块屏幕"></a>一个 idea 长出四块屏幕</h2><p><img src="/images/owlet-lingdong-family.webp" alt="Owlet 的灵动家族：灵动岛、灵动卡、灵动栏、灵动板"></p><p>Owlet 的 UI 表面不算多，但每一块都有自己的坑：</p><table><thead><tr><th>Surface</th><th>作用</th><th>难点</th></tr></thead><tbody><tr><td>Menu bar</td><td>常驻图标 + token 总数</td><td>图标动画不能太耗电</td></tr><tr><td>Floating island</td><td>刘海区实时会话面板</td><td><code>NSPanel</code> 圆角、多屏、透明度</td></tr><tr><td>Dashboard</td><td>历史用量、排名、设置</td><td>SwiftUI 图表和数据库查询</td></tr><tr><td>Dynamic card</td><td>桌面小组件式面板</td><td>像系统 widget，但不能真用 WidgetKit</td></tr></tbody></table><p>最有意思的是命名。某天我突然说：</p><blockquote><p>“刷新数据放到标题旁边怎么样？还有在灵动家族给这个菜单起个名字，一家人要整整齐齐。”</p></blockquote><p>于是就有了“灵动家族”：灵动岛、灵动卡、灵动栏。这个命名一点都不高大上，但它让产品在聊天时变得很好描述。<strong>“灵动卡又崩了”</strong> 比 <strong>“桌面浮动窗口崩溃了”</strong> 可爱多了。</p><p>另一个让我印象深刻的是灵动卡的崩溃排查。同样是 Apple Silicon、同样是 macOS 15，有的机器能打开，有的机器直接崩。最后定位到问题不在业务逻辑，而在 AppKit window level、<code>NSPanel</code>、系统材质和透明圆角叠加后的边界行为。那篇文章我已经单独发过，这里不再展开。</p><h2 id="把青山湖写进代码"><a href="#把青山湖写进代码" class="headerlink" title="把青山湖写进代码"></a>把青山湖写进代码</h2><p><img src="/images/owlet-qingshan-lake-palette.webp" alt="Owlet 的青山湖配色体系：蓝雾、湖青、杉绿、雾白、晨光浅金"></p><p>我是杭州临安人，青山湖就在家门口。Owlet 的视觉方向最后定成了：<strong>像原生 macOS 工具，但从青山湖的湖水、杉林、青山、雾白里取色。</strong></p><p>具体 Palette 长这样：</p><ul><li>蓝雾天空 → 浅背景</li><li>浮萍湖绿 → 主要强调色</li><li>水杉深绿 → 深色文字和进度条</li><li>雾带白 → 面板底色</li><li>晨光浅金 → 仅用于边缘光和状态提示</li><li>雨后湿木 → 少量阴影和分隔</li></ul><p>我甚至专门给 Claude 下了一条规则：</p><blockquote><p>“晨光浅金只用于边缘光、状态提示或小面积 accent，不能主导整个 surface。”</p></blockquote><p>因为项目早期用过一段时间黄&#x2F;金色做主色，结果越调越脏，最后全推翻了。改成青山湖色系后，整个 app 立刻从“网吧风”回到了“能放在 macOS 桌面上不丢人”的状态。</p><h2 id="性能偏执：一个-menu-bar-app-的电量焦虑"><a href="#性能偏执：一个-menu-bar-app-的电量焦虑" class="headerlink" title="性能偏执：一个 menu bar app 的电量焦虑"></a>性能偏执：一个 menu bar app 的电量焦虑</h2><p>Owlet 是常驻应用。只要 Claude 不活跃，它就不应该醒来。</p><p>为此我们做了几件看起来很细节、但很重要的事：</p><ol><li><strong>文件监听用 Dispatch Source，而不是轮询。</strong> 之前有些实现会 sleep-loop 检查 active session，我全部砍掉了。</li><li><strong>JSONL 增量读取。</strong> 50 MB 的会话日志不应该造成 50 MB 内存峰值。ClaudeCodeAdapter 用 <code>(mtime, fileSize, byteOffset)</code> 做缓存，文件增长时只读新增 bytes。</li><li><strong>动画门控。</strong> 菜单栏猫头鹰只有 token 总数变化时才脉冲；dashboard 和 island 只有 active session 存在时才按 0.2s 帧率动画，idle 时最多每秒一帧。</li><li><strong>数据库 upsert 而不是 deleteAll + insert。</strong> 这个改动很小，但能避免大量无用写入。</li></ol><p>这些规则都被写进了 <code>CLAUDE.md</code> 和 <code>AGENTS.md</code>，成为后续所有改动的底线。<strong>一个工具要是自己先变成电老虎，就没资格监控别人的 token 消耗。</strong></p><h2 id="AI-协作的边界：它不是替你做主"><a href="#AI-协作的边界：它不是替你做主" class="headerlink" title="AI 协作的边界：它不是替你做主"></a>AI 协作的边界：它不是替你做主</h2><p>整个过程中，AI 没有替我做任何一个真正重要的决定。</p><ul><li>要不要做这个项目？我决定。</li><li>功能优先级怎么排？我决定。</li><li>命名 Owlet？我决定。</li><li>青山湖配色方向？我决定。</li><li>性能规则里哪些不能回退？我决定。</li></ul><p>AI 做的是：<strong>把我模糊的判断快速翻译成可执行的代码和文档，并在执行过程中帮我规避低级错误。</strong></p><p>这让我越来越确信一个判断框架：在 Agent &#x2F; AI Infra 时代，人的核心竞争力不是写代码的速度，而是<strong>判断框架</strong>——知道什么该做、什么不该做、什么要取舍、什么是底线。AI 负责把判断拉满执行力，两者结合才是极致交付速度。</p><h2 id="要不要用-我的判断框架"><a href="#要不要用-我的判断框架" class="headerlink" title="要不要用 &#x2F; 我的判断框架"></a>要不要用 &#x2F; 我的判断框架</h2><p>如果你也想尝试 vibe coding 一个小工具，我的建议是：</p><table><thead><tr><th>场景</th><th>判断</th></tr></thead><tbody><tr><td>想做一个小而美的个人工具</td><td><strong>值得上</strong></td></tr><tr><td>项目涉及大量系统边界（macOS 权限、沙盒、codesign）</td><td><strong>重点关注</strong></td></tr><tr><td>需要长期维护和多端同步</td><td><strong>可以再等</strong></td></tr><tr><td>你享受“说一句话，AI 帮你验证”的快感</td><td><strong>值得上</strong></td></tr><tr><td>你无法接受 AI 偶尔留下小坑</td><td><strong>可以再等</strong></td></tr></tbody></table><p>对我来说，Owlet 是一个完美的实验场：规模可控、用户就是自己、技术栈有挑战性但不需要服务百万用户。如果后续它能帮更多开发者看清 AI 使用成本，那就更好了。</p><p>现在菜单栏里那只小猫头鹰还在闪。它看着自己诞生的过程，也看着我每天烧掉的 token。</p><p>挺 meta 的，也挺酷的。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/8a33f17a26ad/</id>
    <link href="https://blog.zisheng.pro/posts/8a33f17a26ad/"/>
    <published>2026-07-09T18:30:00.000Z</published>
    <summary>这篇文章记录我用 Claude Code 和 Codex 以 vibe coding 方式做一个 macOS menu-bar 应用的全过程：从最初想“看看我每天烧了多少 token”，到做出 Owlet 这个能实时监控 Claude Code 和 Codex 使用量的小工具。过程中有 AI 双开协作的节奏、把家乡青山湖风景写进配色的执念，也有对 menu-bar app 电量的偏执。</summary>
    <title>我用 Claude Code + Codex vibe 了一个 macOS App：Owlet</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="AI" scheme="https://blog.zisheng.pro/categories/AI/"/>
    <category term="Agent" scheme="https://blog.zisheng.pro/tags/Agent/"/>
    <category term="macOS" scheme="https://blog.zisheng.pro/tags/macOS/"/>
    <category term="SwiftUI" scheme="https://blog.zisheng.pro/tags/SwiftUI/"/>
    <category term="AppKit" scheme="https://blog.zisheng.pro/tags/AppKit/"/>
    <category term="Argos" scheme="https://blog.zisheng.pro/tags/Argos/"/>
    <content>
      <![CDATA[<p>最近给 Argos 做了一个桌面上的“灵动卡”：它不属于主窗口，也不是菜单栏 popover，而是一个常驻桌面的轻量信息面板，用来展示当天 Claude Code 和 Codex 的 Token 用量。</p><p>视觉上它应该接近系统桌面小组件，有一点毛玻璃、有圆角、有环境透色；交互上它又必须像一个普通 Mac app surface，可以拖动、可以关闭、可以从设置里重新打开。问题也出在这里：我们很容易把“看起来像系统小组件”和“实现方式也像系统小组件”混成一件事。</p><p>这次的真实反馈很直接：用户点击打开灵动卡，Argos 直接崩溃。</p><p><img src="/images/argos-dynamic-card-crash-dialog.webp" alt="Argos 崩溃弹窗"></p><h2 id="一句话总结"><a href="#一句话总结" class="headerlink" title="一句话总结"></a>一句话总结</h2><p>macOS 桌面浮层的稳定性不能只看 SwiftUI 视图本身，真正危险的是 AppKit window level、<code>NSPanel</code> 行为、系统材质、透明圆角和启动持久化一起叠加后的系统边界。</p><h2 id="这不是一个普通-SwiftUI-bug"><a href="#这不是一个普通-SwiftUI-bug" class="headerlink" title="这不是一个普通 SwiftUI bug"></a>这不是一个普通 SwiftUI bug</h2><p>最开始我也倾向于按普通 UI bug 排查：是不是某个 <code>@EnvironmentObject</code> 没注入？是不是 <code>TimelineView</code> 刷新太频繁？是不是某个统计数据为空导致 view body 崩了？</p><p>但反馈里有两个信号很关键：</p><table><thead><tr><th>现象</th><th>初步判断</th></tr></thead><tbody><tr><td>只有部分机器崩</td><td>不是纯业务数据问题，更像系统组合差异</td></tr><tr><td>重装也无法恢复</td><td>可能和持久化设置有关，应用启动后自动走到崩溃路径</td></tr><tr><td>打开灵动卡才崩</td><td>主窗口、菜单栏、数据读取不是第一怀疑对象</td></tr><tr><td>同类机器不一定复现</td><td>芯片型号只是表象，系统窗口栈和显示环境也会影响</td></tr></tbody></table><p>这类问题最麻烦的地方是：你的本机不崩，不代表代码没问题。macOS 的 window server、桌面组件层级、Stage Manager、多屏、刘海屏、透明材质都可能让同一段代码走到不同系统路径。</p><h2 id="真正危险的是-window-level"><a href="#真正危险的是-window-level" class="headerlink" title="真正危险的是 window level"></a>真正危险的是 window level</h2><p>灵动卡一开始为了“悬浮在桌面上方”，用了更高的窗口层级。视觉上这很合理：它不应该被普通窗口轻易盖住。</p><p>但 AppKit 的窗口层级不是一个单纯的 z-index。不同 level 背后隐含了不同的系统行为：</p><table><thead><tr><th>层级选择</th><th>优点</th><th>风险</th></tr></thead><tbody><tr><td><code>.normal</code></td><td>最稳定，接近普通窗口</td><td>可能被其他窗口盖住</td></tr><tr><td><code>.floating</code></td><td>更符合悬浮卡片直觉</td><td>更容易进入特殊窗口路径</td></tr><tr><td>desktop widget 附近</td><td>视觉接近系统小组件</td><td>系统版本、桌面状态、多屏下差异最大</td></tr></tbody></table><p>最后我把层级选择暴露到了设置里：默认用最稳的普通窗口，用户可以自己切到浮动或桌面组件风格。这个决策不够“产品洁癖”，但足够工程务实。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">switch</span> windowLevel &#123;</span><br><span class="line"><span class="keyword">case</span> .normal:</span><br><span class="line">    panel.isFloatingPanel <span class="operator">=</span> <span class="literal">false</span></span><br><span class="line">    panel.level <span class="operator">=</span> .normal</span><br><span class="line"><span class="keyword">case</span> .floating:</span><br><span class="line">    panel.isFloatingPanel <span class="operator">=</span> <span class="literal">true</span></span><br><span class="line">    panel.level <span class="operator">=</span> .floating</span><br><span class="line"><span class="keyword">case</span> .desktopWidget:</span><br><span class="line">    panel.isFloatingPanel <span class="operator">=</span> <span class="literal">false</span></span><br><span class="line">    panel.level <span class="operator">=</span> <span class="type">NSWindow</span>.<span class="type">Level</span>(rawValue: <span class="type">Int</span>(<span class="type">CGWindowLevelForKey</span>(.desktopIconWindow)) <span class="operator">+</span> <span class="number">1</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个代码片段的重点不是 API 本身，而是思路：把“视觉偏好”从“稳定默认值”里拆出来。默认路径必须保守，高风险路径可以给高级用户选择。</p><h2 id="透明圆角-panel-不是只画一个-RoundedRectangle"><a href="#透明圆角-panel-不是只画一个-RoundedRectangle" class="headerlink" title="透明圆角 panel 不是只画一个 RoundedRectangle"></a>透明圆角 panel 不是只画一个 RoundedRectangle</h2><p>第二个坑是透明圆角。</p><p>SwiftUI 里画一个 <code>RoundedRectangle</code> 很简单，但 <code>NSPanel</code> 本身仍然是一个 AppKit window。如果只处理 SwiftUI 背景，不处理 <code>NSHostingView</code> 和 window content view，系统可能仍然按矩形 frame 计算阴影、命中区域和合成边界。</p><p>这就是为什么透明 panel 要同时处理两层：</p><table><thead><tr><th>层</th><th>要处理什么</th></tr></thead><tbody><tr><td>SwiftUI view</td><td><code>clipShape</code>、背景、边框、视觉层次</td></tr><tr><td>AppKit hosting&#x2F;container</td><td>clear background、cornerRadius、continuous corner、masksToBounds</td></tr></tbody></table><p>尤其是透明窗口，最好不要依赖 AppKit 默认 shadow。默认 shadow 是按 window frame 投影的，frame 是矩形，圆角外就可能出现多出来的直角、脏边或奇怪边界。</p><h2 id="玻璃质感也要分“安全”和“不安全”"><a href="#玻璃质感也要分“安全”和“不安全”" class="headerlink" title="玻璃质感也要分“安全”和“不安全”"></a>玻璃质感也要分“安全”和“不安全”</h2><p>桌面小组件风格很容易让人想到系统 material，比如 <code>.ultraThinMaterial</code>、<code>NSVisualEffectView</code>、<code>behindWindow</code>、<code>underWindowBackground</code>。</p><p>但这次排查里，我对灵动卡做了一个取舍：先不用 AppKit 的 <code>NSVisualEffectView</code> 做底层材质，而是用 SwiftUI 的轻量 blur、半透明 tint、渐变 stroke 和局部 glow 模拟“雾白玻璃”。</p><p>原因很简单：灵动卡本身已经是一个透明 <code>NSPanel</code>，再叠系统 material 和特殊 window level，变量太多。为了排查和发布稳定性，先减少系统合成路径。</p><table><thead><tr><th>方案</th><th align="right">质感</th><th align="right">稳定性</th><th>当前选择</th></tr></thead><tbody><tr><td><code>NSVisualEffectView</code> 强系统材质</td><td align="right">高</td><td align="right">中低</td><td>暂缓</td></tr><tr><td>SwiftUI blur + tint</td><td align="right">中高</td><td align="right">高</td><td>采用</td></tr><tr><td>纯实色卡片</td><td align="right">中</td><td align="right">最高</td><td>不采用</td></tr></tbody></table><p>这也是我现在对 macOS 视觉实现的一个判断：能用系统能力当然好，但一旦 surface 是透明浮窗、跨桌面、多屏、可拖动，还要启动自动恢复，就不能盲目堆 material。</p><h2 id="持久化是体验，也是事故放大器"><a href="#持久化是体验，也是事故放大器" class="headerlink" title="持久化是体验，也是事故放大器"></a>持久化是体验，也是事故放大器</h2><p>还有一个容易忽略的点：灵动卡是否应该持久化打开状态。</p><p>从产品体验看，答案是应该。用户打开了桌面卡片，下次启动自然还应该在。默认也应该打开，否则用户可能根本不知道这个能力存在。</p><p>但从事故恢复看，持久化会放大问题：如果“打开灵动卡”本身会崩，那么应用每次启动自动恢复灵动卡，就会变成“打开应用即崩”，用户连进设置关闭它的机会都没有。</p><p>所以这里有一个典型工程取舍：</p><table><thead><tr><th>策略</th><th align="right">体验</th><th align="right">故障恢复</th></tr></thead><tbody><tr><td>不持久化，启动默认关闭</td><td align="right">差</td><td align="right">好</td></tr><tr><td>持久化，启动默认打开</td><td align="right">好</td><td align="right">差</td></tr><tr><td>持久化 + 稳定默认层级</td><td align="right">较好</td><td align="right">可接受</td></tr></tbody></table><p>最后我选择恢复持久化和默认打开，但把默认 window level 降到普通窗口，并把高层级选择放到设置里。这不是绝对安全，但比“默认走最高风险路径”合理得多。</p><h2 id="为什么我的机器不崩，用户机器会崩"><a href="#为什么我的机器不崩，用户机器会崩" class="headerlink" title="为什么我的机器不崩，用户机器会崩"></a>为什么我的机器不崩，用户机器会崩</h2><p>这个问题很值得单独说。</p><p>两个 Mac 看起来“除了芯片都一样”，并不代表窗口系统环境一样。至少这些因素都可能不同：</p><table><thead><tr><th>差异点</th><th>可能影响</th></tr></thead><tbody><tr><td>芯片&#x2F;显卡路径</td><td>WindowServer 合成、透明 blur、外接屏策略</td></tr><tr><td>系统小版本</td><td>AppKit &#x2F; SwiftUI bridge 行为变化</td></tr><tr><td>Stage Manager &#x2F; 桌面组件状态</td><td>桌面层级和特殊 window level 行为</td></tr><tr><td>多屏与刘海屏</td><td>默认定位、visibleFrame、notch 区域</td></tr><tr><td>辅助显示工具</td><td>BetterDisplay、HiDPI、虚拟屏可能改变 screen frame</td></tr><tr><td>历史设置</td><td>上一次保存的 window level、位置、visible 状态</td></tr></tbody></table><p>所以这类 bug 不能只问“为什么我不复现”。更有效的问题是：哪些系统路径只有对方会走到？哪些设置会让用户每次启动都自动进入危险路径？</p><h2 id="我的判断框架"><a href="#我的判断框架" class="headerlink" title="我的判断框架"></a>我的判断框架</h2><p>以后再做 macOS 上的桌面浮层，我会按这个顺序判断：</p><ol><li>默认层级先保守：普通窗口优先，浮动和桌面组件风格作为可选项。</li><li>透明圆角同时处理 SwiftUI 和 AppKit，不只画背景。</li><li>系统 material 先少用，等稳定性验证后再加。</li><li>持久化状态要配合故障恢复路径，否则一个开关就可能把用户锁死在崩溃循环里。</li><li>对“我本机不崩”的结论保持怀疑，多看系统版本、显示环境和历史设置。</li></ol><h2 id="要不要用-我的判断框架"><a href="#要不要用-我的判断框架" class="headerlink" title="要不要用 &#x2F; 我的判断框架"></a>要不要用 &#x2F; 我的判断框架</h2><p>如果你只是做一个普通工具窗口，不建议一上来就追系统小组件质感。先把窗口层级、圆角、拖动、恢复、关闭这些基础行为做稳。</p><p>如果你确实要做桌面小组件式 surface，可以上玻璃质感，但默认路径要克制。我的建议是：默认普通层级 + 安全 blur + 可选高级层级。等收集到足够多真实设备反馈后，再决定要不要把更像系统小组件的层级设为默认。</p><p>这次踩坑对我的提醒是：macOS 原生体验不是“看起来像原生”就够了，它还要求你尊重 AppKit 的历史包袱和窗口系统边界。尤其是 Agent 工具这种常驻型应用，稳定性永远比一个更漂亮的默认层级更重要。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/a6040b00245e/</id>
    <link href="https://blog.zisheng.pro/posts/a6040b00245e/"/>
    <published>2026-07-09T14:38:30.000Z</published>
    <summary>这篇文章记录我在做 Argos 灵动卡时踩到的一次 macOS 桌面浮窗崩溃：同样是 Apple Silicon、同样是新版系统，有的机器稳定，有的机器一打开就崩。真正的问题不在业务逻辑，而在 AppKit window level、透明 panel、系统材质和启动持久化策略叠加后的边界行为。</summary>
    <title>一次 macOS 灵动卡崩溃排查：别把桌面小组件当普通窗口</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="AI Infra" scheme="https://blog.zisheng.pro/categories/AI-Infra/"/>
    <category term="macOS" scheme="https://blog.zisheng.pro/tags/macOS/"/>
    <category term="SwiftUI" scheme="https://blog.zisheng.pro/tags/SwiftUI/"/>
    <category term="Argos" scheme="https://blog.zisheng.pro/tags/Argos/"/>
    <category term="Particle System" scheme="https://blog.zisheng.pro/tags/Particle-System/"/>
    <category term="Canvas" scheme="https://blog.zisheng.pro/tags/Canvas/"/>
    <category term="Animation" scheme="https://blog.zisheng.pro/tags/Animation/"/>
    <category term="UI Engineering" scheme="https://blog.zisheng.pro/tags/UI-Engineering/"/>
    <content>
      <![CDATA[<p>今天给 Argos 做了一个“段位晋级彩纸特效”。</p><p>听起来是个很小的 UI 动画：用户 token 用量触发段位变化时，全屏弹一下彩纸，提示“黄金 III”之类的晋级结果。但真做起来，我发现这类效果很容易翻车。粒子太少，不像庆祝；粒子太多，像噪音。彩带想做飘逸，结果像屏幕上画了几条乱线。加爆点，本意是增强“爆炸感”，实际像廉价烟花。加光晕，本意是高级，实际会脏。</p><p>这篇文章复盘这次踩坑。重点不是 SwiftUI 代码，而是把粒子系统里那些概念讲清楚：<strong>particle、emitter、confetti、streamer、glint、ambient sparkles、bloom、burst、motion field、lifetime、distribution</strong>。以后再做类似 UI 特效，至少知道自己在调什么。</p><h2 id="一句话总结"><a href="#一句话总结" class="headerlink" title="一句话总结"></a>一句话总结</h2><p>UI 里的庆祝特效，本质是一个小型 particle system。好看的关键不是“粒子越多越好”，而是让粒子的<strong>出生位置、生命周期、运动方向、密度分布、视觉层级</strong>服务同一个动势。</p><hr><h2 id="这次到底做了什么"><a href="#这次到底做了什么" class="headerlink" title="这次到底做了什么"></a>这次到底做了什么</h2><p>Argos 里有一个段位系统：用户每天使用 Claude Code &#x2F; Codex 的 token 会累计，达到某个 milestone 时触发段位变化。</p><p>我想要的效果是：</p><ol><li>段位变化时出现全屏庆祝反馈。</li><li>中间有一个段位 toast，例如“黄金 III”。</li><li>屏幕上有彩纸、亮片、星芒，形成“晋级”的瞬间感。</li><li>只在调试环境有“测试彩纸”按钮，正式逻辑仍由真实段位变化触发。</li></ol><p>最终通过的版本是：</p><table><thead><tr><th>元素</th><th align="right">最终保留</th><th>原因</th></tr></thead><tbody><tr><td>Confetti rectangles</td><td align="right">是</td><td>语义最清晰，一眼就是庆祝</td></tr><tr><td>Circular chips</td><td align="right">是</td><td>补充面积和层次，不抢主视觉</td></tr><tr><td>Diamond glints</td><td align="right">是</td><td>增加高光和精致感</td></tr><tr><td>Small cross sparkles</td><td align="right">是</td><td>少量点缀，强化“闪一下”</td></tr><tr><td>Streamers &#x2F; 彩带</td><td align="right">否</td><td>方向、曲线、交叉都很难调，容易显脏</td></tr><tr><td>Burst &#x2F; 爆点</td><td align="right">否</td><td>在这个场景里太像烟花，不符合 macOS 工具气质</td></tr><tr><td>Heavy bloom &#x2F; 大光晕</td><td align="right">否</td><td>容易污染画面，让文字和桌面背景变糊</td></tr></tbody></table><p>这其实是一个很典型的 UI 粒子系统取舍：<strong>不是把所有效果都加上，而是删到只剩能稳定表达语义的元素</strong>。</p><hr><h2 id="Particle：粒子不是“点”，而是一个小演员"><a href="#Particle：粒子不是“点”，而是一个小演员" class="headerlink" title="Particle：粒子不是“点”，而是一个小演员"></a>Particle：粒子不是“点”，而是一个小演员</h2><p>很多人听到 particle 会下意识理解成“点”。但在 UI 动画里，一个 particle 更像一个小演员，它至少有这些属性：</p><table><thead><tr><th>属性</th><th>含义</th><th>彩纸特效里的例子</th></tr></thead><tbody><tr><td>position</td><td>当前坐标</td><td>彩纸当前在屏幕哪里</td></tr><tr><td>velocity</td><td>速度</td><td>往左上、右上、下落还是漂移</td></tr><tr><td>acceleration</td><td>加速度</td><td>重力、阻尼、回弹</td></tr><tr><td>lifetime</td><td>生命周期</td><td>0.8s 出现，3.5s 消失</td></tr><tr><td>size</td><td>尺寸</td><td>小彩纸、大亮片</td></tr><tr><td>rotation</td><td>旋转</td><td>彩纸翻转角度</td></tr><tr><td>opacity</td><td>透明度</td><td>出现、停留、淡出</td></tr><tr><td>color</td><td>颜色</td><td>高饱和彩纸色、湖水青、晨光金</td></tr><tr><td>shape</td><td>形状</td><td>矩形、圆形、菱形、星芒</td></tr></tbody></table><p>所以“加一点彩纸”不是加几个随机矩形，而是定义一组小对象：它们什么时候出生、从哪里出生、往哪里走、多久消失、怎么旋转、颜色如何分布。</p><p>这次一开始怪异，根因就是我把粒子看成“画面装饰”，而不是一个系统。粒子的运动方向、密度和语义没对齐，就会出现“从上往下落”“一坨挤在一起”“不够满”“没有爆炸感”这些问题。</p><hr><h2 id="Emitter：发射器决定第一眼的动势"><a href="#Emitter：发射器决定第一眼的动势" class="headerlink" title="Emitter：发射器决定第一眼的动势"></a>Emitter：发射器决定第一眼的动势</h2><p>Emitter 是粒子系统里的发射器。它决定粒子从哪里出生。</p><p>常见 emitter 有几种：</p><table><thead><tr><th>Emitter 类型</th><th>视觉效果</th><th>适用场景</th></tr></thead><tbody><tr><td>Point emitter</td><td>从一个点喷出</td><td>烟花、爆炸、魔法点</td></tr><tr><td>Line emitter</td><td>从一条线喷出</td><td>雨、雪、瀑布、顶部落彩纸</td></tr><tr><td>Area emitter</td><td>从一个区域出现</td><td>环境漂浮、空间尘埃</td></tr><tr><td>Edge emitter</td><td>从屏幕边缘进入</td><td>庆祝彩纸、舞台入场</td></tr><tr><td>Multi-emitter</td><td>多个发射源</td><td>全屏庆祝、游戏技能</td></tr></tbody></table><p>这次最大的教训是：<strong>发射器位置会强烈决定用户对动画的理解</strong>。</p><p>最开始用户反馈“怎么是从上往下落的”，这说明粒子的 emitter 或 velocity 让人联想到“掉落”，而不是“庆祝喷发”。彩纸如果主要从顶部进入，语义就偏“下雨”；如果从左右两侧或底部斜向上出现，语义才接近“庆祝”。</p><p>后来我尝试让彩带从左右两边飞出，但又出现了新问题：左右彩带在中间交叉、起点过于集中、曲线不丝滑。这说明 emitter 不是只看一个点，还要看<strong>发射区域分布</strong>。</p><p>更准确地说，emitter 要调这几个参数：</p><table><thead><tr><th>参数</th><th>作用</th></tr></thead><tbody><tr><td>origin</td><td>发射源位置</td></tr><tr><td>spread</td><td>发射范围</td></tr><tr><td>angle</td><td>初速度方向</td></tr><tr><td>variance</td><td>随机扰动</td></tr><tr><td>rate</td><td>单位时间生成多少粒子</td></tr><tr><td>burst count</td><td>瞬间生成多少粒子</td></tr></tbody></table><p>最终我放弃彩带后，保留了更稳定的 area&#x2F;edge 混合：彩纸从不同区域出现，星芒覆盖更大的屏幕区域，但不再让连续线条承担主视觉。</p><hr><h2 id="Confetti：彩纸是最稳的庆祝语义"><a href="#Confetti：彩纸是最稳的庆祝语义" class="headerlink" title="Confetti：彩纸是最稳的庆祝语义"></a>Confetti：彩纸是最稳的庆祝语义</h2><p>Confetti 就是彩纸。它是庆祝动画里最稳定、最不需要解释的元素。</p><p>UI 里常见的 confetti 粒子通常是：</p><table><thead><tr><th>形状</th><th>视觉特点</th></tr></thead><tbody><tr><td>矩形条</td><td>最像真实彩纸</td></tr><tr><td>小方块</td><td>更像游戏奖励</td></tr><tr><td>圆片</td><td>像亮片或纸屑</td></tr><tr><td>菱形</td><td>更有闪光感</td></tr><tr><td>短弧片</td><td>更像翻转中的纸片</td></tr></tbody></table><p>这次最终保留的是亮色矩形彩纸。原因很简单：它的语义最强。</p><p>彩纸要好看，核心不是形状复杂，而是三件事：</p><ol><li><strong>颜色要亮</strong>：庆祝粒子不能太灰，否则像灰尘。</li><li><strong>尺寸要有层次</strong>：全是同一尺寸会机械。</li><li><strong>旋转要变化</strong>：不旋转就像静态贴纸。</li></ol><p>我一开始颜色不够亮，用户直接指出“彩纸色彩要亮”。这很关键。UI 主体可以克制，但庆祝粒子本身必须高饱和，否则它的情绪表达不成立。</p><p>这里有一个设计边界：<strong>背景和主 UI 需要低饱和，粒子可以高饱和</strong>。如果整个界面都高饱和，会廉价；但庆祝粒子高饱和，是语义需要。</p><hr><h2 id="Streamer：彩带为什么最容易翻车"><a href="#Streamer：彩带为什么最容易翻车" class="headerlink" title="Streamer：彩带为什么最容易翻车"></a>Streamer：彩带为什么最容易翻车</h2><p>Streamer 是长彩带、飘带。它的问题比彩纸复杂很多。</p><p>彩纸是离散粒子，单个粒子不好看也不会毁掉整体。彩带是连续曲线，一条不自然就非常显眼。</p><p>这次彩带翻车主要有几个原因：</p><table><thead><tr><th>问题</th><th>根因</th></tr></thead><tbody><tr><td>太高</td><td>lift 参数过大，曲线飞到屏幕顶部</td></tr><tr><td>太长</td><td>reach 过大，线条穿过视觉中心</td></tr><tr><td>挤在一起</td><td>emitter 起点过于集中</td></tr><tr><td>全是并行</td><td>控制点变化不足，曲线缺少角度差</td></tr><tr><td>交叉很尬</td><td>左右两侧没有限定各自活动区域</td></tr><tr><td>不丝滑</td><td>Bézier 控制点没有形成自然切线</td></tr></tbody></table><p>彩带要自然，至少要同时满足：</p><ol><li>起点分布不能重合。</li><li>曲线控制点要有连续的切线方向。</li><li>同侧可以有交叉，但交叉角度要自然。</li><li>左右两侧如果语义是“各自飞出”，就不能互相穿越中心。</li><li>长度、弧度、透明度要一起衰减。</li></ol><p>这就不是“加几条曲线”了，而是在调一个小型 motion field。</p><p>最后我判断：在这个产品场景里，彩带收益不够高。Argos 是一个 macOS 工具，不是游戏结算页。彩带一旦做不好，负收益很明显。所以最终删掉。</p><p>这个决策很重要：<strong>粒子系统不是越复杂越高级，删掉不稳定元素也是工程能力</strong>。</p><hr><h2 id="Glint：星芒负责“精致”，不要负责“热闹”"><a href="#Glint：星芒负责“精致”，不要负责“热闹”" class="headerlink" title="Glint：星芒负责“精致”，不要负责“热闹”"></a>Glint：星芒负责“精致”，不要负责“热闹”</h2><p>Glint 是闪光、星芒。它通常是菱形、四角星、十字星，出现在高光点附近。</p><p>它适合做：</p><ol><li>金属反光。</li><li>奖励高光。</li><li>魔法感。</li><li>“刚刚发生了好事”的瞬间提示。</li></ol><p>但 glint 不能太多。太多会变成噪点，也会像廉价贴纸。</p><p>这次最终保留了两种 glint：</p><table><thead><tr><th>类型</th><th>作用</th></tr></thead><tbody><tr><td>Diamond glints</td><td>菱形闪光，提供精致感</td></tr><tr><td>Cross sparkles</td><td>十字闪光，提供瞬间感</td></tr></tbody></table><p>它们不是主角，主角仍然是 confetti。glint 的职责是让画面从“只有纸片”变成“有奖励感”。</p><p>一个实用规则：<strong>glint 的 opacity 应该随 pulse 变化，而不是常亮</strong>。常亮的星芒像贴图；一闪一灭才像反光。</p><hr><h2 id="Ambient-Sparkles：环境闪光不是爆点"><a href="#Ambient-Sparkles：环境闪光不是爆点" class="headerlink" title="Ambient Sparkles：环境闪光不是爆点"></a>Ambient Sparkles：环境闪光不是爆点</h2><p>Ambient sparkles 可以翻译成环境闪光、氛围闪点。它和 burst 不一样。</p><table><thead><tr><th>概念</th><th>特点</th><th>观感</th></tr></thead><tbody><tr><td>Burst</td><td>从中心或局部突然爆开</td><td>强刺激、像烟花</td></tr><tr><td>Ambient sparkles</td><td>分散在画面中轻微闪烁</td><td>氛围、精致、空间感</td></tr></tbody></table><p>这次我试过加爆点，但用户反馈“不好看”。原因是爆点会把语义带向“烟花爆炸”，而 Argos 的晋级反馈更像“系统奖励”，不应该过度娱乐化。</p><p>Ambient sparkles 更适合这个场景：它们不抢段位 toast，不制造视觉中心，只是在背景层增加一点“庆祝正在发生”的环境反馈。</p><p>实现时要注意：</p><ol><li>不要集中在一个点。</li><li>不要全部同步闪。</li><li>不要持续太久。</li><li>不要比主彩纸更亮。</li></ol><p>Ambient sparkles 的价值是“补空气感”，不是“制造高潮”。</p><hr><h2 id="Bloom-Glow：光晕是调味料，不是主菜"><a href="#Bloom-Glow：光晕是调味料，不是主菜" class="headerlink" title="Bloom &#x2F; Glow：光晕是调味料，不是主菜"></a>Bloom &#x2F; Glow：光晕是调味料，不是主菜</h2><p>Bloom 是光晕、泛光。Glow 也是发光，但在 UI 语境里更泛一点。Bloom 通常指亮部向周围扩散，让画面有曝光感。</p><p>光晕很容易让效果看起来“高级”，也很容易让画面变脏。</p><p>这次我对光晕的判断是：</p><table><thead><tr><th>光效</th><th align="right">适合程度</th><th>原因</th></tr></thead><tbody><tr><td>小面积 highlight</td><td align="right">高</td><td>能增强奖励感</td></tr><tr><td>粒子边缘轻微 glow</td><td align="right">中</td><td>可以让粒子更亮</td></tr><tr><td>大面积 bloom</td><td align="right">低</td><td>会污染桌面背景和文字</td></tr><tr><td>中央强光爆点</td><td align="right">低</td><td>会抢段位 toast</td></tr></tbody></table><p>尤其在 macOS 上，全屏 overlay 下面是真实桌面和窗口。如果光晕太重，用户看到的是“脏”和“糊”，不是高级。</p><p>所以最后只保留轻量星芒和亮片描边，没有做大面积 bloom。</p><p>我的经验是：<strong>工具型产品里的 bloom，要像盐，不能像汤底</strong>。</p><hr><h2 id="Burst：爆点为什么不一定有“爆炸感”"><a href="#Burst：爆点为什么不一定有“爆炸感”" class="headerlink" title="Burst：爆点为什么不一定有“爆炸感”"></a>Burst：爆点为什么不一定有“爆炸感”</h2><p>Burst 是瞬间爆发。很多庆祝动画都会从一个中心点喷出大量粒子，形成爆炸感。</p><p>但 burst 有一个问题：它非常依赖上下文。</p><p>在游戏里，burst 很自然，因为奖励、击杀、升级本来就是强反馈。在 macOS menu bar 工具里，burst 太强就会显得浮夸。用户这次明确说“彩纸特效不要加爆点不好看”，这个判断是对的。</p><p>Burst 适合：</p><ol><li>游戏结算。</li><li>支付成功。</li><li>大型 milestone。</li><li>首次完成关键任务。</li></ol><p>Burst 不适合：</p><ol><li>高频状态变化。</li><li>工具型产品里的轻量反馈。</li><li>已经有 toast 的场景。</li><li>背景复杂、可读性重要的场景。</li></ol><p>段位晋级当然是好事，但它不是游戏胜利。Argos 的整体调性是克制的 macOS 工具，所以 burst 被删掉是合理的。</p><hr><h2 id="Motion-Field：自然感来自“方向场”，不是-random"><a href="#Motion-Field：自然感来自“方向场”，不是-random" class="headerlink" title="Motion Field：自然感来自“方向场”，不是 random"></a>Motion Field：自然感来自“方向场”，不是 random</h2><p>很多粒子系统不好看，是因为用了太多随机数。</p><p>随机数只能避免重复，不能自动产生自然感。自然感来自 motion field：一套整体一致的运动方向场。</p><p>这次我踩过的坑是：</p><ol><li>每条彩带都有随机控制点，但整体没有一致方向。</li><li>左右彩带有交叉，但交叉没有空间逻辑。</li><li>起点随机，但集中在相近区域，实际观感还是一坨。</li></ol><p>也就是说，random 只是 variation，不是 composition。</p><p>一个更靠谱的思路是：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">先定义整体动势：从两侧 / 中下区域进入，向中上区域扩散，再缓慢下落淡出</span><br><span class="line">再给每个粒子加小随机：角度 ±10°，速度 ±20%，尺寸 ±30%，delay 分层</span><br></pre></td></tr></table></figure><p>如果反过来，先给每个粒子随机，再希望整体自然，通常会失败。</p><hr><h2 id="Distribution：满不满，取决于分布，不只取决于数量"><a href="#Distribution：满不满，取决于分布，不只取决于数量" class="headerlink" title="Distribution：满不满，取决于分布，不只取决于数量"></a>Distribution：满不满，取决于分布，不只取决于数量</h2><p>用户后来反馈“感觉不够满”。第一反应容易是加数量。但只加数量会导致两个问题：</p><ol><li>原来空的地方还是空。</li><li>原来挤的地方更挤。</li></ol><p>所以要区分两个概念：</p><table><thead><tr><th>概念</th><th>解决什么</th></tr></thead><tbody><tr><td>Count</td><td>粒子数量</td></tr><tr><td>Distribution</td><td>粒子分布</td></tr></tbody></table><p>这次最终我同时做了两件事：</p><ol><li>提高粒子数量：彩纸、亮片、星芒都增加。</li><li>扩大覆盖区域：横向更接近全屏，纵向从中下区域扩到中上&#x2F;中下区域。</li></ol><p>这才让画面变“满”。如果只加 count，不改 distribution，只会更乱。</p><p>一个实用判断：如果你觉得画面“不够满”，先看热力图，不要先加粒子。</p><hr><h2 id="Lifetime：动画太快，本质是生命周期太短"><a href="#Lifetime：动画太快，本质是生命周期太短" class="headerlink" title="Lifetime：动画太快，本质是生命周期太短"></a>Lifetime：动画太快，本质是生命周期太短</h2><p>用户一开始还反馈“太快了”。这通常不是单个粒子速度的问题，而是 lifetime 的问题。</p><p>Lifetime 包含：</p><ol><li>出生 delay。</li><li>可见时间。</li><li>fade out 时间。</li><li>整体 overlay 持续时间。</li></ol><p>如果所有粒子几乎同时出现、同时消失，即使速度不快，也会显得“啪一下没了”。如果 delay 分层，粒子批次进入，动画会更有呼吸。</p><p>这次我把整体展示时间拉长，并让不同粒子有不同 delay。这样用户看到的是一段庆祝过程，而不是一帧贴图。</p><hr><h2 id="Toast：中心信息不能和粒子抢主视觉"><a href="#Toast：中心信息不能和粒子抢主视觉" class="headerlink" title="Toast：中心信息不能和粒子抢主视觉"></a>Toast：中心信息不能和粒子抢主视觉</h2><p>这个特效里还有一个中心段位 toast。</p><p>Toast 的任务是告诉用户：你升到了哪个段位。粒子的任务是表达情绪。二者不能互相抢。</p><p>我中间也犯过一个错误：段位 badge 设计太突兀，颜色和粒子不搭，又不够明显。后来调整为更克制的青山湖风格，用深绿&#x2F;湖水青承载，标题变大，减少花哨颜色。</p><p>这里的原则是：</p><ol><li>Toast 负责信息。</li><li>粒子负责情绪。</li><li>Toast 颜色要稳定，粒子颜色可以跳。</li><li>Toast 不要跟粒子一样花。</li></ol><p>如果中心信息也花，背景粒子也花，用户就不知道该看哪里。</p><hr><h2 id="Canvas-实现：为什么不用一堆-SwiftUI-View"><a href="#Canvas-实现：为什么不用一堆-SwiftUI-View" class="headerlink" title="Canvas 实现：为什么不用一堆 SwiftUI View"></a>Canvas 实现：为什么不用一堆 SwiftUI View</h2><p>这次最终用 SwiftUI <code>Canvas</code> 画粒子，而不是 <code>ForEach</code> 渲染一堆 View。</p><p>原因很直接：</p><table><thead><tr><th>方案</th><th>优点</th><th>问题</th></tr></thead><tbody><tr><td>多个 SwiftUI View</td><td>写起来直观</td><td>大量 View diff，动画状态复杂</td></tr><tr><td>Canvas</td><td>单层绘制，适合粒子</td><td>要自己管理坐标、时间、路径</td></tr></tbody></table><p>粒子系统本质上是每帧根据时间计算位置，然后画出来。Canvas 更贴近这个模型。</p><p>大致结构是：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">TimelineView</span>(.animation(minimumInterval: <span class="number">1.0</span> <span class="operator">/</span> <span class="number">30.0</span>)) &#123; timeline <span class="keyword">in</span></span><br><span class="line">    <span class="type">ConfettiCanvas</span>(</span><br><span class="line">        elapsed: timeline.date.timeIntervalSince(startDate),</span><br><span class="line">        frozen: <span class="literal">false</span></span><br><span class="line">    )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>elapsed</code> 是整个系统的时钟。每个粒子根据 <code>elapsed - delay</code> 算自己的局部时间，再算 position、rotation、opacity。</p><p>这个模型比给每个粒子挂一堆 <code>.animation</code> 更可控。</p><hr><h2 id="这次最后留下的系统结构"><a href="#这次最后留下的系统结构" class="headerlink" title="这次最后留下的系统结构"></a>这次最后留下的系统结构</h2><p>最终版本可以理解成 3 层：</p><table><thead><tr><th>层级</th><th>元素</th><th>作用</th></tr></thead><tbody><tr><td>主粒子层</td><td>Confetti rectangles</td><td>庆祝语义</td></tr><tr><td>辅助粒子层</td><td>Circular chips</td><td>增加密度和面积</td></tr><tr><td>高光层</td><td>Diamond glints + cross sparkles</td><td>精致感、瞬间感</td></tr></tbody></table><p>被删掉的是：</p><table><thead><tr><th>元素</th><th>删除原因</th></tr></thead><tbody><tr><td>Streamers</td><td>曲线难自然，抢画面</td></tr><tr><td>Burst</td><td>太像烟花，不符合产品气质</td></tr><tr><td>Heavy bloom</td><td>污染背景，可读性差</td></tr></tbody></table><p>这个结构其实很朴素，但稳定。</p><p>很多 UI 特效最后不是赢在复杂，而是赢在“每个元素都知道自己负责什么”。</p><hr><h2 id="我的判断框架"><a href="#我的判断框架" class="headerlink" title="我的判断框架"></a>我的判断框架</h2><p>以后再做类似粒子特效，我会按这个顺序判断：</p><table><thead><tr><th>问题</th><th>优先调什么</th></tr></thead><tbody><tr><td>不像庆祝</td><td>emitter 方向、颜色亮度、confetti 形状</td></tr><tr><td>太乱</td><td>删除连续元素，降低 bloom，减少同步运动</td></tr><tr><td>不够满</td><td>distribution 先于 count</td></tr><tr><td>太快</td><td>lifetime、delay、fade out</td></tr><tr><td>不自然</td><td>motion field 先于 random</td></tr><tr><td>主信息不明显</td><td>toast 层级、粒子避让、颜色对比</td></tr><tr><td>廉价</td><td>少用爆点和大光晕，控制高饱和面积</td></tr></tbody></table><p>一句话：<strong>先定语义，再定动势，最后才调随机参数</strong>。</p><hr><h2 id="要不要用粒子系统"><a href="#要不要用粒子系统" class="headerlink" title="要不要用粒子系统"></a>要不要用粒子系统</h2><p>我的判断是：值得用，但要克制。</p><p>适合用的场景：</p><ol><li>用户完成一个阶段性目标。</li><li>系统需要表达奖励。</li><li>状态变化低频且重要。</li><li>动画不会打断用户工作流。</li></ol><p>不适合用的场景：</p><ol><li>高频反馈。</li><li>错误提示。</li><li>信息密集页面。</li><li>用户正在专注输入或阅读。</li></ol><p>Argos 这次段位晋级适合用粒子系统，因为它是低频、正向、阶段性的反馈。但最终也证明了一点：工具型产品里的庆祝动画，不应该朝游戏化一路狂奔。真正合适的方向是：<strong>有情绪，但不喧宾夺主</strong>。</p><p>这也是我对这次迭代最大的收获。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/e253ed7eb4b2/</id>
    <link href="https://blog.zisheng.pro/posts/e253ed7eb4b2/"/>
    <published>2026-07-08T15:58:00.000Z</published>
    <summary>这篇文章复盘一次 macOS 工具里“段位晋级彩纸特效”的真实迭代：为什么一开始会像从上往下掉、为什么彩带会显得别扭、爆点和光晕为什么不一定高级，以及如何用 emitter、particle、glint、ambient sparkle、bloom、lifetime、distribution、motion field 这些概念，把一个庆祝动画调到可接受。</summary>
    <title>粒子系统实践：从一版难看的彩纸特效说起</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="AI Infra" scheme="https://blog.zisheng.pro/categories/AI-Infra/"/>
    <category term="Node.js" scheme="https://blog.zisheng.pro/tags/Node-js/"/>
    <category term="Next.js" scheme="https://blog.zisheng.pro/tags/Next-js/"/>
    <category term="Fumadocs" scheme="https://blog.zisheng.pro/tags/Fumadocs/"/>
    <category term="Midway.js" scheme="https://blog.zisheng.pro/tags/Midway-js/"/>
    <category term="OpenAPI" scheme="https://blog.zisheng.pro/tags/OpenAPI/"/>
    <category term="文档站" scheme="https://blog.zisheng.pro/tags/%E6%96%87%E6%A1%A3%E7%AB%99/"/>
    <category term="Tailwind CSS" scheme="https://blog.zisheng.pro/tags/Tailwind-CSS/"/>
    <content>
      <![CDATA[<p>后端服务到了一定规模，文档就成了刚需。不是给外部用户的 README，而是给自己人的：新成员怎么跑起来、接口入参是什么、数据库里几张表是怎么关联的。</p><p>我负责的这个 Node.js 后端服务跑着一套自动化平台，随着接口越来越多，”靠脑子记”已经不够了。于是我决定正经做一个内嵌文档站，要求只有一个：<strong>好看，且不要花时间调样式</strong>。</p><h2 id="一句话总结"><a href="#一句话总结" class="headerlink" title="一句话总结"></a>一句话总结</h2><p>Fumadocs 是目前我见过最开箱即用的 Node.js 文档框架——接入 OpenAPI 两行命令就能出接口文档，颜值不需要额外调，跟 Midway.js &#x2F; Egg.js 静态托管的结合也很自然。</p><hr><h2 id="选型过程：为什么不选其他的"><a href="#选型过程：为什么不选其他的" class="headerlink" title="选型过程：为什么不选其他的"></a>选型过程：为什么不选其他的</h2><p>我评估了以下几个方案：</p><table><thead><tr><th>框架</th><th>技术栈</th><th>OpenAPI 支持</th><th>颜值开箱</th><th>备注</th></tr></thead><tbody><tr><td><strong>VitePress</strong></td><td>Vue 3</td><td>无官方插件</td><td>中等</td><td>适合纯文档，定制自由度高但要花时间</td></tr><tr><td><strong>Docusaurus</strong></td><td>React</td><td>docusaurus-plugin-openapi-docs</td><td>一般</td><td>生态成熟，但 OpenAPI 插件渲染效果不够精致</td></tr><tr><td><strong>Nextra</strong></td><td>Next.js</td><td>有，需要配置</td><td>中等</td><td>和 Next.js 绑定深，学习成本适中</td></tr><tr><td><strong>Mintlify</strong></td><td>SaaS</td><td>一流</td><td>最好看</td><td>需要联网托管，私有项目不适合</td></tr><tr><td><strong>Fumadocs</strong></td><td>Next.js</td><td>fumadocs-openapi，官方维护</td><td><strong>一流</strong></td><td>可自部署，颜值对标 Mintlify</td></tr></tbody></table><p>Mintlify 的 OpenAPI 渲染是业内标杆，但它是 SaaS——文档得推到他们的服务器，对内部项目不合适。</p><p>Fumadocs 是我找到的唯一一个<strong>可以自部署、OpenAPI 一流、不需要调样式就好看</strong>的方案。</p><hr><h2 id="Fumadocs-的颜值从何而来"><a href="#Fumadocs-的颜值从何而来" class="headerlink" title="Fumadocs 的颜值从何而来"></a>Fumadocs 的颜值从何而来</h2><h3 id="设计系统内置"><a href="#设计系统内置" class="headerlink" title="设计系统内置"></a>设计系统内置</h3><p>Fumadocs UI 本身是 Tailwind CSS v4 驱动的设计系统，所有颜色、排版、间距都基于 CSS 变量。导入一行 CSS 就能用：</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* app/globals.css */</span></span><br><span class="line"><span class="keyword">@import</span> <span class="string">&#x27;fumadocs-ui/style.css&#x27;</span>;</span><br><span class="line"><span class="keyword">@import</span> <span class="string">&#x27;tailwindcss&#x27;</span>;</span><br></pre></td></tr></table></figure><p>不需要写主题配置，深色模式自动支持，字体和间距比例都对。</p><h3 id="文档布局开箱可用"><a href="#文档布局开箱可用" class="headerlink" title="文档布局开箱可用"></a>文档布局开箱可用</h3><p>侧边栏、TOC（目录）、面包屑、代码高亮——这些在大多数框架里都要自己拼，Fumadocs 全部内置：</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// app/docs/layout.tsx</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">DocsLayout</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;fumadocs-ui/layouts/docs&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; source &#125; <span class="keyword">from</span> <span class="string">&#x27;@/lib/source&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Layout</span>(<span class="params">&#123; children &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">DocsLayout</span> <span class="attr">tree</span>=<span class="string">&#123;source.pageTree&#125;</span> <span class="attr">nav</span>=<span class="string">&#123;&#123;</span> <span class="attr">title:</span> &#x27;<span class="attr">我的项目</span>&#x27; &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;children&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">DocsLayout</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这就是文档布局的全部代码。侧边栏自动从 <code>content/docs/</code> 的目录结构生成，<code>meta.json</code> 控制排序和分组。</p><h3 id="MDX-组件"><a href="#MDX-组件" class="headerlink" title="MDX 组件"></a>MDX 组件</h3><p><code>&lt;Cards&gt;</code>、<code>&lt;Card&gt;</code>、<code>&lt;Callout&gt;</code>、<code>&lt;Steps&gt;</code> 这些常见 MDX 组件全部内置，写文档时直接用，不需要自己实现。</p><hr><h2 id="fumadocs-openapi：两行命令出接口文档"><a href="#fumadocs-openapi：两行命令出接口文档" class="headerlink" title="fumadocs-openapi：两行命令出接口文档"></a>fumadocs-openapi：两行命令出接口文档</h2><p>这是我最看重的功能。维护一份 <code>openapi.yaml</code>，然后：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 安装</span></span><br><span class="line">npm install fumadocs-openapi</span><br><span class="line"></span><br><span class="line"><span class="comment"># 从 YAML 生成 MDX 页面</span></span><br><span class="line">npx fumadocs-openapi generate -i openapi/my-api.yaml -o content/docs/api</span><br></pre></td></tr></table></figure><p>生成的每个接口都是一个独立的 MDX 页面，包含：</p><ul><li>请求方法 + 路径（带颜色标签）</li><li>路径参数 &#x2F; Query 参数 &#x2F; 请求体的字段说明和类型</li><li>响应示例，支持多状态码</li><li>交互式 “Try it” 面板（可选）</li></ul><p>效果对标 Mintlify 和 Swagger UI Pro，但完全自部署。</p><p>OpenAPI spec 的骨架是这样的：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># openapi/my-api.yaml</span></span><br><span class="line"><span class="attr">openapi:</span> <span class="number">3.0</span><span class="number">.3</span></span><br><span class="line"><span class="attr">info:</span></span><br><span class="line">  <span class="attr">title:</span> <span class="string">My</span> <span class="string">API</span></span><br><span class="line">  <span class="attr">version:</span> <span class="number">1.0</span><span class="number">.0</span></span><br><span class="line"><span class="attr">paths:</span></span><br><span class="line">  <span class="string">/api/skills:</span></span><br><span class="line">    <span class="attr">get:</span></span><br><span class="line">      <span class="attr">summary:</span> <span class="string">获取技能列表</span></span><br><span class="line">      <span class="attr">tags:</span> [<span class="string">Skills</span>]</span><br><span class="line">      <span class="attr">parameters:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">projectKey</span></span><br><span class="line">          <span class="attr">in:</span> <span class="string">query</span></span><br><span class="line">          <span class="attr">schema:</span></span><br><span class="line">            <span class="attr">type:</span> <span class="string">string</span></span><br><span class="line">      <span class="attr">responses:</span></span><br><span class="line">        <span class="attr">&#x27;200&#x27;:</span></span><br><span class="line">          <span class="attr">description:</span> <span class="string">技能列表</span></span><br><span class="line">          <span class="attr">content:</span></span><br><span class="line">            <span class="attr">application/json:</span></span><br><span class="line">              <span class="attr">schema:</span></span><br><span class="line">                <span class="attr">type:</span> <span class="string">array</span></span><br><span class="line">                <span class="attr">items:</span></span><br><span class="line">                  <span class="string">$ref:</span> <span class="string">&#x27;#/components/schemas/Skill&#x27;</span></span><br></pre></td></tr></table></figure><p>写一次 spec，既能生成文档，将来也能用于接口测试和 Mock Server——比手写 Markdown 维护成本低。</p><hr><h2 id="怎么和-Midway-js-Egg-js-结合"><a href="#怎么和-Midway-js-Egg-js-结合" class="headerlink" title="怎么和 Midway.js &#x2F; Egg.js 结合"></a>怎么和 Midway.js &#x2F; Egg.js 结合</h2><p>这里有个有意思的架构问题：文档站是 Next.js 项目，服务端是 Midway.js（Egg.js 底层）。两个进程，怎么变成”一个项目”？</p><h3 id="静态导出-egg-static-托管"><a href="#静态导出-egg-static-托管" class="headerlink" title="静态导出 + egg-static 托管"></a>静态导出 + egg-static 托管</h3><p>Next.js 支持 <code>output: &#39;export&#39;</code>，构建后输出纯静态文件：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// client/next.config.mjs</span></span><br><span class="line"><span class="keyword">const</span> config = &#123;</span><br><span class="line">  <span class="attr">output</span>: <span class="string">&#x27;export&#x27;</span>,      <span class="comment">// 输出静态 HTML/JS/CSS，不需要 Node.js 运行时</span></span><br><span class="line">  <span class="attr">images</span>: &#123; <span class="attr">unoptimized</span>: <span class="literal">true</span> &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>Egg.js 内置 <code>egg-static</code> 插件，开启后可以托管静态目录：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/config/config.default.ts</span></span><br><span class="line"><span class="keyword">import</span> &#123; join &#125; <span class="keyword">from</span> <span class="string">&#x27;path&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="attr">static</span>: &#123;</span><br><span class="line">  <span class="attr">prefix</span>: <span class="string">&#x27;/&#x27;</span>,</span><br><span class="line">  <span class="attr">dir</span>: [<span class="title function_">join</span>(appInfo.<span class="property">appDir</span>, <span class="string">&#x27;client&#x27;</span>, <span class="string">&#x27;out&#x27;</span>)],  <span class="comment">// Next.js 导出目录</span></span><br><span class="line">  <span class="attr">dynamic</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="attr">preload</span>: <span class="literal">false</span>,</span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/config/plugin.ts</span></span><br><span class="line"><span class="attr">static</span>: &#123;</span><br><span class="line">  <span class="attr">enable</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="attr">package</span>: <span class="string">&#x27;egg-static&#x27;</span>,</span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure><p>请求流程：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">HTTP 请求</span><br><span class="line">  ↓</span><br><span class="line">egg-static（命中 client/out/ 静态文件 → 直接返回 HTML/JS/CSS）</span><br><span class="line">  ↓</span><br><span class="line">Midway 路由（没有匹配的静态文件 → 走 API 控制器）</span><br></pre></td></tr></table></figure><p>也就是说，<code>GET /</code> 返回文档站首页，<code>GET /api/skills</code> 走 Midway 控制器——静态文件和 API 共用同一个端口，完全无感。</p><h3 id="本地开发：一个命令同时跑两个进程"><a href="#本地开发：一个命令同时跑两个进程" class="headerlink" title="本地开发：一个命令同时跑两个进程"></a>本地开发：一个命令同时跑两个进程</h3><p>生产环境用静态导出。本地开发时，Next.js 需要一个 dev server 做热更新，不能用静态文件。用 <code>concurrently</code> 解决：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// package.json（项目根目录）</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;dev&quot;</span><span class="punctuation">:</span> <span class="string">&quot;concurrently --names &#x27;server,docs&#x27; --prefix-colors &#x27;cyan,magenta&#x27; --kill-others-on-fail &#x27;npm run dev:server&#x27; &#x27;npm run dev:docs&#x27;&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;dev:server&quot;</span><span class="punctuation">:</span> <span class="string">&quot;cross-env NODE_ENV=local mwtsc --watch --run @midwayjs/mock/app.js&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;dev:docs&quot;</span><span class="punctuation">:</span> <span class="string">&quot;npm --prefix client run dev&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><code>npm run dev</code> 之后，终端里：</p><ul><li><code>cyan</code> 前缀的输出是 Midway 后端（<code>:7001</code>）</li><li><code>magenta</code> 前缀的输出是 Fumadocs（<code>:3000</code>）</li></ul><p>Home controller 做一个简单的判断：本地环境没有 <code>client/out/</code>，就重定向到 <code>:3000</code>；其他环境如果 build 缺失就显示一个保底状态页。</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/controller/home.ts</span></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">CLIENT_INDEX</span> = <span class="title function_">join</span>(__dirname, <span class="string">&#x27;../../client/out/index.html&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="meta">@Get</span>(<span class="string">&#x27;/&#x27;</span>)</span><br><span class="line"><span class="keyword">async</span> <span class="title function_">home</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="title function_">existsSync</span>(<span class="variable constant_">CLIENT_INDEX</span>)) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">ctx</span>.<span class="property">type</span> = <span class="string">&#x27;html&#x27;</span>;</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">readFileSync</span>(<span class="variable constant_">CLIENT_INDEX</span>, <span class="string">&#x27;utf-8&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">ctx</span>.<span class="property">app</span>.<span class="property">config</span>.<span class="property">env</span> === <span class="string">&#x27;local&#x27;</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">ctx</span>.<span class="title function_">redirect</span>(<span class="string">&#x27;http://localhost:3000&#x27;</span>);</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 返回保底状态页</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="目录结构"><a href="#目录结构" class="headerlink" title="目录结构"></a>目录结构</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">my-server/</span><br><span class="line">├── src/              # Midway.js 后端</span><br><span class="line">│   ├── controller/</span><br><span class="line">│   ├── service/</span><br><span class="line">│   └── model/</span><br><span class="line">├── client/           # Fumadocs 文档站（Next.js）</span><br><span class="line">│   ├── app/</span><br><span class="line">│   ├── content/docs/ # MDX 文档内容</span><br><span class="line">│   ├── openapi/      # OpenAPI spec</span><br><span class="line">│   └── out/          # next build 产物（被 egg-static 服务）</span><br><span class="line">└── package.json      # dev/build 统一入口</span><br></pre></td></tr></table></figure><hr><h2 id="版本对齐的坑"><a href="#版本对齐的坑" class="headerlink" title="版本对齐的坑"></a>版本对齐的坑</h2><p>Fumadocs 各包的 peer dependency 比较严格，截至写作时：</p><table><thead><tr><th>包</th><th>版本</th><th>备注</th></tr></thead><tbody><tr><td><code>fumadocs-core</code></td><td><code>^16.9.0</code></td><td></td></tr><tr><td><code>fumadocs-mdx</code></td><td><code>^15.0.0</code></td><td>注意不是 ^12，v15 才支持 core v16</td></tr><tr><td><code>fumadocs-ui</code></td><td><code>^16.0.0</code></td><td></td></tr><tr><td><code>fumadocs-openapi</code></td><td><code>^10.0.0</code></td><td>要求 core ^16.9、ui ^16.9</td></tr><tr><td><code>tailwindcss</code></td><td><code>^4.0.0</code></td><td>v16 ui 内置 Tailwind v4，不再用 v3 preset</td></tr></tbody></table><p><code>fumadocs-mdx</code> 对 Next.js 16 有 optional peer dep，安装时加 <code>--legacy-peer-deps</code> 即可，不影响 Next.js 15 运行。</p><p>CSS 配置从 Tailwind v3 的 <code>tailwind.config.ts</code> 改成 v4 的 <code>@import</code> 方式：</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">@import</span> <span class="string">&#x27;fumadocs-ui/style.css&#x27;</span>;  <span class="comment">/* Fumadocs 设计系统 + Tailwind v4 */</span></span><br><span class="line"><span class="keyword">@import</span> <span class="string">&#x27;tailwindcss&#x27;</span>;             <span class="comment">/* 补充 utility classes */</span></span><br><span class="line"><span class="keyword">@source</span> <span class="string">&#x27;../app/**/*.&#123;ts,tsx&#125;&#x27;</span>;</span><br><span class="line"><span class="keyword">@source</span> <span class="string">&#x27;../content/**/*.&#123;md,mdx&#125;&#x27;</span>;</span><br></pre></td></tr></table></figure><hr><h2 id="要不要用-我的判断框架"><a href="#要不要用-我的判断框架" class="headerlink" title="要不要用 &#x2F; 我的判断框架"></a>要不要用 &#x2F; 我的判断框架</h2><p><strong>适合用 Fumadocs 的场景：</strong></p><ul><li>需要 OpenAPI 接口文档，且对渲染效果有要求</li><li>项目本身是 Node.js，可以把文档站代码一起放在 repo 里</li><li>团队熟悉 React &#x2F; Next.js（文档站本身就是 Next.js 项目）</li><li>不想花时间在文档样式上，要开箱即用</li></ul><p><strong>不适合的场景：</strong></p><ul><li>项目是纯静态网站 &#x2F; 博客，VitePress 更轻量</li><li>团队技术栈是 Vue，Vitepress 更顺手</li><li>需要公开托管 + 自定义域名，Mintlify 的 SaaS 体验更好</li><li>对 Next.js 包体积有严格限制（文档站打出来还是有一定体积的）</li></ul><p><strong>我的判断</strong>：值得上。特别是如果你的项目已经在写 OpenAPI spec，fumadocs-openapi 自动生成这一步能省很多时间，比手写 Markdown 维护成本低，比 Swagger UI 好看一个量级。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/99abd9f09884/</id>
    <link href="https://blog.zisheng.pro/posts/99abd9f09884/"/>
    <published>2026-07-08T15:30:00.000Z</published>
    <summary>给 Midway.js 后端服务搭文档站的选型过程：为什么在 Vitepress / Docusaurus 里最终选了 Fumadocs，它开箱即用的颜值从何而来，fumadocs-openapi 如何自动从 OpenAPI spec 生成接口文档，以及怎么用 Next.js 静态导出 + egg-static 把它嵌进 Midway 项目、一个命令同时跑服务端和文档站。</summary>
    <title>为什么选 Fumadocs：Node.js 项目接入开箱即用文档站</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="AI" scheme="https://blog.zisheng.pro/categories/AI/"/>
    <category term="AI Infra" scheme="https://blog.zisheng.pro/tags/AI-Infra/"/>
    <category term="Facebook Ads" scheme="https://blog.zisheng.pro/tags/Facebook-Ads/"/>
    <category term="投流" scheme="https://blog.zisheng.pro/tags/%E6%8A%95%E6%B5%81/"/>
    <category term="小说站" scheme="https://blog.zisheng.pro/tags/%E5%B0%8F%E8%AF%B4%E7%AB%99/"/>
    <category term="广告预算" scheme="https://blog.zisheng.pro/tags/%E5%B9%BF%E5%91%8A%E9%A2%84%E7%AE%97/"/>
    <category term="增长" scheme="https://blog.zisheng.pro/tags/%E5%A2%9E%E9%95%BF/"/>
    <content>
      <![CDATA[<p>这两天我在看小说站的 Facebook 投流数据时，遇到一个很典型的问题：<strong>一天预算 100 元，想拿到 2000 个成效，怎么做到？</strong></p><p>如果只看投放后台，很容易把它理解成「是不是受众太窄」「是不是预算没有花满」「是不是素材还可以优化」。这些都可能对，但第一步不是调后台，而是先做一遍反推。增长问题最后都要落到单位经济模型上，尤其是广告套利这种业务，数学先于手感。</p><h2 id="一句话总结"><a href="#一句话总结" class="headerlink" title="一句话总结"></a>一句话总结</h2><p><strong>100 元预算拿 2000 个成效，要求单次成效成本降到 0.05 元。</strong> 如果当前单次成本是 0.33 元，那就意味着还要再降 85%。这不是常规优化能稳定做到的目标。真正可执行的路径是：先扩受众让 100 元预算稳定花满，再在 0.33 元成本附近逐步放量到约 660 元&#x2F;天。</p><h2 id="先算目标单次成本"><a href="#先算目标单次成本" class="headerlink" title="先算目标单次成本"></a>先算目标单次成本</h2><p>这个问题的第一行公式非常简单：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">目标单次成效成本 = 总预算 / 目标成效数</span><br><span class="line">100 / 2000 = 0.05 元</span><br></pre></td></tr></table></figure><p>也就是说，如果一天只花 100 元，还要拿 2000 个落地页浏览或类似成效，每一次成效最多只能花 5 分钱。</p><p>再对比当前数据：</p><table><thead><tr><th>指标</th><th align="right">数值</th><th>含义</th></tr></thead><tbody><tr><td>日预算目标</td><td align="right">100 元</td><td>希望每天最多花这么多</td></tr><tr><td>目标成效</td><td align="right">2000 次</td><td>希望每天拿到的结果数</td></tr><tr><td>目标单次成本</td><td align="right">0.05 元</td><td>100 &#x2F; 2000</td></tr><tr><td>当前单次成本</td><td align="right">0.33 元</td><td>已经跑出来的实际成本</td></tr><tr><td>需要降幅</td><td align="right">约 85%</td><td>从 0.33 降到 0.05</td></tr></tbody></table><p>这里的关键判断是：<strong>0.33 元并不是一个明显偏高的落地页浏览成本。</strong> 在 Facebook 上跑冷流量，尤其是内容站、小说站这种非强交易场景，0.33 元已经属于相当低的区间。素材、受众、地域、设备、落地页速度都继续优化，当然可能还能降，但把长期稳定成本压到 0.05 元，不应该作为计划基线。</p><h2 id="这不是「单次成本太高」的问题"><a href="#这不是「单次成本太高」的问题" class="headerlink" title="这不是「单次成本太高」的问题"></a>这不是「单次成本太高」的问题</h2><p>很多人看到「2000 成效没达成」，第一反应是单次成效费用太高。但这句话要拆成两种情况。</p><p>第一种是真的高。比如目标是 0.3 元，当前跑到 2 元，那就是素材、受众、出价或落地页承诺不匹配，要从投放质量上修。</p><p>第二种是目标本身不现实。比如目标是 0.05 元，当前是 0.33 元，而且 0.33 元已经在低成本区间，那就不是「优化一下」能解决的问题。继续追 0.05 元，只会把团队带进错误动作：频繁换受众、频繁改预算、过早切换转化目标，最后学习期不断重置，成本反而上升。</p><p>我更愿意把它叫做 <strong>目标成本错配</strong>：预算目标、成效目标、市场真实成本不在同一个平面上。</p><h2 id="为什么前面还会说「先扩受众」"><a href="#为什么前面还会说「先扩受众」" class="headerlink" title="为什么前面还会说「先扩受众」"></a>为什么前面还会说「先扩受众」</h2><p>在原始数据里还有一个信号：覆盖人数只有 939，展示 1019。这个量级太小了。</p><p>如果一个广告组一天只覆盖不到 1000 人，就算单次成本看起来不差，也不能直接判断它有放量能力。原因有三个：</p><ol><li>样本太小，CPC&#x2F;LPV 容易被少量点击扰动。</li><li>受众太窄，预算提高后频次会快速上升，CTR 下滑。</li><li>算法探索空间不足，Facebook 没法找到更多相似人群。</li></ol><p>所以「先扩受众」不是为了让 100 元直接变成 2000 成效，而是为了验证两件事：</p><table><thead><tr><th>要验证什么</th><th>为什么重要</th></tr></thead><tbody><tr><td>100 元能不能花满</td><td>花不满说明受众池或出价&#x2F;审核存在约束</td></tr><tr><td>成本能不能维持在 0.33 元附近</td><td>只有成本稳定，后续预算放量才有意义</td></tr></tbody></table><p>扩受众的动作可以很直接：</p><table><thead><tr><th>动作</th><th>目的</th></tr></thead><tbody><tr><td>用 Advantage+ Audience 或放宽兴趣词</td><td>给算法更大的探索空间</td></tr><tr><td>地域从单城市扩到省&#x2F;全国</td><td>避免局部受众耗尽</td></tr><tr><td>去掉非必要年龄&#x2F;性别限制</td><td>降低系统性 CPM 压力</td></tr><tr><td>有数据后建 1%-5% Lookalike</td><td>用真实访问者替代人工猜兴趣</td></tr></tbody></table><p>这里的阶段目标不是「立刻 2000 成效」，而是「100 元预算能稳定花完，同时成本没有明显劣化」。</p><h2 id="真正的预算应该是多少"><a href="#真正的预算应该是多少" class="headerlink" title="真正的预算应该是多少"></a>真正的预算应该是多少</h2><p>如果当前单次成本是 0.33 元，目标是每天 2000 次成效，那预算反推是：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">所需预算 = 目标成效数 * 当前单次成本</span><br><span class="line">2000 * 0.33 = 660 元/天</span><br></pre></td></tr></table></figure><p>所以答案很直接：<strong>要稳定拿 2000 成效&#x2F;天，预算基线不是 100 元，而是约 660 元&#x2F;天。</strong></p><p>这不是说马上把预算从 100 拉到 660。Facebook 的学习期对预算变化很敏感，突然大幅加预算会让系统重新探索，成本可能被放大。更稳的方式是按 20%-30% 的步长放量：</p><table><thead><tr><th>阶段</th><th align="right">日预算</th><th>观察周期</th></tr></thead><tbody><tr><td>1</td><td align="right">100 元</td><td>2-3 天</td></tr><tr><td>2</td><td align="right">130 元</td><td>2-3 天</td></tr><tr><td>3</td><td align="right">170 元</td><td>2-3 天</td></tr><tr><td>4</td><td align="right">220 元</td><td>2-3 天</td></tr><tr><td>5</td><td align="right">290 元</td><td>2-3 天</td></tr><tr><td>6</td><td align="right">380 元</td><td>2-3 天</td></tr><tr><td>7</td><td align="right">500 元</td><td>2-3 天</td></tr><tr><td>8</td><td align="right">660 元</td><td>稳定观察</td></tr></tbody></table><p>每一步只看一个问题：<strong>成本有没有保持在可接受范围内。</strong> 如果从 0.33 元涨到 0.6 元甚至更高，就说明当前素材或受众承载不了这个预算，需要回头补素材、扩受众或拆广告组，而不是继续硬加。</p><h2 id="对小说站投流尤其要克制"><a href="#对小说站投流尤其要克制" class="headerlink" title="对小说站投流尤其要克制"></a>对小说站投流尤其要克制</h2><p>小说站这种 AdSense &#x2F; AdX 变现场景，收入不是一次点击立刻发生，而是由后续阅读页数和广告 RPM 慢慢摊出来。简单写就是：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">单次访问价值 = 平均阅读页数 * 页面 RPM / 1000</span><br><span class="line">利润 = 单次访问价值 - CPC</span><br></pre></td></tr></table></figure><p>这意味着 Facebook 端的 CPC 只是上半场。真正决定能不能套利的，还有两个站内指标：</p><table><thead><tr><th>杠杆</th><th>解释</th></tr></thead><tbody><tr><td>平均阅读页数</td><td>用户从第 1 章继续读到第几章</td></tr><tr><td>页面 RPM</td><td>每 1000 次页面浏览能产生多少广告收入</td></tr></tbody></table><p>如果站内阅读深度和 RPM 还没稳定，盲目追求低到不现实的 CPC，会让投放策略变形。比如为了便宜点击去投低质量受众，短期 CPC 降了，但用户进站 3 秒就走，最后广告收入更差。</p><p>所以我现在更关心的是三层漏斗：</p><table><thead><tr><th>层级</th><th>核心指标</th><th>判断</th></tr></thead><tbody><tr><td>广告层</td><td>CTR &#x2F; CPC &#x2F; CPM</td><td>素材能不能把人带进来</td></tr><tr><td>落地层</td><td>LPV &#x2F; ViewContent</td><td>页面有没有真正加载并开始阅读</td></tr><tr><td>阅读层</td><td>ChapterRead50 &#x2F; NextChapterClick &#x2F; ChapterCompleted</td><td>用户是不是继续读</td></tr></tbody></table><p>便宜点击不是目标，<strong>便宜且会读的点击</strong>才是目标。</p><h2 id="我的判断框架"><a href="#我的判断框架" class="headerlink" title="我的判断框架"></a>我的判断框架</h2><p>以后遇到类似问题，我会按这个顺序判断。</p><p>第一步，先算目标成本。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">目标单次成本 = 预算 / 目标成效</span><br></pre></td></tr></table></figure><p>如果目标成本低到明显脱离市场区间，先改目标，不要急着改广告。</p><p>第二步，看当前成本是不是异常。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">当前成本 / 目标成本 = 需要优化的倍数</span><br></pre></td></tr></table></figure><p>如果只差 20%-30%，素材和受众优化可能有效。如果差 80% 以上，就要高度怀疑目标不现实。</p><p>第三步，看预算有没有花满。</p><p>预算花不满，优先扩受众、放宽地域、检查审核和出价。预算能花满但成本上升，说明放量承载能力不足。</p><p>第四步，再决定是降本还是加预算。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">目标预算 = 目标成效 * 可稳定维持的单次成本</span><br></pre></td></tr></table></figure><p>用稳定成本反推预算，而不是用愿望预算反推不现实的成本。</p><h2 id="要不要用-我的判断框架"><a href="#要不要用-我的判断框架" class="headerlink" title="要不要用 &#x2F; 我的判断框架"></a>要不要用 &#x2F; 我的判断框架</h2><p>这次结论很明确：</p><table><thead><tr><th>判断</th><th>结论</th></tr></thead><tbody><tr><td>100 元拿 2000 成效</td><td>不作为现实目标</td></tr><tr><td>当前 0.33 元单次成本</td><td>不是明显偏高，先视为可接受基线</td></tr><tr><td>下一步重点</td><td>扩受众，让 100 元稳定花满</td></tr><tr><td>放量路径</td><td>成本稳定后逐步加到约 660 元&#x2F;天</td></tr><tr><td>风险点</td><td>过快加预算、频繁改广告组、追求低质便宜点击</td></tr></tbody></table><p>我的最终判断是：<strong>当前任务不是把 0.33 元硬压到 0.05 元，而是验证 0.33 元能否在更大受众池里稳定复现。</strong> 复现成功，再用预算换量；复现失败，再回头修素材、落地页和读者质量。增长不是许愿，先把单位经济模型算清楚，后面的动作才不会跑偏。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/7926e49e1567/</id>
    <link href="https://blog.zisheng.pro/posts/7926e49e1567/"/>
    <published>2026-07-07T02:45:00.000Z</published>
    <summary>很多投流问题看起来像优化问题，本质却是数学约束。本文用「一天 100 元预算、目标 2000 个成效」这个例子，拆解目标单次成本、当前成本、受众规模和放量节奏之间的关系，给出一个判断框架：什么时候该优化素材，什么时候该扩受众，什么时候必须承认预算目标不现实。</summary>
    <title>Facebook广告预算反推：一天100元想拿2000成效现实吗</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="效率工具" scheme="https://blog.zisheng.pro/categories/%E6%95%88%E7%8E%87%E5%B7%A5%E5%85%B7/"/>
    <category term="macOS" scheme="https://blog.zisheng.pro/tags/macOS/"/>
    <category term="键盘" scheme="https://blog.zisheng.pro/tags/%E9%94%AE%E7%9B%98/"/>
    <category term="效率" scheme="https://blog.zisheng.pro/tags/%E6%95%88%E7%8E%87/"/>
    <content>
      <![CDATA[<p>用 macOS 写代码或者写文章，有个小事经常让我抓狂：长按 <code>Backspace</code> 删内容，前半秒没动静，开始动了又慢得像在爬。这个体感问题不大，但它会在你每天反复删除、修改、回退的时候持续消耗效率。</p><h2 id="一句话总结"><a href="#一句话总结" class="headerlink" title="一句话总结"></a>一句话总结</h2><p>系统偏好设置 → 键盘，把「键重复速率」和「重复前延迟」两个滑块都拉到最右边，如果还嫌不够快，用两条终端命令突破 GUI 上限。</p><h2 id="在系统设置里调"><a href="#在系统设置里调" class="headerlink" title="在系统设置里调"></a>在系统设置里调</h2><p>打开<strong>系统设置 → 键盘</strong>（直接 Spotlight 搜「键盘」），你会看到类似下面这个界面：</p><p><img src="/images/macos-keyboard-repeat-settings.webp" alt="macOS 键盘设置面板"></p><p>两个关键滑块：</p><table><thead><tr><th>设置项</th><th>作用</th><th>推荐</th></tr></thead><tbody><tr><td><strong>键重复速率</strong></td><td>长按后每秒触发多少次按键</td><td>拖到最右「快」</td></tr><tr><td><strong>重复前延迟</strong></td><td>按住多久后开始重复</td><td>拖到最右「短」</td></tr></tbody></table><p>改完立刻生效，不需要重启。</p><h2 id="系统设置不够用？用终端突破上限"><a href="#系统设置不够用？用终端突破上限" class="headerlink" title="系统设置不够用？用终端突破上限"></a>系统设置不够用？用终端突破上限</h2><p>GUI 滑块有最小值限制，终端可以设置更激进的数字：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 重复速度（数字越小越快，GUI 最小值是 2，这里设 1）</span></span><br><span class="line">defaults write NSGlobalDomain KeyRepeat -int 1</span><br><span class="line"></span><br><span class="line"><span class="comment"># 开始重复前的延迟（数字越小延迟越短，GUI 最小值约 15，这里设 10）</span></span><br><span class="line">defaults write NSGlobalDomain InitialKeyRepeat -int 10</span><br></pre></td></tr></table></figure><p><strong>改完需要注销重新登录</strong>才能生效。</p><p>单位说明：<code>KeyRepeat</code> 的单位是 15ms&#x2F;tick，设 1 &#x3D; 15ms 重复一次（约 66次&#x2F;秒）；<code>InitialKeyRepeat</code> 单位也是 15ms，设 10 &#x3D; 150ms 后开始重复。</p><h2 id="我的判断框架"><a href="#我的判断框架" class="headerlink" title="我的判断框架"></a>我的判断框架</h2><p><strong>值得上</strong>：任何重度键盘用户，尤其是写代码、写长文的，改完体感提升很明显，Backspace 终于跟上思维速度了。</p><p><strong>可以再等</strong>：偶尔写写邮件、主要用鼠标操作的用户，默认值就够了，改太激进反而容易误触删多。</p><p><strong>注意</strong>：如果用的是外接键盘，部分键盘固件有自己的重复速度设置，系统设置可能对其无效，需要去键盘配套软件里改（比如 VIA、QMK）。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/f1449d85feaf/</id>
    <link href="https://blog.zisheng.pro/posts/f1449d85feaf/"/>
    <published>2026-07-06T12:00:00.000Z</published>
    <summary>macOS 默认的键盘重复速度对重度键盘用户来说偏慢，长按 Backspace 删文字像在等动画播完。本文结合系统设置截图，说明键重复速率和重复前延迟怎么调，以及什么时候需要用终端突破 GUI 限制，适合程序员和写作重度用户。</summary>
    <title>macOS 长按 Backspace 删除太慢？改这两个设置</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="运营" scheme="https://blog.zisheng.pro/categories/%E8%BF%90%E8%90%A5/"/>
    <category term="Facebook Ads" scheme="https://blog.zisheng.pro/tags/Facebook-Ads/"/>
    <category term="流量变现" scheme="https://blog.zisheng.pro/tags/%E6%B5%81%E9%87%8F%E5%8F%98%E7%8E%B0/"/>
    <category term="版位选择" scheme="https://blog.zisheng.pro/tags/%E7%89%88%E4%BD%8D%E9%80%89%E6%8B%A9/"/>
    <category term="内容站" scheme="https://blog.zisheng.pro/tags/%E5%86%85%E5%AE%B9%E7%AB%99/"/>
    <category term="AdX" scheme="https://blog.zisheng.pro/tags/AdX/"/>
    <content>
      <![CDATA[<p>我在跑几个英语小说站的流量变现（romance、paranormal、fantasy 题材，AdX 广告收益模式）。Facebook 广告是主要拉新渠道，目标受众是 25-45 岁英语女性读者。</p><p>跑了几个月下来，版位选择这件事看似简单，实则有不少坑。记录一下我的判断框架。</p><h2 id="一句话总结"><a href="#一句话总结" class="headerlink" title="一句话总结"></a>一句话总结</h2><p><strong>动态信息流选 Facebook + Instagram + Instagram 探索，快拍&#x2F;Reels 看素材，Marketplace 和 Messenger 全跳过。</strong></p><hr><h2 id="动态信息流版位"><a href="#动态信息流版位" class="headerlink" title="动态信息流版位"></a>动态信息流版位</h2><p>这是流量最大、质量最稳的版位类型，优先配置。</p><p><strong>必选：</strong></p><ul><li><strong>Facebook 动态</strong> — 基本盘，CPM 稳定，受众覆盖广</li><li><strong>Instagram 动态</strong> — 小说受众在 Instagram 的核心阵地，点击质量高</li><li><strong>Instagram 探索首页</strong> — 用户在主动发现新内容，意图强，适合拉新</li></ul><p><strong>可跳过：</strong></p><ul><li><strong>Facebook 主页动态 &#x2F; Instagram 主页动态</strong> — 量极小，意义不大</li><li><strong>Facebook 右边栏</strong> — 纯桌面端，CTR 低得离谱</li><li><strong>Threads 动态</strong> — 受众偏科技&#x2F;新闻，不匹配 romance fiction 读者画像</li><li><strong>Facebook 通知</strong> — 广告出现在通知栏，用户体验极差，反感率高</li></ul><p><strong>坑：Facebook Marketplace</strong></p><p>Facebook 经常在「复制广告组」时默认勾选 Marketplace。<strong>去掉它。</strong> Marketplace 的用户在找二手商品，购物心态和阅读心态完全不同，点进小说站后跳出率极高，白烧预算。</p><hr><h2 id="快拍-Reels-版位"><a href="#快拍-Reels-版位" class="headerlink" title="快拍 &amp; Reels 版位"></a>快拍 &amp; Reels 版位</h2><p>这类版位<strong>依赖素材格式</strong>，不能无脑勾。</p><p><strong>前提：素材是竖版视频（9:16）才开，图片素材直接跳过整组。</strong></p><p>在有竖版视频素材的情况下：</p><ul><li><strong>Instagram 快拍</strong> — 优先，受众活跃</li><li><strong>Instagram Reels</strong> — 优先，算法推流强，新受众覆盖好</li><li><strong>Facebook 快拍</strong> — 次选，补量用</li><li><strong>Facebook Reels</strong> — 可选，跑量后看 CPM 决定保不保留</li></ul><p><strong>跳过：</strong></p><ul><li><strong>Messenger 快拍</strong> — 场景割裂，用户不在内容消费状态，CTR 极低</li><li><strong>Reels 插播广告 &#x2F; Reels 内嵌广告</strong> — 素材被强制裁剪，图片广告在这里投放效果极差</li></ul><hr><h2 id="搜索结果版位"><a href="#搜索结果版位" class="headerlink" title="搜索结果版位"></a>搜索结果版位</h2><ul><li><strong>Instagram 搜索结果</strong> — 值得开。用户主动搜索 romance、dark fiction 等词时出现，意图明确</li><li><strong>Facebook 搜索结果</strong> — 跳过。Facebook 搜索使用率极低，量小到忽略不计</li></ul><hr><h2 id="别被系统建议带跑"><a href="#别被系统建议带跑" class="headerlink" title="别被系统建议带跑"></a>别被系统建议带跑</h2><p>Facebook「复制广告组」时会推荐几条建议，值得单独说一下：</p><table><thead><tr><th>建议</th><th>操作</th></tr></thead><tbody><tr><td>添加 Instagram 版位（降低 23% CPA）</td><td>可接受，方向对</td></tr><tr><td>将预算增加至与同类广告组一致</td><td>跳过，这是 Meta 的增收话术</td></tr><tr><td>发送 Messenger 消息</td><td>坚决拒绝，会把广告目标从「流量」切换成「对话」，Pixel 事件全断</td></tr></tbody></table><hr><h2 id="我的判断框架"><a href="#我的判断框架" class="headerlink" title="我的判断框架"></a>我的判断框架</h2><p><strong>版位能不能开，核心看两件事：</strong></p><ol><li><strong>受众在不在那里</strong> — romance 读者的主力阵地是 Instagram 和 Facebook 动态，不是 Marketplace、不是 Messenger</li><li><strong>素材格式匹不匹配</strong> — 图片素材不要碰竖版视频版位，被系统裁剪后效果等于废</li></ol><p>按这两个维度过滤一遍，版位选择基本不会踩坑。</p><p>WhatsApp 动态不用考虑，受众集中在印度&#x2F;巴西&#x2F;东南亚，和英语小说站的目标市场完全不重叠。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/6797aa7ce19a/</id>
    <link href="https://blog.zisheng.pro/posts/6797aa7ce19a/"/>
    <published>2026-07-06T12:00:00.000Z</published>
    <summary>运营英语小说站近半年，Facebook 广告版位踩了不少坑。这篇文章整理我实际跑量的版位选择逻辑——哪些是核心版位、哪些是陷阱、哪些要看素材类型才能开——给同类内容站（小说、短文、UGC）的运营参考。</summary>
    <title>小说站 Facebook 广告版位选择指南</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="AI" scheme="https://blog.zisheng.pro/categories/AI/"/>
    <category term="流量变现" scheme="https://blog.zisheng.pro/tags/%E6%B5%81%E9%87%8F%E5%8F%98%E7%8E%B0/"/>
    <category term="Meta Pixel" scheme="https://blog.zisheng.pro/tags/Meta-Pixel/"/>
    <category term="小说站" scheme="https://blog.zisheng.pro/tags/%E5%B0%8F%E8%AF%B4%E7%AB%99/"/>
    <category term="Facebook广告" scheme="https://blog.zisheng.pro/tags/Facebook%E5%B9%BF%E5%91%8A/"/>
    <category term="Android" scheme="https://blog.zisheng.pro/tags/Android/"/>
    <category term="ATT" scheme="https://blog.zisheng.pro/tags/ATT/"/>
    <content>
      <![CDATA[<p>最近在给自己的小说站跑 Facebook 广告，一个不起眼的配置让我琢磨了很久：设备定向要不要排除 iOS？</p><p>直觉上觉得应该不排，覆盖更多用户嘛。但实际想清楚之后，我做了一个反直觉的决定：<strong>只投 Android，最低 Android 9，iOS 暂时全排除</strong>。</p><p>这个决策背后有技术原因，不是玄学。</p><h2 id="一句话总结"><a href="#一句话总结" class="headerlink" title="一句话总结"></a>一句话总结</h2><p>iOS 14.5+ 的 ATT 政策让 Meta Pixel 无法回传事件，广告系列的优化方向会跑偏；在没有 CAPI（Conversions API）的情况下，投 iOS 等于把预算喂给算法噪音。</p><h2 id="iOS-信号丢失：不只是归因问题"><a href="#iOS-信号丢失：不只是归因问题" class="headerlink" title="iOS 信号丢失：不只是归因问题"></a>iOS 信号丢失：不只是归因问题</h2><p>先讲技术机制。</p><p>iOS 14.5 之后 Apple 推出了 ATT（App Tracking Transparency），要求所有应用在追踪用户行为前必须显式获得授权。绝大多数用户选择「不允许追踪」。</p><p>这对 Meta Pixel 的影响是致命的：</p><ol><li>用户在 FB 上看到广告，点击进入小说站</li><li>小说站的 Pixel 代码触发 <code>ViewContent</code> 事件，准备回传给 FB</li><li><strong>iOS 系统层面直接拦截</strong>，事件永远到不了 Meta 服务器</li></ol><p>结果是：FB 的广告系列认为这批 iOS 用户点了广告之后「什么都没发生」。算法会把这批流量标记为低质量，调整投放方向——但这个调整方向是错的，因为信息本身就是残缺的。</p><p>更严重的问题是：<strong>这不只是归因断裂，而是优化方向整体跑偏</strong>。</p><p>你以为广告系列在学习「哪些人喜欢看小说」，实际上它在学习「哪些 Android 用户点击了广告」（因为 Android 的 Pixel 信号是完整的）混合「哪些 iOS 用户不知道为什么点了」（信号丢失的噪音）。两个群体混在一起喂给算法，学习效率会被拉低。</p><h2 id="为什么选-Android-9-作为最低版本"><a href="#为什么选-Android-9-作为最低版本" class="headerlink" title="为什么选 Android 9 作为最低版本"></a>为什么选 Android 9 作为最低版本</h2><p>排除 iOS 之后，还需要设一个 Android 的最低系统版本。我选了 <strong>Android 9（Pie）</strong>，原因很具体：</p><p><strong>技术侧</strong>：我的小说站用 Next.js 静态导出，前端依赖了几个关键的现代 Web API：</p><ul><li><code>IntersectionObserver</code>：AdSense 懒加载和 ChapterPixel 的章节完成检测都依赖这个 API，Android 8 以上才稳定支持</li><li>CSS Grid &#x2F; Gap &#x2F; Aspect-ratio：Android 9 以上渲染稳定，Android 8 的 WebView 有已知 bug</li></ul><p><strong>运营侧</strong>：Android 8 发布于 2017 年，2026 年在用 Android 8 的用户基本是 5 年以上没换机的存量，购买力和活跃度都很低。排除掉这批人不影响核心受众覆盖，还能减少无效曝光。</p><p>实际覆盖：最低 Android 9 能覆盖 <strong>约 98% 的活跃 Android 用户</strong>。</p><h2 id="只投-Android-对费用的影响"><a href="#只投-Android-对费用的影响" class="headerlink" title="只投 Android 对费用的影响"></a>只投 Android 对费用的影响</h2><p>信号质量之外，设备选择还直接影响投放成本。</p><p><strong>CPM（千次曝光成本）</strong>：iOS 广告主竞争密度更高，因为多数广告主默认认为 iOS 用户消费力强。实际测下来，仅投 Android 的 CPM 通常低 15–40%，同预算能买到更多曝光。</p><p><strong>单次成效费用（CPR）</strong>：这个才是核心指标。CPR 低不只因为 CPM 低，更因为归因完整——算法清楚知道哪次点击带来了 ViewContent，出价越来越精准。iOS 混进来后，算法收到的是残缺信号，出价逻辑会变得保守且不稳定。</p><p><strong>学习期</strong>：Facebook 广告系列需要积累 50 次转化事件才能脱离学习期。Android 信号完整，50 次的门槛更快达到；iOS 混进来后，部分转化事件丢失，学习期被拉长，预算在不确定状态下消耗更多。</p><p>什么时候 Android 反而更贵：如果落地页在 Android 低端机上加载慢（碎片化严重），跳出率高会把 CPR 拉上去。这也是为什么要设最低 Android 9——过滤低端慢机，间接保住落地页体验。</p><h2 id="操作路径"><a href="#操作路径" class="headerlink" title="操作路径"></a>操作路径</h2><p>在 FB 广告管理工具里，广告组层级配置：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">广告组 → 版位 → 手动版位</span><br><span class="line"></span><br><span class="line">设备类型：仅限移动设备</span><br><span class="line">操作系统：仅限 Android（选移动设备后出现）</span><br><span class="line">操作系统版本：最低 Android 9 / 最高：无</span><br></pre></td></tr></table></figure><p>注意：这个设置在「手动版位」里，先切换到手动版位才能看到 OS 筛选项。「进阶赋能型版位（推荐）」模式下无法手动指定 OS。</p><h2 id="什么时候回开-iOS"><a href="#什么时候回开-iOS" class="headerlink" title="什么时候回开 iOS"></a>什么时候回开 iOS</h2><p>当前排除 iOS 是因为没有 CAPI，信号丢失没有补救手段。有两个条件满足任意一个，就可以考虑回开：</p><p><strong>条件一：CAPI 上线</strong>。Conversions API 是服务端事件回传，绕过 iOS 的浏览器层拦截，直接从服务器把事件推给 Meta。上了 CAPI 之后，iOS 用户的 ViewContent 等核心事件可以正常回传，算法能重新学习这批用户。</p><p>实现门槛不低——静态站（Next.js output: ‘export’）没有运行中的 Node.js 服务器，需要额外部署一个 API 服务（Vercel Function &#x2F; Cloudflare Worker &#x2F; AWS Lambda 都可以）。当前日耗 &lt;$50，性价比不足，等规模上来再搞。</p><p><strong>条件二：GA4 数据证明 iOS 质量更高</strong>。接入 GA4 后，可以按操作系统分组看读者质量指标：人均阅读章节数、平均停留时长、跳出率。如果 iOS 用户这些指标显著优于 Android，那即使信号丢失，从流量质量角度也值得回开——只是归因会不准，但读者是真实的。</p><h2 id="我的判断框架"><a href="#我的判断框架" class="headerlink" title="我的判断框架"></a>我的判断框架</h2><table><thead><tr><th>场景</th><th>建议</th></tr></thead><tbody><tr><td>日耗 &lt; $50，没有 CAPI，没有 GA4 数据</td><td><strong>只投 Android（当前）</strong>，先把算法喂干净的信号</td></tr><tr><td>GA4 数据显示 iOS 读者质量 ≥ Android</td><td><strong>考虑回开 iOS</strong>，接受归因不准的代价换流量</td></tr><tr><td>CAPI 上线，信号丢失消除</td><td><strong>回开 iOS</strong>，此时投全量设备效益最大</td></tr><tr><td>日耗 &gt; $100，CAPI 还没上</td><td><strong>重点关注</strong>，Signal Loss 警告会出现，CAPI ROI 开始合算</td></tr></tbody></table><p>当前我的判断：<strong>可以再等</strong>。先把 GA4 接进来，有了读者质量数据之后再决定 iOS 是否值得回开。</p><p>这个决策不是永久的，是阶段性的——在信号不完整的情况下，优先喂给算法干净的数据，比追求覆盖面更重要。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/e7f31df10b7c/</id>
    <link href="https://blog.zisheng.pro/posts/e7f31df10b7c/"/>
    <published>2026-07-05T13:00:00.000Z</published>
    <summary>在跑小说站 Facebook 广告的过程中，我选择了只投 Android、排除 iOS。不是因为 iOS 用户少，而是 iOS 14.5+ 之后的 ATT 政策让 Meta Pixel 的信号回传彻底断裂——算法学到的根本不是「爱读小说的人」。本文从技术原理到广告组配置，完整讲清楚这个决策，以及什么时候该回开 iOS。</summary>
    <title>Facebook 广告投小说站：为什么只投 Android，不投 iOS</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="AI" scheme="https://blog.zisheng.pro/categories/AI/"/>
    <category term="Facebook Pixel" scheme="https://blog.zisheng.pro/tags/Facebook-Pixel/"/>
    <category term="Meta 广告" scheme="https://blog.zisheng.pro/tags/Meta-%E5%B9%BF%E5%91%8A/"/>
    <category term="Next.js" scheme="https://blog.zisheng.pro/tags/Next-js/"/>
    <category term="小说站" scheme="https://blog.zisheng.pro/tags/%E5%B0%8F%E8%AF%B4%E7%AB%99/"/>
    <category term="前端踩坑" scheme="https://blog.zisheng.pro/tags/%E5%89%8D%E7%AB%AF%E8%B8%A9%E5%9D%91/"/>
    <content>
      <![CDATA[<p>最近在给几个小说站点（Next.js 静态导出）接入 Facebook Pixel 做社交流量变现，过程里踩了一堆坑。这些坑不是 API 文档写错了，而是”文档说清楚了但你不知道这是个坑”——前端转 AI&#x2F;增长方向最容易踩的那种。</p><p>整理成 6 个问题，给同样在做内容站 + FB 投流的人参考。</p><h2 id="一句话总结"><a href="#一句话总结" class="headerlink" title="一句话总结"></a>一句话总结</h2><p>Facebook Pixel 接入表面是三行代码的事，实际上涉及浏览器追踪、事件分层、两个完全独立的 Meta 后台工具、广告算法优化方向——每一层都有不明显的陷阱。</p><hr><h2 id="坑-1：AEM-已死，别再用这个词"><a href="#坑-1：AEM-已死，别再用这个词" class="headerlink" title="坑 1：AEM 已死，别再用这个词"></a>坑 1：AEM 已死，别再用这个词</h2><p>接入之前我研究了一堆文章，大量英文资料在讲 AEM（Aggregated Event Measurement）——iOS 14 之后 Meta 引入的事件聚合测量机制，当时限制每域名 8 个优先级事件槽。</p><p>结果打开 Meta 事件管理工具，完全找不到 AEM 的配置入口。</p><p><strong>原因</strong>：Meta 在 2023 年左右悄悄把这套机制改名了，现在叫「广告组转化事件」，UI 路径在广告管理工具 → 广告组 → 广告组转化事件（不在事件管理工具里）。</p><p>原来的 8 槽限制也放开了，现在可以正常选任意自定义转化事件作为优化目标，不再有数量硬上限。</p><p><strong>教训</strong>：看到 AEM 相关资料，先确认发布时间。2023 年之前的文章大概率在讲一个已经不存在的界面。</p><hr><h2 id="坑-2：三层事件，搞清楚再动手"><a href="#坑-2：三层事件，搞清楚再动手" class="headerlink" title="坑 2：三层事件，搞清楚再动手"></a>坑 2：三层事件，搞清楚再动手</h2><p>这是最容易搞混的一块，我在广告管理工具里死活找不到 <code>ChapterRead50</code> 事件，折腾了很久才搞清楚原因。</p><p>FB Pixel 有三种完全不同的东西：</p><h3 id="标准事件（Standard-Events）"><a href="#标准事件（Standard-Events）" class="headerlink" title="标准事件（Standard Events）"></a>标准事件（Standard Events）</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">fbq</span>(<span class="string">&#x27;track&#x27;</span>, <span class="string">&#x27;ViewContent&#x27;</span>, &#123; <span class="attr">content_type</span>: <span class="string">&#x27;chapter&#x27;</span>, <span class="attr">content_name</span>: title &#125;)</span><br></pre></td></tr></table></figure><p>Meta 内置，有固定的事件名列表（ViewContent、Purchase、Lead、AddToCart 等）。<strong>可以直接在广告管理工具里选为优化目标</strong>，不需要任何额外配置。</p><h3 id="自定义事件（Custom-Events）"><a href="#自定义事件（Custom-Events）" class="headerlink" title="自定义事件（Custom Events）"></a>自定义事件（Custom Events）</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">fbq</span>(<span class="string">&#x27;trackCustom&#x27;</span>, <span class="string">&#x27;ChapterRead50&#x27;</span>, &#123; <span class="attr">content_name</span>: title, <span class="attr">chapter_order</span>: <span class="number">1</span> &#125;)</span><br></pre></td></tr></table></figure><p>你自己取名字，Meta 不认识这个名字。<strong>在广告管理工具的转化事件下拉里根本不会出现</strong>——这就是我卡住的地方。</p><h3 id="自定义转化（Custom-Conversions）"><a href="#自定义转化（Custom-Conversions）" class="headerlink" title="自定义转化（Custom Conversions）"></a>自定义转化（Custom Conversions）</h3><p>在<strong>事件管理工具</strong>里创建的规则，把自定义事件包装成 Meta 认识的转化目标：规则 &#x3D; “当收到 <code>ChapterRead50</code> 事件时，记为一次转化”。</p><p>创建完自定义转化之后，才能在广告管理工具的广告组里选它作为优化事件。</p><p><strong>数据流</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">网站代码 fbq(&#x27;trackCustom&#x27;, &#x27;ChapterRead50&#x27;)</span><br><span class="line">  → 事件管理工具接收原始事件</span><br><span class="line">  → 你手动创建自定义转化（规则：事件 = ChapterRead50）</span><br><span class="line">  → 广告管理工具广告组：转化事件选「阅读章节 50%」（这个名字你自己取）</span><br></pre></td></tr></table></figure><p><code>ViewContent</code> 是标准事件，跳过中间步骤直接可选。<code>ChapterRead50</code> &#x2F; <code>ChapterCompleted</code> &#x2F; <code>TimeOnPage30</code> 是自定义事件，必须先包装。</p><hr><h2 id="坑-3：ChapterCompleted-触发时机错了"><a href="#坑-3：ChapterCompleted-触发时机错了" class="headerlink" title="坑 3：ChapterCompleted 触发时机错了"></a>坑 3：ChapterCompleted 触发时机错了</h2><p>我最开始的实现逻辑：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">onScroll</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> ratio = <span class="variable language_">window</span>.<span class="property">scrollY</span> / (<span class="variable language_">document</span>.<span class="property">body</span>.<span class="property">scrollHeight</span> - <span class="variable language_">window</span>.<span class="property">innerHeight</span>)</span><br><span class="line">  <span class="keyword">if</span> (!firedCompleted.<span class="property">current</span> &amp;&amp; ratio &gt;= <span class="number">0.9</span>) &#123;</span><br><span class="line">    firedCompleted.<span class="property">current</span> = <span class="literal">true</span></span><br><span class="line">    <span class="title function_">fbq</span>(<span class="string">&#x27;trackCustom&#x27;</span>, <span class="string">&#x27;ChapterCompleted&#x27;</span>, payload)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>看起来没问题——滚动到 90% 触发章节完成。</p><p><strong>实际问题</strong>：<code>document.body.scrollHeight</code> 包含正文下方的”YOU MIGHT ALSO LIKE”推荐区。这块区域通常占页面高度的 15-25%。</p><p>结果：</p><ul><li>如果 90% 触发，用户需要滚进推荐区才算”读完”，触发时机太晚</li><li>如果改低阈值，推荐区越长的页面误差越大，无法准确对应正文结束位置</li></ul><p><strong>正确做法</strong>：在正文末尾、推荐区之前放一个哨兵 div：</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&#123;<span class="comment">/* 正文内容区 */</span>&#125;</span><br><span class="line">&lt;<span class="title class_">AdsenseSlot</span> slot=<span class="string">&quot;9124751639&quot;</span> /&gt;</span><br><span class="line"></span><br><span class="line">&#123;<span class="comment">/* 哨兵：ChapterCompleted 在此触发 */</span>&#125;</span><br><span class="line">&lt;div id=<span class="string">&quot;chapter-content-end&quot;</span> /&gt;</span><br><span class="line"></span><br><span class="line">&#123;<span class="comment">/* YOU MIGHT ALSO LIKE 推荐区 */</span>&#125;</span><br><span class="line">&lt;div className=<span class="string">&quot;recommendations&quot;</span>&gt;...&lt;/div&gt;</span><br></pre></td></tr></table></figure><p>然后用 IntersectionObserver 监听这个元素进入视口：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> sentinel = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;chapter-content-end&#x27;</span>)</span><br><span class="line"><span class="keyword">if</span> (sentinel) &#123;</span><br><span class="line">  <span class="keyword">const</span> io = <span class="keyword">new</span> <span class="title class_">IntersectionObserver</span>(<span class="function">(<span class="params">entries</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (entries[<span class="number">0</span>].<span class="property">isIntersecting</span> &amp;&amp; !firedCompleted.<span class="property">current</span>) &#123;</span><br><span class="line">      firedCompleted.<span class="property">current</span> = <span class="literal">true</span></span><br><span class="line">      <span class="title function_">fbq</span>(<span class="string">&#x27;trackCustom&#x27;</span>, <span class="string">&#x27;ChapterCompleted&#x27;</span>, payload)</span><br><span class="line">      io.<span class="title function_">disconnect</span>()</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;, &#123; <span class="attr">threshold</span>: <span class="number">0.1</span> &#125;)</span><br><span class="line">  io.<span class="title function_">observe</span>(sentinel)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>好处：不依赖高度计算，哨兵放哪就在哪触发，推荐区多长都不影响。</p><hr><h2 id="坑-4：两个后台工具职责完全不同"><a href="#坑-4：两个后台工具职责完全不同" class="headerlink" title="坑 4：两个后台工具职责完全不同"></a>坑 4：两个后台工具职责完全不同</h2><p>很多人（包括我最开始）把事件管理工具和广告管理工具搞混。</p><table><thead><tr><th>工具</th><th>URL</th><th>干什么</th></tr></thead><tbody><tr><td>事件管理工具</td><td>eventsmanager.facebook.com&#x2F;events_manager2</td><td>数据层：看 Pixel 收到的事件、测试验证、建自定义转化</td></tr><tr><td>广告管理工具</td><td>adsmanager.facebook.com&#x2F;adsmanager&#x2F;manage&#x2F;campaigns</td><td>投放层：建广告系列、选优化目标、设预算、花钱</td></tr></tbody></table><p><strong>关键链路</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">事件管理工具：验证 ChapterRead50 收到了 → 创建自定义转化</span><br><span class="line">           ↓</span><br><span class="line">广告管理工具：广告组 → 转化事件 → 选「阅读章节 50%」</span><br></pre></td></tr></table></figure><p>自定义事件必须先在事件管理工具里”包装”成自定义转化，才能在广告管理工具的下拉列表里出现。两个工具缺一不可，操作有顺序。</p><p>另外，事件管理工具显示的状态：</p><ul><li><strong>使用中</strong>：近期有事件触发</li><li><strong>已暂停</strong>：0 次事件，Meta 自动暂停——<strong>不影响选择，也不需要手动激活</strong>，有真实流量触发后自动切回使用中</li></ul><hr><h2 id="坑-5：Meta-广告系列目标创建后锁死，必须新建"><a href="#坑-5：Meta-广告系列目标创建后锁死，必须新建" class="headerlink" title="坑 5：Meta 广告系列目标创建后锁死，必须新建"></a>坑 5：Meta 广告系列目标创建后锁死，必须新建</h2><p>从冷启动阶段（流量目标 &#x2F; PageView 优化）升级到 ViewContent 优化阶段，需要的不是”修改广告组的优化事件”，而是<strong>新建一条销量目标的广告系列</strong>。</p><p>原因：广告系列目标（流量 &#x2F; 销量 &#x2F; 品牌知名度等）在广告系列层面设置，创建后<strong>不能修改</strong>。优化事件在广告组层面，但广告组的可用优化事件范围受广告系列目标约束——流量目标的广告系列里，无法选「销量」相关的转化事件。</p><p><strong>操作路径</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">新建广告系列</span><br><span class="line">  → 广告目标：销量</span><br><span class="line">  → 转化位置：网站</span><br><span class="line">  → 广告组：转化事件选 ViewContent</span><br><span class="line">  → 成效目标：转化量最大化</span><br></pre></td></tr></table></figure><p>不要试图在已有的流量广告系列里改，改不了。</p><hr><h2 id="坑-6：CAPI-在静态导出站不是零成本"><a href="#坑-6：CAPI-在静态导出站不是零成本" class="headerlink" title="坑 6：CAPI 在静态导出站不是零成本"></a>坑 6：CAPI 在静态导出站不是零成本</h2><p>很多教程推荐同时上 Browser Pixel + Conversions API（CAPI），理由是 iOS 14 之后浏览器 Pixel 会丢 20-40% 信号。</p><p>这个说法本身没错，但有个前提：你得有服务端。</p><p>我的站点用 Next.js <code>output: &#39;export&#39;</code> 静态导出，全站 HTML&#x2F;CSS&#x2F;JS 推 CDN，<strong>没有运行中的 Node.js 服务</strong>。CAPI 需要从服务端调用 Meta Graph API 回传事件，这意味着：</p><ul><li><strong>方案 A</strong>：改用 Vercel 部署（支持 Next.js Route Handler），在 <code>app/api/fb-event/route.ts</code> 里回传</li><li><strong>方案 B</strong>：单独部署 Cloudflare Worker 或 Vercel Function 作为中转</li><li><strong>方案 C</strong>：Meta 官方 CAPI Gateway（零后端成本，但灵活性差）</li></ul><p>每个方案都有额外成本（架构成本或金钱成本）。当前阶段流量不大、日耗不高，浏览器 Pixel 足够，CAPI 等 Events Manager 出现”信号丢失”警告再评估。</p><p>另外 CAPI 和 Browser Pixel 同时上报同一事件时，需要用 <code>event_id</code> 去重——前端生成 UUID 写 Cookie，服务端从 Cookie 读同一个 ID 再回传，这套打通有额外工程量。</p><hr><h2 id="要不要上-我的判断框架"><a href="#要不要上-我的判断框架" class="headerlink" title="要不要上 &#x2F; 我的判断框架"></a>要不要上 &#x2F; 我的判断框架</h2><table><thead><tr><th>条件</th><th>建议</th></tr></thead><tbody><tr><td>刚接 Pixel，流量 &lt; 1000 UV&#x2F;天</td><td>只上 Browser Pixel，先跑通 ViewContent + 自定义转化流程</td></tr><tr><td>ViewContent 稳定 ≥ 50 次&#x2F;周</td><td>新建销量目标广告系列，切 ViewContent 优化</td></tr><tr><td>ChapterRead50 稳定 ≥ 50 次&#x2F;周</td><td>考虑升级第三阶段，切 ChapterRead50 优化</td></tr><tr><td>Events Manager 出现”信号丢失”</td><td>开始评估 CAPI 方案</td></tr><tr><td>日广告消耗 &gt; $100</td><td>优先上 CAPI，信号准确率的收益大于实现成本</td></tr></tbody></table><p>核心判断：先把事件质量做好（正确触发、正确包装自定义转化），再考虑量的问题（CAPI）。顺序搞反了没意义。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/55b8cce2d3ce/</id>
    <link href="https://blog.zisheng.pro/posts/55b8cce2d3ce/"/>
    <published>2026-07-04T18:00:00.000Z</published>
    <summary>为小说站点接入 Facebook Pixel 并对接广告投流，踩了一堆坑。整理 6 个非显而易见的问题：AEM 术语已废弃、三层事件类型搞混、ChapterCompleted 触发时机错误、两个后台工具职责不清、Meta 广告系列目标锁死、CAPI 在静态导出站的额外成本。给同样做内容站投流的前端开发者参考。</summary>
    <title>Facebook Pixel 接入踩坑全记录：从 AEM 到 ChapterCompleted 触发时机</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="AI" scheme="https://blog.zisheng.pro/categories/AI/"/>
    <category term="Claude Code" scheme="https://blog.zisheng.pro/tags/Claude-Code/"/>
    <category term="Prompt Engineering" scheme="https://blog.zisheng.pro/tags/Prompt-Engineering/"/>
    <category term="AI Infra" scheme="https://blog.zisheng.pro/tags/AI-Infra/"/>
    <category term="Token Optimization" scheme="https://blog.zisheng.pro/tags/Token-Optimization/"/>
    <content>
      <![CDATA[<p>给 Claude Code 写自定义 Skill 已经有一段时间了。最近做了一次专项 token 优化，顺手记录一下思路和方法。</p><h2 id="一句话总结"><a href="#一句话总结" class="headerlink" title="一句话总结"></a>一句话总结</h2><p>Skill 的 reference 文件不是文档，是 context。每 KB 都是运行时成本。按 phase 拆分文件比压缩内容更有杠杆。</p><hr><h2 id="背景：Skill-的-token-消耗来自哪里"><a href="#背景：Skill-的-token-消耗来自哪里" class="headerlink" title="背景：Skill 的 token 消耗来自哪里"></a>背景：Skill 的 token 消耗来自哪里</h2><p>Claude Code 的 Skill 系统工作原理很简单：用户触发 Skill → <code>SKILL.md</code> 进入 context → 执行时按 phase 按需加载 <code>references/*.md</code>。</p><p>理论上”按需加载”已经很省了。但当某个 reference 文件本身就是 162KB，问题就来了：</p><ul><li>它被加载时就是 162KB 的 context 消耗</li><li>如果这个文件里有一半内容是”当前 phase 不需要的”，那一半就是纯浪费</li></ul><p>我的 <code>cover-allure-elements.md</code> 就是这个情况。它同时服务两个 phase：</p><ul><li><strong>A2</strong>（封面生成）：需要 allure 规则 + costume 模板 + genre prompt 模板</li><li><strong>A2.5</strong>（插图生成）：只需要 allure 规则 + pose 表 + exposure tier，<strong>不需要</strong> genre 完整 prompt 模板（~30KB）</li></ul><hr><h2 id="三类优化，效果从高到低"><a href="#三类优化，效果从高到低" class="headerlink" title="三类优化，效果从高到低"></a>三类优化，效果从高到低</h2><h3 id="1-结构性拆分（最高杠杆）"><a href="#1-结构性拆分（最高杠杆）" class="headerlink" title="1. 结构性拆分（最高杠杆）"></a>1. 结构性拆分（最高杠杆）</h3><p>把”不同 phase 的消费者用不同部分”升级为”不同 phase 加载不同文件”。</p><p><strong>操作</strong>：把 <code>cover-allure-elements.md</code> 里的 15 个 genre prompt 模板（约 380 行、30KB）拆出来，新建 <code>cover-genre-playbook.md</code>。</p><p><strong>SKILL.md reference loading 更新</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">A2：加载 cover-allure-elements.md + cover-genre-playbook.md</span><br><span class="line">A2.5：只加载 cover-allure-elements.md</span><br></pre></td></tr></table></figure><p><strong>效果</strong>：每次插图生成任务（A2.5）比之前少加载 27KB。这是结构性节省，不是内容压缩——文件总量没变，但每次调用用到的更少了。</p><h3 id="2-跨文件去重（中等杠杆）"><a href="#2-跨文件去重（中等杠杆）" class="headerlink" title="2. 跨文件去重（中等杠杆）"></a>2. 跨文件去重（中等杠杆）</h3><p>审查发现同一段合规说明出现了三次：</p><table><thead><tr><th>位置</th><th>内容</th><th>行数</th></tr></thead><tbody><tr><td><code>SKILL.md</code></td><td>“no nipples&#x2F;genitals&#x2F;sex acts”</td><td>~8 行</td></tr><tr><td><code>cover-allure-elements.md §0</code></td><td>同上，散文版</td><td>~12 行</td></tr><tr><td><code>adsense-arbitrage.md §1.1</code></td><td>同上，加业务背景</td><td>~10 行</td></tr></tbody></table><p>处理方式：<code>cover-allure-elements.md §0</code> 保留权威版（因为它被 A2 和 A2.5 都加载，离使用者最近），另外两处改为 1 行 cross-reference：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">§0 floor: per `cover-allure-elements.md §0` — no explicit content. Suggestive allure fully allowed; push it.</span><br></pre></td></tr></table></figure><p>节省约 22 行。规则只在一个地方维护，更新时不会出现三处不同步的问题。</p><h3 id="3-模板压缩（中低杠杆）"><a href="#3-模板压缩（中低杠杆）" class="headerlink" title="3. 模板压缩（中低杠杆）"></a>3. 模板压缩（中低杠杆）</h3><p><code>cover-allure-elements.md</code> 里有 21 个 costume 类型，每个都是完整的 prompt 模板（visual + best-for + prompt block）。问题在于：模型其实只需要看懂”如何构造 costume 提示词”的方法论，不需要 21 份完整示例。</p><p>操作：保留 5 个最高频的 costume（Academy、Nurse、Silk Robe、Evening Gown、Wet White Shirt），剩余 16 个改为一行列举：</p><blockquote><p>Other costume types (same structure — use Fabric Allure Ranking to construct prompts): Flight Attendant, Police, Maid, Qipao, Wedding…</p></blockquote><p>节省约 200 行。模型用 5 个 exemplar 学会方法论，遇到其他类型自行套用。</p><hr><h2 id="工具：如何快速找到可优化点"><a href="#工具：如何快速找到可优化点" class="headerlink" title="工具：如何快速找到可优化点"></a>工具：如何快速找到可优化点</h2><p>用一个分析 agent 扫描所有 reference 文件，输出：</p><ol><li><strong>跨文件冗余</strong>：同一内容在哪些文件里出现了多次</li><li><strong>verbose prose 转 table</strong>：段落式描述是否可以压缩为表格</li><li><strong>example 密度</strong>：8 个完整示例是否可以替换为 3 个 exemplar + 方法论</li><li><strong>重复 preamble</strong>：每个文件开头的”为什么这很重要”是否是多余重复</li></ol><p>Prompt 给到 agent 时要求输出具体行号和节省估算，否则结果太泛化没法执行。</p><hr><h2 id="一个反直觉的结论"><a href="#一个反直觉的结论" class="headerlink" title="一个反直觉的结论"></a>一个反直觉的结论</h2><p><strong>内容压缩是锦上添花，结构分离才是关键。</strong></p><p>文件从 162KB 压缩到 117KB，感觉很好。但真正节省大的是”A2.5 不再加载 genre playbook”那 27KB——它每次插图任务都生效，是乘法效应。</p><p>类比到普通工程：代码优化一个函数（内容压缩）vs 拆出一个只在特定路径加载的模块（结构分离）。后者的效益通常更高，因为它减少的是每次调用的基础成本。</p><hr><h2 id="顺带发现的合规问题"><a href="#顺带发现的合规问题" class="headerlink" title="顺带发现的合规问题"></a>顺带发现的合规问题</h2><p><code>facebook-ads.md</code> 全文混用了中文术语（广告系列、广告组、类似受众等）。Skill 文件有一条硬规则：必须用英文写。这不是审美问题，是一致性问题——如果 skill 文件被 agent 加载并转述给另一个 agent，中英混杂会增加解析摩擦。</p><p>处理方式是直接 find-replace 全部术语：广告系列 → Campaign，广告组 → Ad Set，类似受众 → Lookalike Audience，等。</p><hr><h2 id="要不要做-判断框架"><a href="#要不要做-判断框架" class="headerlink" title="要不要做 &#x2F; 判断框架"></a>要不要做 &#x2F; 判断框架</h2><p><strong>值得做</strong>：</p><ul><li>Skill 被高频调用（每次写章节、每次生图都触发）</li><li>单个 reference 文件超过 50KB</li><li>同一个文件被不同 phase 消费，但 phase 之间需要的内容有明显差异</li></ul><p><strong>可以再等</strong>：</p><ul><li>Skill 调用频率低（每月几次）</li><li>所有 reference 文件都在 20KB 以下</li><li>文件内容高度内聚，没有明显的 phase 分离机会</li></ul><p><strong>不值得做</strong>：</p><ul><li>为了”显得更整洁”而拆分（增加维护成本，收益低）</li><li>把所有内容压到极限（模型需要足够的示例才能理解方法论，过度压缩反而降质量）</li></ul><hr><p>当前 fiction-site-builder skill 的总 reference 文件大约 830KB，按 phase 加载，单次任务实际加载通常在 100–200KB 区间。优化之后高频 phase（A2.5）少了 27KB，低频 phase（A2）靠新的文件拆分仍然能拿到完整 genre 库。</p><p>整体感受：token 优化不是”写少一点”，而是”用对粒度”。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/c9a29cde60cd/</id>
    <link href="https://blog.zisheng.pro/posts/c9a29cde60cd/"/>
    <published>2026-07-04T14:00:00.000Z</published>
    <summary>记录一次给 Claude Code 自定义 Skill 做 token 瘦身的实践。核心方法是把 reference 文件按&quot;谁在哪个 phase 用&quot;拆分，把同一个合规说明的三份副本压缩成一份，把 21 个 costume 模板压缩为 5 个 exemplar。结果：最重的单个 reference 文件从 162KB 降到 117KB，每次插图生成额外省 27KB 上下文。适合正在构建 Claude Code 自定义 Skill 的开发者。</summary>
    <title>Claude Code Skill Token 优化实践：从 162KB 到 117KB</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="AI" scheme="https://blog.zisheng.pro/categories/AI/"/>
    <category term="Facebook Pixel" scheme="https://blog.zisheng.pro/tags/Facebook-Pixel/"/>
    <category term="Facebook Ads" scheme="https://blog.zisheng.pro/tags/Facebook-Ads/"/>
    <category term="独立站" scheme="https://blog.zisheng.pro/tags/%E7%8B%AC%E7%AB%8B%E7%AB%99/"/>
    <category term="投流" scheme="https://blog.zisheng.pro/tags/%E6%8A%95%E6%B5%81/"/>
    <category term="流量变现" scheme="https://blog.zisheng.pro/tags/%E6%B5%81%E9%87%8F%E5%8F%98%E7%8E%B0/"/>
    <category term="AdSense" scheme="https://blog.zisheng.pro/tags/AdSense/"/>
    <content>
      <![CDATA[<p>在给小说站接 Facebook Pixel 的过程中，我踩了一个坑：把 ScrollDepth25 和 ScrollDepth75 也想着加进 AEM（聚合事件衡量）的优先级队列——毕竟”用户滚动越深说明越感兴趣”，这个逻辑听起来很合理。</p><p>后来想清楚了，这是在做一件很蠢的事。</p><h2 id="一句话总结"><a href="#一句话总结" class="headerlink" title="一句话总结"></a>一句话总结</h2><p>Facebook Pixel 事件分两层——<strong>转化事件</strong>（优化广告用）和<strong>参与度事件</strong>（诊断 + 建受众用）——用错层等于给算法喂错信号，Facebook 会去找”爱滚动”的人，不是”爱读小说”的人。</p><h2 id="两层事件，本质是信号质量问题"><a href="#两层事件，本质是信号质量问题" class="headerlink" title="两层事件，本质是信号质量问题"></a>两层事件，本质是信号质量问题</h2><p>先看 Facebook 优化目标是怎么工作的：</p><p>你在 Campaign 里设置”优化 ViewContent”，Facebook 就会把你的广告展示给<strong>最可能触发 ViewContent 的人</strong>。算法本质是在学习：哪些人口特征、行为特征的人，最容易产生这个事件。</p><p>所以核心问题是：<strong>你希望 Facebook 去找什么样的人？</strong></p><p>这就是事件分层的本质——不同事件代表不同的用户行为，代表不同质量的人群信号。</p><table><thead><tr><th>层级</th><th>事件</th><th>信号含义</th><th>用法</th></tr></thead><tbody><tr><td><strong>转化事件</strong></td><td>ViewContent</td><td>打开了章节、开始读</td><td>AEM + Campaign 优化目标</td></tr><tr><td><strong>转化事件</strong></td><td>ChapterRead50</td><td>读到章节 50%，真读者</td><td>AEM 高优先级</td></tr><tr><td><strong>转化事件</strong></td><td>ChapterCompleted</td><td>读完整章，深度读者</td><td>AEM 最高优先级</td></tr><tr><td><strong>参与度事件</strong></td><td>PageView</td><td>点进了网站，包括 1 秒就走的</td><td>数据量积累</td></tr><tr><td><strong>参与度事件</strong></td><td>ScrollDepth25</td><td>滚了四分之一，可能在看</td><td>受众建模、行为诊断</td></tr><tr><td><strong>参与度事件</strong></td><td>ScrollDepth75</td><td>真的在读这一章</td><td>高质量 Lookalike 种子</td></tr></tbody></table><h2 id="AEM-的-8-个槽，是资源，不是垃圾桶"><a href="#AEM-的-8-个槽，是资源，不是垃圾桶" class="headerlink" title="AEM 的 8 个槽，是资源，不是垃圾桶"></a>AEM 的 8 个槽，是资源，不是垃圾桶</h2><p>iOS 14+ 之后，苹果的隐私政策让大部分 iOS 用户的广告追踪失效。Facebook 的解决方案是 AEM（Aggregated Event Measurement）：你预先申报最多 8 个事件，按优先级排列，Facebook 用差分隐私的方式帮你归因。</p><p>关键：<strong>只有 8 个槽</strong>，而且优先级顺序决定归因逻辑——当一个 iOS 用户触发了多个事件，Facebook 只把转化归因给其中优先级最高的一个。</p><p>正确的 AEM 配置应该是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ChapterCompleted (1) &gt; ChapterRead50 (2) &gt; ViewContent (3) &gt; PageView (4)</span><br></pre></td></tr></table></figure><p>ScrollDepth25、ScrollDepth75 <strong>不应该在这个队列里</strong>。原因：</p><ol><li>它们不是转化信号，是过程信号</li><li>浪费宝贵的 AEM 槽位</li><li>如果 ScrollDepth75 的优先级高于 ViewContent，iOS 用户触发了两个事件，归因会错误地落在 ScrollDepth75 而不是 ViewContent</li></ol><h2 id="ScrollDepth-有用，但用法不是”优化”"><a href="#ScrollDepth-有用，但用法不是”优化”" class="headerlink" title="ScrollDepth 有用，但用法不是”优化”"></a>ScrollDepth 有用，但用法不是”优化”</h2><p>有用，但用法是<strong>数据诊断 + 受众分层</strong>，不是优化目标。三个具体用途：</p><p><strong>1. 诊断广告质量</strong></p><p>投一批广告 → 看落地章节的 ScrollDepth75 触发率：</p><ul><li>ScrollDepth75 率高 &#x3D; 这批人真的在读 → 广告找对人了</li><li>ScrollDepth75 率低但 ViewContent 高 &#x3D; 人进来了但不读 → 广告文案&#x2F;封面和内容不匹配</li></ul><p>没有 ScrollDepth，你只知道”有人进来了”，不知道”有没有人在读”。这个区别决定了你该换素材还是换受众，解法完全不同。</p><p><strong>2. 建高质量 Lookalike</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ScrollDepth75 的人 → 建自定义受众 → Lookalike 1%</span><br></pre></td></tr></table></figure><p>比用 PageView 或 ViewContent 建的 Lookalike 质量高得多——这批人是确认的真读者，Facebook 会去找特征相似的人，带来的也更可能是真读者。</p><p><strong>3. 归因分析</strong></p><p>对比 ScrollDepth25 vs ScrollDepth75 的转化漏斗：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">100 人进章节 → 60 人到 25% → 30 人到 75%</span><br></pre></td></tr></table></figure><p>留存率 50%，说明内容前段吸引力还行，后段掉人。这是<strong>内容问题</strong>，不是广告问题——没有 ScrollDepth 数据，这个判断很难做到。</p><hr><p><strong>简单说：ScrollDepth 是侦察兵，不是士兵。</strong></p><p>它不直接打仗（优化 Campaign），但告诉你谁在认真读、广告找的人对不对、内容哪里在掉人。没有它，你做投流决策是靠猜的。</p><h2 id="一个反直觉的坑：PageView-不是越多越好"><a href="#一个反直觉的坑：PageView-不是越多越好" class="headerlink" title="一个反直觉的坑：PageView 不是越多越好"></a>一个反直觉的坑：PageView 不是越多越好</h2><p>初期容易有个误区：PageView 数量越多，数据越充分，广告就越精准。</p><p>实际上 PageView 的信号质量是最低的——包含了点进来 1 秒就关掉的人、误点广告的人、爬虫（如果 Pixel 没做过滤的话）。用 PageView 做优化目标，Facebook 学到的是”什么样的人最容易点广告”，而不是”什么样的人最容易读小说”。</p><p>PageView 的唯一价值是<strong>数据量积累</strong>——新账号冷启动阶段，用它帮 Facebook 快速建立人群基础，之后该切的时候要切。</p><p>当 ViewContent 积累到 500 次，就应该把 Campaign 优化目标从”落地页点击”切换到”ViewContent”。这一步是投流的质变节点。</p><h2 id="我的判断框架"><a href="#我的判断框架" class="headerlink" title="我的判断框架"></a>我的判断框架</h2><p>遇到一个新事件，用两个问题判断它的定位：</p><p><strong>问题 1：这个事件代表的用户，是我真正想要找到的人吗？</strong></p><ul><li>ChapterCompleted 的人 &#x3D; 读完整章 &#x3D; 是我想要的人 ✓</li><li>ScrollDepth25 的人 &#x3D; 滚了一点 &#x3D; 不确定，太模糊 ✗</li></ul><p><strong>问题 2：如果 Facebook 去找更多”触发这个事件的人”，找来的人符合预期吗？</strong></p><ul><li>优化 ChapterRead50 → 找来的人更可能认真读小说 ✓</li><li>优化 ScrollDepth → 找来的人更可能”爱滚动”，不代表会读完 ✗</li></ul><p>通过这两个问题的事件 → 放进转化层，可以用作 AEM 和优化目标。<br>不通过的事件 → 放进参与度层，用来建受众和做诊断。</p><hr><p>具体到我们的小说站，最终实现的事件体系：</p><ul><li><strong>布置在 layout（全局）</strong>：PageView</li><li><strong>布置在章节页 mount（进章节触发）</strong>：ViewContent</li><li><strong>布置在章节页 scroll（读的过程触发）</strong>：ChapterRead50、ChapterCompleted、ScrollDepth25、ScrollDepth75</li></ul><p>前两层进 AEM 配置和 Campaign 优化目标，后面的 scroll 事件全部留在参与度层做受众和诊断用。这个分层是当前最合理的实现，不过随着数据积累，ChapterCompleted 进 AEM 的时机还需要观察。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/2b2ef1726b7e/</id>
    <link href="https://blog.zisheng.pro/posts/2b2ef1726b7e/"/>
    <published>2026-07-04T14:00:00.000Z</published>
    <summary>给小说站接 Facebook Pixel 时，我发现一个反直觉的问题：不是所有自定义事件都该用来优化广告。PageView、ViewContent、ScrollDepth、ChapterCompleted——这些事件的信号质量天壤之别，用错了等于花真金白银训练 Facebook 去找&quot;爱点广告的人&quot;而不是&quot;爱读小说的人&quot;。这篇文章把事件分层讲清楚，给出判断框架。</summary>
    <title>Facebook Pixel 事件分层：用错事件等于训练算法找错人</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="AI" scheme="https://blog.zisheng.pro/categories/AI/"/>
    <category term="Facebook Pixel" scheme="https://blog.zisheng.pro/tags/Facebook-Pixel/"/>
    <category term="AdSense" scheme="https://blog.zisheng.pro/tags/AdSense/"/>
    <category term="Next.js" scheme="https://blog.zisheng.pro/tags/Next-js/"/>
    <category term="小说站" scheme="https://blog.zisheng.pro/tags/%E5%B0%8F%E8%AF%B4%E7%AB%99/"/>
    <category term="广告套利" scheme="https://blog.zisheng.pro/tags/%E5%B9%BF%E5%91%8A%E5%A5%97%E5%88%A9/"/>
    <category term="scroll tracking" scheme="https://blog.zisheng.pro/tags/scroll-tracking/"/>
    <content>
      <![CDATA[<p>做小说站投流，最容易踩的坑是：用 <code>PageView</code> 作为广告优化事件。</p><p><code>PageView</code> 的语义是「有人来了」，而不是「有人读了」。Facebook 算法拿这个信号去优化，会找到「擅长点广告」的人群，而不是「愿意读小说」的人群。结果就是 CTR 看着不错，但用户进来翻一页就走，AdSense 会话 RPM 极低，ROAS（Return on Ad Spend，广告花费回报率 &#x3D; 广告带来的收入 &#x2F; 广告花费）算下来根本不打平。</p><h2 id="一句话总结"><a href="#一句话总结" class="headerlink" title="一句话总结"></a>一句话总结</h2><p>在章节页实装 3 个 Pixel 事件：ViewContent（进入即触发）、ChapterRead50 和 ChapterCompleted（scroll depth 事件），让 Facebook 算法学会找「真正读完一章」的人，而不是「会点广告」的人。</p><h2 id="为什么-PageView-是最差的优化信号"><a href="#为什么-PageView-是最差的优化信号" class="headerlink" title="为什么 PageView 是最差的优化信号"></a>为什么 PageView 是最差的优化信号</h2><p>小说站的盈利公式：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Profit/visit = (会话 RPM × 翻页数/session) / 1000 − Facebook CPC</span><br></pre></td></tr></table></figure><p>CPC 固定的情况下，唯一能拉高 ROAS 的是「翻页数&#x2F;session」。而翻页数取决于用户是不是真的在读——这正是 <code>PageView</code> 衡量不到的。</p><p>Facebook 的优化逻辑是：给我 50 个「转化事件」的样本，我帮你找更多像这批人的用户。用 <code>PageView</code> 做样本，找到的是「会点广告的人」；用 <code>ChapterCompleted</code>（读完整章）做样本，找到的是「深度读者」。</p><h2 id="Pixel-事件层级设计"><a href="#Pixel-事件层级设计" class="headerlink" title="Pixel 事件层级设计"></a>Pixel 事件层级设计</h2><p>我给章节页设计了 6 层事件，覆盖从「进来了」到「读完了」的完整漏斗：</p><table><thead><tr><th>事件</th><th>触发</th><th>AEM 优先级</th><th>用途</th></tr></thead><tbody><tr><td><code>PageView</code></td><td>全局自动</td><td>4（最低）</td><td>数据量打底，冷启动用</td></tr><tr><td><code>ViewContent</code></td><td>进入章节即触发</td><td>3</td><td>广告优化主信号，积累 500+ 后切优化目标</td></tr><tr><td><code>ScrollDepth25</code></td><td>scroll ≥ 25%</td><td>漏斗分析</td><td>判断 ch1 开头 hook 是否抓住人</td></tr><tr><td><code>ChapterRead50</code></td><td>scroll ≥ 50%</td><td>2</td><td>强意图信号，AEM 次级优先</td></tr><tr><td><code>ScrollDepth75</code></td><td>scroll ≥ 75%</td><td>漏斗分析</td><td>章节中后段内容质量诊断</td></tr><tr><td><code>ChapterCompleted</code></td><td>scroll ≥ <strong>90%</strong></td><td>1（最高）</td><td>最强信号，Lookalike seed</td></tr></tbody></table><blockquote><p><strong>为什么用 90% 而不是 100%？</strong> 章节页底部有广告位、推荐书目、Share 栏。读完正文后用户视线就停了，不会继续滚到绝对底部。90% 才是实际的「读完」代理指标，100% 在现实中几乎永远不触发。</p></blockquote><h2 id="实现：ChapterPixel-组件"><a href="#实现：ChapterPixel-组件" class="headerlink" title="实现：ChapterPixel 组件"></a>实现：ChapterPixel 组件</h2><p>一个轻量的 client component，挂在 chapter page 里，用单个 passive scroll listener + <code>useRef</code> 防重复触发：</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&#x27;use client&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; useEffect, useRef &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">Props</span> &#123;</span><br><span class="line">  <span class="attr">chapterTitle</span>: <span class="built_in">string</span></span><br><span class="line">  <span class="attr">chapterOrder</span>: <span class="built_in">number</span></span><br><span class="line">  <span class="attr">bookSlug</span>: <span class="built_in">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">ChapterPixel</span>(<span class="params">&#123; chapterTitle, chapterOrder, bookSlug &#125;: Props</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> fired25 = <span class="title function_">useRef</span>(<span class="literal">false</span>)</span><br><span class="line">  <span class="keyword">const</span> fired50 = <span class="title function_">useRef</span>(<span class="literal">false</span>)</span><br><span class="line">  <span class="keyword">const</span> fired75 = <span class="title function_">useRef</span>(<span class="literal">false</span>)</span><br><span class="line">  <span class="keyword">const</span> fired90 = <span class="title function_">useRef</span>(<span class="literal">false</span>)</span><br><span class="line"></span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> fbq = (<span class="variable language_">window</span> <span class="keyword">as</span> <span class="title class_">Window</span> &amp; &#123; fbq?: <span class="function">(<span class="params">...args: <span class="built_in">unknown</span>[]</span>) =&gt;</span> <span class="built_in">void</span> &#125;).<span class="property">fbq</span></span><br><span class="line">    <span class="comment">// ViewContent 进入即触发 — 区分「进来了」和「立刻离开」</span></span><br><span class="line">    fbq?.(<span class="string">&#x27;track&#x27;</span>, <span class="string">&#x27;ViewContent&#x27;</span>, &#123;</span><br><span class="line">      <span class="attr">content_type</span>: <span class="string">&#x27;chapter&#x27;</span>,</span><br><span class="line">      <span class="attr">content_name</span>: chapterTitle,</span><br><span class="line">      <span class="attr">content_ids</span>: [<span class="string">`<span class="subst">$&#123;bookSlug&#125;</span>-ch<span class="subst">$&#123;chapterOrder&#125;</span>`</span>],</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> payload = &#123; <span class="attr">content_name</span>: chapterTitle, <span class="attr">chapter_order</span>: chapterOrder &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">onScroll</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">      <span class="keyword">const</span> total = <span class="variable language_">document</span>.<span class="property">body</span>.<span class="property">scrollHeight</span> - <span class="variable language_">window</span>.<span class="property">innerHeight</span></span><br><span class="line">      <span class="keyword">if</span> (total &lt;= <span class="number">0</span>) <span class="keyword">return</span></span><br><span class="line">      <span class="keyword">const</span> ratio = <span class="variable language_">window</span>.<span class="property">scrollY</span> / total</span><br><span class="line">      <span class="keyword">const</span> fire = (<span class="variable language_">window</span> <span class="keyword">as</span> <span class="title class_">Window</span> &amp; &#123; fbq?: <span class="function">(<span class="params">...args: <span class="built_in">unknown</span>[]</span>) =&gt;</span> <span class="built_in">void</span> &#125;).<span class="property">fbq</span></span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (!fired25.<span class="property">current</span> &amp;&amp; ratio &gt;= <span class="number">0.25</span>) &#123;</span><br><span class="line">        fired25.<span class="property">current</span> = <span class="literal">true</span></span><br><span class="line">        fire?.(<span class="string">&#x27;trackCustom&#x27;</span>, <span class="string">&#x27;ScrollDepth25&#x27;</span>, payload)</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">if</span> (!fired50.<span class="property">current</span> &amp;&amp; ratio &gt;= <span class="number">0.5</span>) &#123;</span><br><span class="line">        fired50.<span class="property">current</span> = <span class="literal">true</span></span><br><span class="line">        fire?.(<span class="string">&#x27;trackCustom&#x27;</span>, <span class="string">&#x27;ChapterRead50&#x27;</span>, payload)</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">if</span> (!fired75.<span class="property">current</span> &amp;&amp; ratio &gt;= <span class="number">0.75</span>) &#123;</span><br><span class="line">        fired75.<span class="property">current</span> = <span class="literal">true</span></span><br><span class="line">        fire?.(<span class="string">&#x27;trackCustom&#x27;</span>, <span class="string">&#x27;ScrollDepth75&#x27;</span>, payload)</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">if</span> (!fired90.<span class="property">current</span> &amp;&amp; ratio &gt;= <span class="number">0.9</span>) &#123;</span><br><span class="line">        fired90.<span class="property">current</span> = <span class="literal">true</span></span><br><span class="line">        fire?.(<span class="string">&#x27;trackCustom&#x27;</span>, <span class="string">&#x27;ChapterCompleted&#x27;</span>, payload)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;scroll&#x27;</span>, onScroll, &#123; <span class="attr">passive</span>: <span class="literal">true</span> &#125;)</span><br><span class="line">    <span class="keyword">return</span> <span class="function">() =&gt;</span> <span class="variable language_">window</span>.<span class="title function_">removeEventListener</span>(<span class="string">&#x27;scroll&#x27;</span>, onScroll)</span><br><span class="line">  &#125;, [chapterTitle, chapterOrder, bookSlug])</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="literal">null</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>chapter page 里直接挂上去：</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// app/book/[slug]/chapter/[n]/page.tsx</span></span><br><span class="line">&lt;<span class="title class_">ChapterPixel</span></span><br><span class="line">  chapterTitle=&#123;chapter.<span class="property">title</span>&#125;</span><br><span class="line">  chapterOrder=&#123;chapter.<span class="property">order</span>&#125;</span><br><span class="line">  bookSlug=&#123;slug&#125;</span><br><span class="line">/&gt;</span><br></pre></td></tr></table></figure><p>几个设计细节值得注意：</p><ul><li><strong><code>useRef</code> 防重复</strong>：scroll 事件高频触发，必须用 ref 确保每个深度只上报一次</li><li><strong><code>passive: true</code></strong>：不阻塞渲染线程，对 Core Web Vitals 无影响</li><li><strong><code>fbq</code> optional chaining</strong>：Pixel 加载有延迟，防止脚本未就绪时报错</li><li><strong>ViewContent 在 mount 而非 scroll</strong>：「进入章节」本身就是有效的意图信号，不需要等到用户滚动</li></ul><h2 id="FB-后台还需要手动配置"><a href="#FB-后台还需要手动配置" class="headerlink" title="FB 后台还需要手动配置"></a>FB 后台还需要手动配置</h2><p>代码侧搞定后，还有两步后台操作才能真正用上这些信号：</p><p><strong>1. 创建自定义转化</strong>（ChapterRead50 &#x2F; ChapterCompleted）</p><p>Events Manager → 自定义转化 → 新建，选择「自定义事件」→ 选对应事件名。<code>PageView</code> 和 <code>ViewContent</code> 是 Facebook 标准事件，不需要这步。</p><p><strong>2. 配置 AEM（聚合事件衡量）</strong></p><p>iOS 14+ 之后大部分 iOS 用户拒绝追踪，Facebook 的解法是「申报最多 8 个事件 + 优先级」，信号丢失时归因到优先级最高的可用事件。</p><p>路径：Events Manager → 你的 Pixel → 聚合事件衡量 → 配置网络事件</p><p>推荐优先级：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ChapterCompleted (1) &gt; ChapterRead50 (2) &gt; ViewContent (3) &gt; PageView (4)</span><br></pre></td></tr></table></figure><p><code>ScrollDepth25</code> 和 <code>ScrollDepth75</code> 不加进 AEM——它们是漏斗分析用的，不是广告优化目标。AEM 只配置你真正用于 Campaign 优化的事件。</p><h2 id="要不要做-我的判断框架"><a href="#要不要做-我的判断框架" class="headerlink" title="要不要做 &#x2F; 我的判断框架"></a>要不要做 &#x2F; 我的判断框架</h2><p><strong>值得立刻做：</strong> 任何在跑 Facebook 流量的小说站都应该实装。几十行代码，对投流效果的影响是系统性的——算法找的人不一样，后续所有优化都站在更好的基础上。</p><p><strong>可以再等：</strong> CAPI（Conversions API，服务端回传）。iOS 信号丢失严重时 CAPI 能补回 30%+ 的归因，但实现复杂（静态导出站需要额外部署 serverless function）。当前阶段流量体量不大，先把浏览器 Pixel 跑稳，等 ViewContent 积累到 500+ 再评估是否上 CAPI。</p><p><strong>重点关注：</strong> ViewContent 积累到 500 次后，把 Campaign 优化目标从「落地页点击」切换为「ViewContent」，这是整个链路里 CPM 下降最明显的节点。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/9738a32a70ad/</id>
    <link href="https://blog.zisheng.pro/posts/9738a32a70ad/"/>
    <published>2026-07-04T14:00:00.000Z</published>
    <summary>在小说站 Facebook 投流中，PageView 是最糟糕的优化信号——它把「看了 3 秒就走的人」和「读完整章的人」混在一起。这篇文章记录我如何用 ChapterPixel 组件实装 6 层 scroll depth 事件，让 Facebook 算法精准区分不同深度的读者，最终降低 CPM、提升会话 RPM。</summary>
    <title>小说站 Facebook Pixel 深度配置：用 scroll depth 事件训练算法找「真实读者」</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="AI" scheme="https://blog.zisheng.pro/categories/AI/"/>
    <category term="Claude Code" scheme="https://blog.zisheng.pro/categories/AI/Claude-Code/"/>
    <category term="Claude Code" scheme="https://blog.zisheng.pro/tags/Claude-Code/"/>
    <category term="AI Infra" scheme="https://blog.zisheng.pro/tags/AI-Infra/"/>
    <category term="/btw" scheme="https://blog.zisheng.pro/tags/btw/"/>
    <category term="上下文管理" scheme="https://blog.zisheng.pro/tags/%E4%B8%8A%E4%B8%8B%E6%96%87%E7%AE%A1%E7%90%86/"/>
    <category term="工作流" scheme="https://blog.zisheng.pro/tags/%E5%B7%A5%E4%BD%9C%E6%B5%81/"/>
    <content>
      <![CDATA[<p>做 Agent 开发之后，我越来越在意一件事：<strong>上下文的纯净度</strong>。Claude Code 在处理一个大型重构任务时，我突然想确认一个之前讨论过的设计决策——如果直接在主对话里问，这个问题和答案会永远留在历史里，影响后续 Claude 的判断。如果开新 session，又要重新铺上下文。</p><p>我以前的做法是忍着不问，或者开一个单独的 Claude 窗口。直到我发现了 <code>/btw</code>。</p><h2 id="一句话总结"><a href="#一句话总结" class="headerlink" title="一句话总结"></a>一句话总结</h2><p><code>/btw</code> &#x3D; 不留痕的侧问（有上下文，无工具，答案不进历史）；<code>f to fork</code> &#x3D; 把这个侧问升级成有完整工具的新主战场。</p><hr><h2 id="一、-btw-是什么"><a href="#一、-btw-是什么" class="headerlink" title="一、&#x2F;btw 是什么"></a>一、&#x2F;btw 是什么</h2><p><code>/btw</code> 是 “by the way”（顺便问一句）的缩写，在 Claude Code <strong>v2.1.72</strong>（2026-03-10）引入。</p><p>它的核心设计哲学：<strong>快速提问，不污染历史</strong>。</p><p>执行 <code>/btw 你的问题</code> 之后，Claude 会在一个浮层面板里回答，这条问答<strong>不会作为 turn 添加到对话历史</strong>。主任务继续，上下文保持干净。</p><h3 id="与-subagent-的镜像关系"><a href="#与-subagent-的镜像关系" class="headerlink" title="与 subagent 的镜像关系"></a>与 subagent 的镜像关系</h3><p>官方文档有一句非常精准的描述：</p><blockquote><p><code>/btw</code> is the inverse of a subagent: it sees your full conversation but has no tools, while a subagent has full tools but starts with an empty context.</p></blockquote><table><thead><tr><th>维度</th><th><code>/btw</code></th><th>Subagent</th></tr></thead><tbody><tr><td>上下文</td><td>✅ 完整继承主对话</td><td>❌ 空白开始</td></tr><tr><td>工具访问</td><td>❌ 无（只能基于已有上下文回答）</td><td>✅ 完整工具</td></tr><tr><td>历史污染</td><td>❌ 不进入对话历史</td><td>结果汇报回主对话</td></tr><tr><td>适合场景</td><td>快速澄清、上下文查询</td><td>后台并行任务</td></tr></tbody></table><hr><h2 id="二、结果面板：6-个键盘快捷键"><a href="#二、结果面板：6-个键盘快捷键" class="headerlink" title="二、结果面板：6 个键盘快捷键"></a>二、结果面板：6 个键盘快捷键</h2><p><code>/btw</code> 的答案出现后，浮层面板支持以下操作：</p><table><thead><tr><th>键</th><th>操作</th><th>说明</th></tr></thead><tbody><tr><td><code>↑</code> &#x2F; <code>↓</code></td><td>滚动答案</td><td>长答案时用</td></tr><tr><td><code>←</code> &#x2F; <code>→</code></td><td>切换历史侧问</td><td>v2.1.187+，在本 session 的多个 <code>/btw</code> 答案间翻页</td></tr><tr><td><code>c</code></td><td>复制为 Markdown</td><td>推荐方式，原始格式，见下文</td></tr><tr><td><code>f</code></td><td>Fork 成新 session</td><td>v2.1.161+，见下文</td></tr><tr><td><code>x</code></td><td>清除历史侧问列表</td><td>清掉面板上方显示的之前问答记录</td></tr><tr><td><code>Esc</code> &#x2F; <code>Enter</code> &#x2F; <code>Space</code></td><td>关闭面板</td><td>返回主对话提示符</td></tr></tbody></table><hr><h2 id="三、c-to-copy：为什么不用鼠标选？"><a href="#三、c-to-copy：为什么不用鼠标选？" class="headerlink" title="三、c to copy：为什么不用鼠标选？"></a>三、c to copy：为什么不用鼠标选？</h2><p>按 <code>c</code> 会把答案以<strong>原始 Markdown 格式</strong>复制到剪贴板。</p><p>这不是废话——终端渲染文本时会做”硬换行”（hard wrap），鼠标选中复制出来的是被截断的版本，粘贴到 Notion &#x2F; Obsidian 之后格式全乱。</p><p><code>c</code> 键直接拿的是 Claude 返回的源文本，<strong>代码块、标题、列表全部保留</strong>，粘贴即用。</p><hr><h2 id="四、f-to-fork：把侧问升级成主战场"><a href="#四、f-to-fork：把侧问升级成主战场" class="headerlink" title="四、f to fork：把侧问升级成主战场"></a>四、f to fork：把侧问升级成主战场</h2><p>这是整个 <code>/btw</code> 功能里最强的一个操作。</p><p>按 <code>f</code> 之后，Claude Code 会：</p><ol><li>把<strong>完整的主对话历史</strong> + <strong>这条 <code>/btw</code> 问答</strong>作为 transcript 继承</li><li>在此基础上开一个<strong>新的独立 session</strong></li><li>新 session 有<strong>完整的工具访问权限</strong>（可以读文件、运行命令、编辑代码）</li><li>原始 session 通过 <code>/resume</code> 可随时返回</li></ol><p><strong>典型工作流</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"># 正在做大型重构，Claude 还在工作</span><br><span class="line">/btw 这个 API 的响应格式对吗？</span><br><span class="line"></span><br><span class="line"># Claude 回答：不对，应该是 &#123; data: [], total: number &#125;</span><br><span class="line"># 你觉得需要深入修复，不想打断主任务</span><br><span class="line"></span><br><span class="line">f    # Fork 成新 session，继承完整上下文 + 有工具</span><br><span class="line"># 新 session 里：帮我修复这个 API 的响应格式</span><br><span class="line"></span><br><span class="line"># 修完之后 /resume 回主任务</span><br></pre></td></tr></table></figure><h3 id="Fork-vs-Branch"><a href="#Fork-vs-Branch" class="headerlink" title="Fork vs Branch"></a>Fork vs Branch</h3><table><thead><tr><th></th><th><code>/btw</code> + <code>f</code></th><th><code>/branch</code></th></tr></thead><tbody><tr><td>原 session</td><td>继续运行</td><td>暂停</td></tr><tr><td>新 session</td><td>独立开启，有完整工具</td><td>你切换过去继续</td></tr><tr><td>适合场景</td><td>侧问后发现需要深入处理</td><td>主动想探索另一条路径</td></tr></tbody></table><hr><h2 id="五、与直接在主对话输入的区别"><a href="#五、与直接在主对话输入的区别" class="headerlink" title="五、与直接在主对话输入的区别"></a>五、与直接在主对话输入的区别</h2><table><thead><tr><th>维度</th><th><code>/btw 侧问</code></th><th>主对话输入</th></tr></thead><tbody><tr><td>上下文污染</td><td>❌ 不进历史</td><td>✅ 永久写入</td></tr><tr><td>工具访问</td><td>❌ 无</td><td>✅ 完整</td></tr><tr><td>成本</td><td>低（复用 prompt cache）</td><td>标准</td></tr><tr><td>是否中断主任务</td><td>❌ 可在 Claude 工作时插入</td><td>✅ 等当前 turn 完成</td></tr><tr><td>适合提问类型</td><td>澄清、确认、上下文查询</td><td>新指令、代码修改</td></tr></tbody></table><p>关键点：<strong><code>/btw</code> 可以在 Claude 还在处理响应时插入</strong>，不需要等它完成。</p><hr><h2 id="六、5-个最有价值的实战场景"><a href="#六、5-个最有价值的实战场景" class="headerlink" title="六、5 个最有价值的实战场景"></a>六、5 个最有价值的实战场景</h2><h3 id="场景-1：长任务中途确认设计决策"><a href="#场景-1：长任务中途确认设计决策" class="headerlink" title="场景 1：长任务中途确认设计决策"></a>场景 1：长任务中途确认设计决策</h3><p>Claude 正在做 30 分钟的大型重构，你突然想想起来一个细节：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/btw 我们之前说的是用 React Query 还是 SWR 管理数据缓存？</span><br></pre></td></tr></table></figure><p>快速得到答案，主任务继续，历史不受影响。</p><h3 id="场景-2：对话已很长，快速找回前面的信息"><a href="#场景-2：对话已很长，快速找回前面的信息" class="headerlink" title="场景 2：对话已很长，快速找回前面的信息"></a>场景 2：对话已很长，快速找回前面的信息</h3><p>50+ turns 的长 session，需要确认一个早期决策：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/btw 我们在第几步决定不用 Redux 的？原因是什么？</span><br></pre></td></tr></table></figure><p>成本低（命中 prompt cache），不会因为问这个让后续对话偏题。</p><h3 id="场景-3：上下文检查，防止-Claude-跑偏"><a href="#场景-3：上下文检查，防止-Claude-跑偏" class="headerlink" title="场景 3：上下文检查，防止 Claude 跑偏"></a>场景 3：上下文检查，防止 Claude 跑偏</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/btw 目前你对这个项目的技术栈理解是什么？列一下</span><br></pre></td></tr></table></figure><p>用来”探针”Claude 当前的理解是否和你一致，不对就及时修正，而不是等出了 bug 再发现。</p><h3 id="场景-4：侧问-fork-的黄金组合"><a href="#场景-4：侧问-fork-的黄金组合" class="headerlink" title="场景 4：侧问 + fork 的黄金组合"></a>场景 4：侧问 + fork 的黄金组合</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">/btw 这个 SQL 查询有没有 N+1 问题？</span><br><span class="line"></span><br><span class="line"># Claude 回答：有，在 getUserOrders 里</span><br><span class="line">f</span><br><span class="line"># 新 session 里处理这个问题，有完整文件访问权限</span><br></pre></td></tr></table></figure><h3 id="场景-5：多-subagent-并行时的状态检查"><a href="#场景-5：多-subagent-并行时的状态检查" class="headerlink" title="场景 5：多 subagent 并行时的状态检查"></a>场景 5：多 subagent 并行时的状态检查</h3><p>后台跑着几个 subagent，不想打断主对话：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/btw 目前正在运行的 subagent 有哪些任务？</span><br></pre></td></tr></table></figure><hr><h2 id="七、配合个人工作流的实践建议"><a href="#七、配合个人工作流的实践建议" class="headerlink" title="七、配合个人工作流的实践建议"></a>七、配合个人工作流的实践建议</h2><p>我目前在 fwork（飞猪 Cloud GUI Agent）开发中的用法：</p><ul><li><strong>任何”确认类”问题</strong>优先 <code>/btw</code>，而不是直接问——问完即走，不留污染</li><li><strong>每隔 20-30 turns</strong> 用 <code>/btw 你对当前任务状态的理解是什么</code> 做一次”探针”</li><li>发现侧问答案值得深入时，<strong>立即 <code>f</code> fork</strong>，不要在主对话里展开</li><li>长对话结束前用 <code>/btw</code> 整理关键决策，再用 <code>c</code> 复制到 Obsidian 存档</li></ul><hr><h2 id="要不要用-我的判断框架"><a href="#要不要用-我的判断框架" class="headerlink" title="要不要用 &#x2F; 我的判断框架"></a>要不要用 &#x2F; 我的判断框架</h2><p><strong>现在就该用</strong>：</p><ul><li>你经常在长 session 中途想问一个细节，但又担心影响后续上下文</li><li>你有”问完发现要深入处理”的场景，<code>f</code> + fork 是完美解法</li><li>你在跑多个 subagent，需要快速旁问而不中断主流</li></ul><p><strong>注意这些限制</strong>：</p><ul><li><code>/btw</code> 没有工具——它不能帮你读文件、运行命令。只能基于<strong>已在上下文里的信息</strong>回答</li><li>如果问题本身需要 Claude 去查代码，还是得在主对话问（或者 fork 出去处理）</li><li><code>←</code>&#x2F;<code>→</code> 切换历史侧问需要 v2.1.187+，旧版本没有</li></ul><p><strong>重点关注</strong>：</p><ul><li><code>f to fork</code> 是 <code>/btw</code> 价值密度最高的功能，但大多数人不知道——记住这个键</li><li>复制答案永远用 <code>c</code>，不要鼠标选，格式更干净</li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/e263b0c1c425/</id>
    <link href="https://blog.zisheng.pro/posts/e263b0c1c425/"/>
    <published>2026-07-04T13:00:00.000Z</published>
    <summary>Claude Code 的 /btw 命令是一个&quot;顺便问一句&quot;的侧问功能，答案不会进入对话历史，不打断主任务，还能用 c 复制为原始 Markdown、用 f 一键 fork 成拥有完整工具的新 session。本文从原理到实战，带你把这个被大多数人忽视的小命令用到极致，从此告别&quot;为了问一个细节把上下文污染了&quot;的困境。</summary>
    <title>Claude Code /btw 实践指南</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="广告投放" scheme="https://blog.zisheng.pro/categories/%E5%B9%BF%E5%91%8A%E6%8A%95%E6%94%BE/"/>
    <category term="Facebook" scheme="https://blog.zisheng.pro/categories/%E5%B9%BF%E5%91%8A%E6%8A%95%E6%94%BE/Facebook/"/>
    <category term="Facebook Pixel" scheme="https://blog.zisheng.pro/tags/Facebook-Pixel/"/>
    <category term="独立站" scheme="https://blog.zisheng.pro/tags/%E7%8B%AC%E7%AB%8B%E7%AB%99/"/>
    <category term="Meta Pixel" scheme="https://blog.zisheng.pro/tags/Meta-Pixel/"/>
    <category term="CAPI" scheme="https://blog.zisheng.pro/tags/CAPI/"/>
    <category term="转化追踪" scheme="https://blog.zisheng.pro/tags/%E8%BD%AC%E5%8C%96%E8%BF%BD%E8%B8%AA/"/>
    <category term="广告投放" scheme="https://blog.zisheng.pro/tags/%E5%B9%BF%E5%91%8A%E6%8A%95%E6%94%BE/"/>
    <content>
      <![CDATA[<p>我在帮飞猪做 Cloud GUI Agent 的同时，一直在用独立站项目练手 AI Infra 的落地。一个绕不开的问题是：<strong>广告数据怎么接</strong>？Google Analytics 之外，Facebook Pixel 几乎是独立站的标配。但大多数教程停留在”粘贴代码到 head 里”就完了，而真正让投放起效的细节——事件质量、CAPI 双打、iOS 14 之后的归因——这些从来没人说清楚。</p><p>这篇文章，我从前端工程师的角度把整条链路拆开来讲。</p><h2 id="一句话总结"><a href="#一句话总结" class="headerlink" title="一句话总结"></a>一句话总结</h2><p>Facebook Pixel &#x3D; 浏览器侧事件（Browser Events）+ 服务端转化 API（CAPI）双打，才能在隐私限制下保住数据质量，进而让算法有料可投。</p><hr><h2 id="一、Pixel-是什么，能做什么"><a href="#一、Pixel-是什么，能做什么" class="headerlink" title="一、Pixel 是什么，能做什么"></a>一、Pixel 是什么，能做什么</h2><p>Facebook Pixel（现在官方叫 <strong>Meta Pixel</strong>）是一段 JavaScript 代码，安装在你的网站后，负责把用户行为回传给 Meta，用于：</p><table><thead><tr><th>用途</th><th>说明</th></tr></thead><tbody><tr><td>转化追踪</td><td>记录购买、注册、加购等行为，衡量广告 ROI</td></tr><tr><td>再营销</td><td>对浏览过某页面但未购买的用户重新投放</td></tr><tr><td>相似受众</td><td>以转化用户为种子，扩展相似人群</td></tr><tr><td>广告优化</td><td>给算法信号，让 Campaign 朝高转化方向学习</td></tr></tbody></table><hr><h2 id="二、安装：最小可用配置"><a href="#二、安装：最小可用配置" class="headerlink" title="二、安装：最小可用配置"></a>二、安装：最小可用配置</h2><h3 id="2-1-获取-Pixel-ID"><a href="#2-1-获取-Pixel-ID" class="headerlink" title="2.1 获取 Pixel ID"></a>2.1 获取 Pixel ID</h3><p>登录 <a href="https://business.facebook.com/">Meta Business Manager</a> → <strong>Events Manager</strong> → 创建数据源 → 选 <strong>Web</strong> → 获得形如 <code>1234567890123456</code> 的 Pixel ID。</p><h3 id="2-2-基础代码"><a href="#2-2-基础代码" class="headerlink" title="2.2 基础代码"></a>2.2 基础代码</h3><p>把下面这段放进所有页面的 <code>&lt;head&gt;</code> 里：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- Meta Pixel Code --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">!<span class="keyword">function</span>(<span class="params">f,b,e,v,n,t,s</span>)</span></span><br><span class="line"><span class="language-javascript">&#123;<span class="keyword">if</span>(f.<span class="property">fbq</span>)<span class="keyword">return</span>;n=f.<span class="property">fbq</span>=<span class="keyword">function</span>(<span class="params"></span>)&#123;n.<span class="property">callMethod</span>?</span></span><br><span class="line"><span class="language-javascript">n.<span class="property">callMethod</span>.<span class="title function_">apply</span>(n,<span class="variable language_">arguments</span>):n.<span class="property">queue</span>.<span class="title function_">push</span>(<span class="variable language_">arguments</span>)&#125;;</span></span><br><span class="line"><span class="language-javascript"><span class="keyword">if</span>(!f.<span class="property">_fbq</span>)f.<span class="property">_fbq</span>=n;n.<span class="property">push</span>=n;n.<span class="property">loaded</span>=!<span class="number">0</span>;n.<span class="property">version</span>=<span class="string">&#x27;2.0&#x27;</span>;</span></span><br><span class="line"><span class="language-javascript">n.<span class="property">queue</span>=[];t=b.<span class="title function_">createElement</span>(e);t.<span class="property">async</span>=!<span class="number">0</span>;</span></span><br><span class="line"><span class="language-javascript">t.<span class="property">src</span>=v;s=b.<span class="title function_">getElementsByTagName</span>(e)[<span class="number">0</span>];</span></span><br><span class="line"><span class="language-javascript">s.<span class="property">parentNode</span>.<span class="title function_">insertBefore</span>(t,s)&#125;(<span class="variable language_">window</span>, <span class="variable language_">document</span>,<span class="string">&#x27;script&#x27;</span>,</span></span><br><span class="line"><span class="language-javascript"><span class="string">&#x27;https://connect.facebook.net/en_US/fbevents.js&#x27;</span>);</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript"><span class="title function_">fbq</span>(<span class="string">&#x27;init&#x27;</span>, <span class="string">&#x27;YOUR_PIXEL_ID&#x27;</span>);</span></span><br><span class="line"><span class="language-javascript"><span class="title function_">fbq</span>(<span class="string">&#x27;track&#x27;</span>, <span class="string">&#x27;PageView&#x27;</span>);</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">noscript</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">img</span> <span class="attr">height</span>=<span class="string">&quot;1&quot;</span> <span class="attr">width</span>=<span class="string">&quot;1&quot;</span> <span class="attr">style</span>=<span class="string">&quot;display:none&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">src</span>=<span class="string">&quot;https://www.facebook.com/tr?id=YOUR_PIXEL_ID&amp;ev=PageView&amp;noscript=1&quot;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">noscript</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- End Meta Pixel Code --&gt;</span></span><br></pre></td></tr></table></figure><p><strong><code>fbq(&#39;init&#39;, ID)</code></strong> 初始化并绑定用户标识，<strong><code>fbq(&#39;track&#39;, &#39;PageView&#39;)</code></strong> 发送第一个事件。</p><h3 id="2-3-在-React-Next-js-中接入"><a href="#2-3-在-React-Next-js-中接入" class="headerlink" title="2.3 在 React &#x2F; Next.js 中接入"></a>2.3 在 React &#x2F; Next.js 中接入</h3><p>以 Next.js App Router 为例：</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// app/layout.tsx</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Script</span> <span class="keyword">from</span> <span class="string">&#x27;next/script&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">PIXEL_ID</span> = process.<span class="property">env</span>.<span class="property">NEXT_PUBLIC_META_PIXEL_ID</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">RootLayout</span>(<span class="params">&#123; children &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">head</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Script</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">id</span>=<span class="string">&quot;meta-pixel&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">strategy</span>=<span class="string">&quot;afterInteractive&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">dangerouslySetInnerHTML</span>=<span class="string">&#123;&#123;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">__html:</span> `</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">              !<span class="attr">function</span>(<span class="attr">f</span>,<span class="attr">b</span>,<span class="attr">e</span>,<span class="attr">v</span>,<span class="attr">n</span>,<span class="attr">t</span>,<span class="attr">s</span>)&#123;<span class="attr">...</span>&#125;(<span class="attr">window</span>,<span class="attr">document</span>,&#x27;<span class="attr">script</span>&#x27;,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">              &#x27;<span class="attr">https:</span>//<span class="attr">connect.facebook.net</span>/<span class="attr">en_US</span>/<span class="attr">fbevents.js</span>&#x27;);</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">              <span class="attr">fbq</span>(&#x27;<span class="attr">init</span>&#x27;, &#x27;$&#123;<span class="attr">PIXEL_ID</span>&#125;&#x27;);</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">              <span class="attr">fbq</span>(&#x27;<span class="attr">track</span>&#x27;, &#x27;<span class="attr">PageView</span>&#x27;);</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            `,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          &#125;&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        /&gt;</span><span class="language-handlebars"><span class="language-xml"></span></span></span></span><br><span class="line"><span class="language-xml"><span class="language-handlebars"><span class="language-xml">      <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span></span></span></span><br><span class="line"><span class="language-xml"><span class="language-handlebars"><span class="language-xml">      <span class="tag">&lt;<span class="name">body</span>&gt;</span>&#123;children&#125;<span class="tag">&lt;/<span class="name">body</span>&gt;</span></span></span></span></span><br><span class="line"><span class="language-xml"><span class="language-handlebars"><span class="language-xml">    <span class="tag">&lt;/<span class="name">html</span>&gt;</span></span></span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p><code>strategy=&quot;afterInteractive&quot;</code> 让 Pixel 脚本不阻塞 LCP，是 Next.js 推荐方式。</p></blockquote><hr><h2 id="三、标准事件：必须掌握的-9-个"><a href="#三、标准事件：必须掌握的-9-个" class="headerlink" title="三、标准事件：必须掌握的 9 个"></a>三、标准事件：必须掌握的 9 个</h2><p>Meta 有一套预定义事件，算法对这些事件有专属优化模型，<strong>优先用标准事件，而不是自定义事件</strong>。</p><table><thead><tr><th>事件名</th><th>触发时机</th><th>代码</th></tr></thead><tbody><tr><td><code>PageView</code></td><td>每次页面加载</td><td>自动（init 后自动触发）</td></tr><tr><td><code>ViewContent</code></td><td>商品详情页</td><td><code>fbq(&#39;track&#39;, &#39;ViewContent&#39;, {...})</code></td></tr><tr><td><code>Search</code></td><td>搜索结果页</td><td><code>fbq(&#39;track&#39;, &#39;Search&#39;, {...})</code></td></tr><tr><td><code>AddToCart</code></td><td>加入购物车</td><td><code>fbq(&#39;track&#39;, &#39;AddToCart&#39;, {...})</code></td></tr><tr><td><code>AddToWishlist</code></td><td>收藏&#x2F;心愿单</td><td><code>fbq(&#39;track&#39;, &#39;AddToWishlist&#39;, {...})</code></td></tr><tr><td><code>InitiateCheckout</code></td><td>进入结算流程</td><td><code>fbq(&#39;track&#39;, &#39;InitiateCheckout&#39;, {...})</code></td></tr><tr><td><code>AddPaymentInfo</code></td><td>填写支付信息</td><td><code>fbq(&#39;track&#39;, &#39;AddPaymentInfo&#39;, {...})</code></td></tr><tr><td><code>Purchase</code></td><td>订单完成</td><td><code>fbq(&#39;track&#39;, &#39;Purchase&#39;, {...})</code></td></tr><tr><td><code>Lead</code></td><td>表单提交&#x2F;注册</td><td><code>fbq(&#39;track&#39;, &#39;Lead&#39;, {...})</code></td></tr></tbody></table><h3 id="Purchase-事件示例（最重要）"><a href="#Purchase-事件示例（最重要）" class="headerlink" title="Purchase 事件示例（最重要）"></a>Purchase 事件示例（最重要）</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">fbq</span>(<span class="string">&#x27;track&#x27;</span>, <span class="string">&#x27;Purchase&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">value</span>: <span class="number">99.00</span>,          <span class="comment">// 订单金额，必填</span></span><br><span class="line">  <span class="attr">currency</span>: <span class="string">&#x27;USD&#x27;</span>,       <span class="comment">// 货币代码，必填</span></span><br><span class="line">  <span class="attr">content_ids</span>: [<span class="string">&#x27;SKU_001&#x27;</span>, <span class="string">&#x27;SKU_002&#x27;</span>],  <span class="comment">// 商品 ID 数组</span></span><br><span class="line">  <span class="attr">content_type</span>: <span class="string">&#x27;product&#x27;</span>,</span><br><span class="line">  <span class="attr">num_items</span>: <span class="number">2</span>,</span><br><span class="line">  <span class="attr">order_id</span>: <span class="string">&#x27;ORD_20260704_001&#x27;</span>,  <span class="comment">// 去重用，强烈建议加</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="ViewContent-事件示例"><a href="#ViewContent-事件示例" class="headerlink" title="ViewContent 事件示例"></a>ViewContent 事件示例</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">fbq</span>(<span class="string">&#x27;track&#x27;</span>, <span class="string">&#x27;ViewContent&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">content_name</span>: <span class="string">&#x27;iPhone 16 Pro Case&#x27;</span>,</span><br><span class="line">  <span class="attr">content_ids</span>: [<span class="string">&#x27;CASE_IP16P_BLK&#x27;</span>],</span><br><span class="line">  <span class="attr">content_type</span>: <span class="string">&#x27;product&#x27;</span>,</span><br><span class="line">  <span class="attr">value</span>: <span class="number">19.90</span>,</span><br><span class="line">  <span class="attr">currency</span>: <span class="string">&#x27;USD&#x27;</span>,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><hr><h2 id="四、自定义事件：超出标准事件的场景"><a href="#四、自定义事件：超出标准事件的场景" class="headerlink" title="四、自定义事件：超出标准事件的场景"></a>四、自定义事件：超出标准事件的场景</h2><p>当标准事件满足不了需求时，用 <code>fbq(&#39;trackCustom&#39;, &#39;EventName&#39;, params)</code>：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 视频播放 25% 时</span></span><br><span class="line"><span class="title function_">fbq</span>(<span class="string">&#x27;trackCustom&#x27;</span>, <span class="string">&#x27;VideoWatch25&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">video_title</span>: <span class="string">&#x27;产品介绍视频&#x27;</span>,</span><br><span class="line">  <span class="attr">watch_percentage</span>: <span class="number">25</span>,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 用户点击了特定 CTA</span></span><br><span class="line"><span class="title function_">fbq</span>(<span class="string">&#x27;trackCustom&#x27;</span>, <span class="string">&#x27;ClickPricingButton&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">plan</span>: <span class="string">&#x27;pro&#x27;</span>,</span><br><span class="line">  <span class="attr">location</span>: <span class="string">&#x27;hero_section&#x27;</span>,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><blockquote><p>自定义事件无法用于 Campaign 层级的标准优化目标（如”Purchase”优化），只能用于自定义转化。</p></blockquote><hr><h2 id="五、用户数据匹配：提升命中率的关键"><a href="#五、用户数据匹配：提升命中率的关键" class="headerlink" title="五、用户数据匹配：提升命中率的关键"></a>五、用户数据匹配：提升命中率的关键</h2><p>Pixel 默认靠 Cookie 识别用户，但可以传入哈希后的用户数据提升匹配率（<strong>Advanced Matching</strong>）：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">fbq</span>(<span class="string">&#x27;init&#x27;</span>, <span class="string">&#x27;YOUR_PIXEL_ID&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">em</span>: <span class="title function_">sha256</span>(user.<span class="property">email</span>),         <span class="comment">// 邮件（SHA-256）</span></span><br><span class="line">  <span class="attr">ph</span>: <span class="title function_">sha256</span>(user.<span class="property">phone</span>),         <span class="comment">// 电话（SHA-256）</span></span><br><span class="line">  <span class="attr">fn</span>: <span class="title function_">sha256</span>(user.<span class="property">firstName</span>.<span class="title function_">toLowerCase</span>()),</span><br><span class="line">  <span class="attr">ln</span>: <span class="title function_">sha256</span>(user.<span class="property">lastName</span>.<span class="title function_">toLowerCase</span>()),</span><br><span class="line">  <span class="attr">ct</span>: <span class="title function_">sha256</span>(user.<span class="property">city</span>.<span class="title function_">toLowerCase</span>()),</span><br><span class="line">  <span class="attr">st</span>: <span class="title function_">sha256</span>(user.<span class="property">state</span>),         <span class="comment">// 两字母缩写，美国适用</span></span><br><span class="line">  <span class="attr">zp</span>: <span class="title function_">sha256</span>(user.<span class="property">zipCode</span>),</span><br><span class="line">  <span class="attr">country</span>: <span class="title function_">sha256</span>(<span class="string">&#x27;us&#x27;</span>),</span><br><span class="line">  <span class="attr">external_id</span>: <span class="title function_">sha256</span>(user.<span class="property">userId</span>),  <span class="comment">// 自有用户 ID，最重要</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p><strong>规范要求</strong>：所有字段必须先 SHA-256 哈希、lowercase、trim 后再传入。Meta 在服务端对比自己的数据库，不存储原始 PII。</p><p>简易哈希工具（浏览器端）：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">sha256</span>(<span class="params">message</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> msgBuffer = <span class="keyword">new</span> <span class="title class_">TextEncoder</span>().<span class="title function_">encode</span>(message.<span class="title function_">trim</span>().<span class="title function_">toLowerCase</span>());</span><br><span class="line">  <span class="keyword">const</span> hashBuffer = <span class="keyword">await</span> crypto.<span class="property">subtle</span>.<span class="title function_">digest</span>(<span class="string">&#x27;SHA-256&#x27;</span>, msgBuffer);</span><br><span class="line">  <span class="keyword">const</span> hashArray = <span class="title class_">Array</span>.<span class="title function_">from</span>(<span class="keyword">new</span> <span class="title class_">Uint8Array</span>(hashBuffer));</span><br><span class="line">  <span class="keyword">return</span> hashArray.<span class="title function_">map</span>(<span class="function"><span class="params">b</span> =&gt;</span> b.<span class="title function_">toString</span>(<span class="number">16</span>).<span class="title function_">padStart</span>(<span class="number">2</span>, <span class="string">&#x27;0&#x27;</span>)).<span class="title function_">join</span>(<span class="string">&#x27;&#x27;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="六、Conversions-API（CAPI）：iOS-14-之后的救命稻草"><a href="#六、Conversions-API（CAPI）：iOS-14-之后的救命稻草" class="headerlink" title="六、Conversions API（CAPI）：iOS 14 之后的救命稻草"></a>六、Conversions API（CAPI）：iOS 14 之后的救命稻草</h2><h3 id="为什么需要-CAPI"><a href="#为什么需要-CAPI" class="headerlink" title="为什么需要 CAPI"></a>为什么需要 CAPI</h3><p>iOS 14.5+ 的 ATT 框架、Safari ITP、Chrome 第三方 Cookie 限制，导致<strong>浏览器侧 Pixel 信号大量丢失</strong>，典型症状：</p><ul><li>Events Manager 里 Purchase 事件数比实际订单少 30-60%</li><li>归因窗口缩短，ROAS 虚低</li><li>再营销受众缩水</li></ul><p>CAPI 是<strong>服务端直接调用 Meta Graph API</strong> 发送事件，绕过浏览器限制：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">用户行为 → 你的服务器 → Meta Graph API（CAPI）</span><br><span class="line">         ↘ 浏览器 Pixel（Browser Events）</span><br></pre></td></tr></table></figure><p>两路数据 Meta 会自动去重，合并后的信号质量远优于单路。</p><h3 id="CAPI-接入（Node-js-示例）"><a href="#CAPI-接入（Node-js-示例）" class="headerlink" title="CAPI 接入（Node.js 示例）"></a>CAPI 接入（Node.js 示例）</h3><p>安装官方 SDK：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install facebook-nodejs-business-sdk</span><br></pre></td></tr></table></figure><p>在订单完成时，从服务端发送 Purchase 事件：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">FacebookAdsApi</span>, <span class="title class_">ServerSideApi</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;facebook-nodejs-business-sdk&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> bizSdk = <span class="built_in">require</span>(<span class="string">&#x27;facebook-nodejs-business-sdk&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> &#123; <span class="title class_">EventRequest</span>, <span class="title class_">UserData</span>, <span class="title class_">CustomData</span>, <span class="title class_">ServerEvent</span> &#125; = bizSdk;</span><br><span class="line"></span><br><span class="line"><span class="title class_">FacebookAdsApi</span>.<span class="title function_">init</span>(process.<span class="property">env</span>.<span class="property">META_ACCESS_TOKEN</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">sendPurchaseEvent</span>(<span class="params">order, req</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> userData = <span class="keyword">new</span> <span class="title class_">UserData</span>()</span><br><span class="line">    .<span class="title function_">setEmails</span>([<span class="keyword">await</span> <span class="title function_">sha256</span>(order.<span class="property">email</span>)])</span><br><span class="line">    .<span class="title function_">setPhones</span>([<span class="keyword">await</span> <span class="title function_">sha256</span>(order.<span class="property">phone</span>)])</span><br><span class="line">    .<span class="title function_">setExternalId</span>(<span class="keyword">await</span> <span class="title function_">sha256</span>(order.<span class="property">userId</span>))</span><br><span class="line">    .<span class="title function_">setClientIpAddress</span>(req.<span class="property">ip</span>)</span><br><span class="line">    .<span class="title function_">setClientUserAgent</span>(req.<span class="property">headers</span>[<span class="string">&#x27;user-agent&#x27;</span>])</span><br><span class="line">    .<span class="title function_">setFbc</span>(req.<span class="property">cookies</span>.<span class="property">_fbc</span>)   <span class="comment">// Facebook Click ID Cookie</span></span><br><span class="line">    .<span class="title function_">setFbp</span>(req.<span class="property">cookies</span>.<span class="property">_fbp</span>);  <span class="comment">// Facebook Browser ID Cookie</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> customData = <span class="keyword">new</span> <span class="title class_">CustomData</span>()</span><br><span class="line">    .<span class="title function_">setValue</span>(order.<span class="property">totalAmount</span>)</span><br><span class="line">    .<span class="title function_">setCurrency</span>(<span class="string">&#x27;USD&#x27;</span>)</span><br><span class="line">    .<span class="title function_">setContentIds</span>(order.<span class="property">items</span>.<span class="title function_">map</span>(<span class="function"><span class="params">i</span> =&gt;</span> i.<span class="property">sku</span>))</span><br><span class="line">    .<span class="title function_">setContentType</span>(<span class="string">&#x27;product&#x27;</span>)</span><br><span class="line">    .<span class="title function_">setOrderId</span>(order.<span class="property">id</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> serverEvent = <span class="keyword">new</span> <span class="title class_">ServerEvent</span>()</span><br><span class="line">    .<span class="title function_">setEventName</span>(<span class="string">&#x27;Purchase&#x27;</span>)</span><br><span class="line">    .<span class="title function_">setEventTime</span>(<span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title class_">Date</span>.<span class="title function_">now</span>() / <span class="number">1000</span>))</span><br><span class="line">    .<span class="title function_">setUserData</span>(userData)</span><br><span class="line">    .<span class="title function_">setCustomData</span>(customData)</span><br><span class="line">    .<span class="title function_">setEventSourceUrl</span>(order.<span class="property">pageUrl</span>)</span><br><span class="line">    .<span class="title function_">setActionSource</span>(<span class="string">&#x27;website&#x27;</span>)</span><br><span class="line">    .<span class="title function_">setEventId</span>(<span class="string">`purchase_<span class="subst">$&#123;order.id&#125;</span>`</span>); <span class="comment">// 去重 ID，与浏览器事件一致</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> eventRequest = <span class="keyword">new</span> <span class="title class_">EventRequest</span>(process.<span class="property">env</span>.<span class="property">META_PIXEL_ID</span>)</span><br><span class="line">    .<span class="title function_">setEvents</span>([serverEvent]);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> eventRequest.<span class="title function_">execute</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="关键字段说明"><a href="#关键字段说明" class="headerlink" title="关键字段说明"></a>关键字段说明</h3><table><thead><tr><th>字段</th><th>来源</th><th>作用</th></tr></thead><tbody><tr><td><code>_fbc</code></td><td>Cookie（<code>fbclid</code> 参数写入）</td><td>追踪来自 Facebook 广告的点击</td></tr><tr><td><code>_fbp</code></td><td>Cookie（Pixel 自动写入）</td><td>追踪浏览器会话</td></tr><tr><td><code>event_id</code></td><td>自定义（保持唯一）</td><td><strong>浏览器与 CAPI 去重的核心字段</strong></td></tr><tr><td><code>action_source</code></td><td>固定传 <code>&#39;website&#39;</code></td><td>告知 Meta 事件来源</td></tr><tr><td><code>client_ip_address</code></td><td>从请求头获取</td><td>提升匹配率</td></tr></tbody></table><hr><h2 id="七、事件去重：防止数据虚报"><a href="#七、事件去重：防止数据虚报" class="headerlink" title="七、事件去重：防止数据虚报"></a>七、事件去重：防止数据虚报</h2><p>当浏览器 Pixel 和 CAPI 同时发出同一事件，Meta 用 <code>event_id</code> + <code>event_name</code> 组合去重。<strong>两路必须传同一个 <code>event_id</code></strong>：</p><p>浏览器端：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> eventId = <span class="string">`purchase_<span class="subst">$&#123;orderId&#125;</span>_<span class="subst">$&#123;<span class="built_in">Date</span>.now()&#125;</span>`</span>;</span><br><span class="line"></span><br><span class="line"><span class="title function_">fbq</span>(<span class="string">&#x27;track&#x27;</span>, <span class="string">&#x27;Purchase&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">value</span>: <span class="number">99.00</span>,</span><br><span class="line">  <span class="attr">currency</span>: <span class="string">&#x27;USD&#x27;</span>,</span><br><span class="line">  <span class="attr">order_id</span>: orderId,</span><br><span class="line">&#125;, &#123;</span><br><span class="line">  <span class="attr">eventID</span>: eventId,  <span class="comment">// 注意是 eventID，大写 D</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>服务端 CAPI：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">serverEvent.<span class="title function_">setEventId</span>(eventId);  <span class="comment">// 与浏览器端完全一致</span></span><br></pre></td></tr></table></figure><hr><h2 id="八、事件质量评分（Event-Match-Quality）"><a href="#八、事件质量评分（Event-Match-Quality）" class="headerlink" title="八、事件质量评分（Event Match Quality）"></a>八、事件质量评分（Event Match Quality）</h2><p>Events Manager 里每个事件都有一个 <strong>EMQ 分数（0-10）</strong>，直接影响广告投放效果：</p><table><thead><tr><th>分数区间</th><th>判断</th><th>优化方向</th></tr></thead><tbody><tr><td>7-10</td><td>优秀</td><td>保持</td></tr><tr><td>4-6</td><td>一般</td><td>补充更多用户标识字段</td></tr><tr><td>0-3</td><td>差</td><td>检查是否有 external_id &#x2F; email 传入</td></tr></tbody></table><p>提分策略：</p><ol><li><strong>传 <code>external_id</code></strong>（登录用户的 userId 哈希）是最有效的单一操作</li><li>同时传 email + phone</li><li>接入 CAPI 补充 IP、UA、<code>_fbc</code>&#x2F;<code>_fbp</code></li><li>确保事件在正确时机触发（购买完成页，而非提交按钮点击时）</li></ol><hr><h2 id="九、聚合事件衡量（AEM）：iOS-14-之后必配"><a href="#九、聚合事件衡量（AEM）：iOS-14-之后必配" class="headerlink" title="九、聚合事件衡量（AEM）：iOS 14 之后必配"></a>九、聚合事件衡量（AEM）：iOS 14 之后必配</h2><p>iOS 14 之后，针对 iOS 用户的广告归因受 ATT 限制，必须在 <strong>Events Manager → 聚合事件衡量</strong> 里配置优先级：</p><ul><li>最多 <strong>8 个事件</strong>，按转化漏斗从低到高排优先级</li><li>推荐顺序：<code>Purchase</code> &gt; <code>InitiateCheckout</code> &gt; <code>AddToCart</code> &gt; <code>ViewContent</code> &gt; <code>PageView</code></li><li>没有配置 AEM 的事件，iOS 数据几乎为零</li></ul><hr><h2 id="十、与-GA4-协同：数据互补而非替代"><a href="#十、与-GA4-协同：数据互补而非替代" class="headerlink" title="十、与 GA4 协同：数据互补而非替代"></a>十、与 GA4 协同：数据互补而非替代</h2><table><thead><tr><th>维度</th><th>Meta Pixel</th><th>GA4</th></tr></thead><tbody><tr><td>归因模型</td><td>Meta 内部（助攻算法）</td><td>数据驱动 &#x2F; 线性</td></tr><tr><td>用户识别</td><td><code>_fbp</code> &#x2F; <code>external_id</code></td><td><code>client_id</code> &#x2F; <code>user_id</code></td></tr><tr><td>跨设备</td><td>依赖 Meta 账号登录</td><td>依赖 GA4 User-ID</td></tr><tr><td>适合看什么</td><td>广告 ROI、受众效果</td><td>全站行为漏斗、留存</td></tr></tbody></table><p>两套数据天然不会对齐，<strong>不要试图让两者数字一致</strong>，应该：</p><ul><li>用 Meta Pixel 判断广告素材&#x2F;受众效果</li><li>用 GA4 看站内用户路径和行为质量</li><li>订单数据以自己数据库为准（Source of Truth）</li></ul><hr><h2 id="十一、调试工具"><a href="#十一、调试工具" class="headerlink" title="十一、调试工具"></a>十一、调试工具</h2><table><thead><tr><th>工具</th><th>用途</th></tr></thead><tbody><tr><td><strong>Meta Pixel Helper</strong>（Chrome 插件）</td><td>实时查看页面触发了哪些事件，是否有错误</td></tr><tr><td><strong>Events Manager → Test Events</strong></td><td>输入网址，实时验证服务端 CAPI 数据</td></tr><tr><td><strong>Events Manager → Diagnostics</strong></td><td>查看事件错误、去重情况</td></tr><tr><td><strong>Graph API Explorer</strong></td><td>手动发测试 CAPI 请求</td></tr></tbody></table><p>用 Pixel Helper 检查时，常见问题：</p><ul><li><code>Pixel ID mismatch</code>：页面上多个 Pixel ID 冲突</li><li><code>No events detected</code>：脚本未正确加载（广告拦截器、CSP 限制）</li><li><code>Duplicate pixel</code>：同一事件触发了两次（SPA 路由切换时 <code>PageView</code> 重复）</li></ul><hr><h2 id="十二、隐私合规：不能忽略的法律红线"><a href="#十二、隐私合规：不能忽略的法律红线" class="headerlink" title="十二、隐私合规：不能忽略的法律红线"></a>十二、隐私合规：不能忽略的法律红线</h2><table><thead><tr><th>地区</th><th>法规</th><th>要点</th></tr></thead><tbody><tr><td>欧盟&#x2F;英国</td><td>GDPR</td><td>必须获得明确同意后才能加载 Pixel</td></tr><tr><td>加州</td><td>CCPA</td><td>提供 “Do Not Sell” 选项</td></tr><tr><td>中国大陆</td><td>个人信息保护法</td><td>境外传输需单独授权</td></tr></tbody></table><p>实现方式：接入 CMP（Consent Management Platform，如 Cookiebot、OneTrust），在用户同意后动态加载 Pixel：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 用户同意后再初始化</span></span><br><span class="line"><span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;CookieConsentGiven&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">fbq</span>(<span class="string">&#x27;init&#x27;</span>, <span class="variable constant_">PIXEL_ID</span>);</span><br><span class="line">  <span class="title function_">fbq</span>(<span class="string">&#x27;track&#x27;</span>, <span class="string">&#x27;PageView&#x27;</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>CAPI 在服务端运行，理论上不受客户端 Cookie 限制，但数据回传本身也需要有合法依据（合同履行、合法利益或明确同意）。</p><hr><h2 id="十三、内容站套利：优化事件才是最大杠杆"><a href="#十三、内容站套利：优化事件才是最大杠杆" class="headerlink" title="十三、内容站套利：优化事件才是最大杠杆"></a>十三、内容站套利：优化事件才是最大杠杆</h2><p>前面十二节的案例几乎都围绕电商（Purchase、AddToCart）。但如果你跑的是<strong>内容站 + AdSense&#x2F;AdX 广告套利模型</strong>——比如小说站、文章站——没有 Purchase 事件，优化目标怎么选？这是最容易踩坑的一环。</p><h3 id="套利的利润公式"><a href="#套利的利润公式" class="headerlink" title="套利的利润公式"></a>套利的利润公式</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">每次点击利润 = (平均每次会话 pageviews × RPM / 1000) − Facebook CPC</span><br></pre></td></tr></table></figure><p>比如 RPM $8、用户平均浏览 4 页（4 章）：</p><ul><li>每次会话广告收入 &#x3D; 4 × 8&#x2F;1000 &#x3D; $0.032 × 4 &#x3D; <strong>$0.128</strong></li><li>目标 CPC &lt; $0.10，才有稳定利润空间</li></ul><p>这个模型里，广告侧唯一能控制的变量是 <strong>CPC</strong> 和 <strong>受众质量</strong>（高质量受众 &#x3D; 每次会话浏览更多页）。优化事件的选择直接决定算法能不能找到”真实读者”。</p><h3 id="优化事件信号质量从低到高"><a href="#优化事件信号质量从低到高" class="headerlink" title="优化事件信号质量从低到高"></a>优化事件信号质量从低到高</h3><table><thead><tr><th>事件</th><th>捕捉的是谁</th><th>信号质量</th><th>可用时机</th></tr></thead><tbody><tr><td><code>PageView</code></td><td>任何点进来的人，包括 3 秒就走的</td><td>最差</td><td>立刻可用，但不建议</td></tr><tr><td><code>ViewContent</code>（页面加载时触发）</td><td>点进来且页面加载完成的</td><td>中</td><td>积累 500+ 后可用</td></tr><tr><td><code>ViewContent</code>（滚动 50% 触发）</td><td>真正读了半章的人</td><td>最好</td><td>积累 500+ 后可用</td></tr></tbody></table><p><strong>为什么不能用 <code>PageView</code> 做优化目标：</strong> <code>PageView</code> 在落地页加载完就触发，连内容还没看，可能就是广告点击后立刻返回的人。把这个信号喂给算法，等于教它”帮我找更多会点广告但不读文章的人”——会话深度低，每次会话 pageviews 低，套利失败。</p><h3 id="内容站的三段-Campaign-路线"><a href="#内容站的三段-Campaign-路线" class="headerlink" title="内容站的三段 Campaign 路线"></a>内容站的三段 Campaign 路线</h3><p><strong>第一阶段：冷启动（0–499 ViewContent 事件）</strong></p><p>Campaign 目标选 <strong>Traffic</strong>，优化目标选 <strong>Landing Page Views</strong>（不是 Link Clicks）。Landing Page Views 需要页面真正加载完成，比 Link Clicks 多过滤一层跳出。</p><p><strong>第二阶段：信号足够后切换（500+ ViewContent 事件）</strong></p><p>把 Campaign 目标切成 <strong>Engagement</strong> 或 <strong>Conversions</strong>，优化目标选 <code>ViewContent</code>。这时算法已经有足够样本，会去找”打开章节页”的人，而不只是”点广告”的人。</p><p>注意：<code>ViewContent</code> 触发越晚（比如滚动到 50% 才触发），信号质量越高，但积累速度越慢。可以先用”页面加载时触发”过渡到 500 个，再换成”滚动 50% 触发”重新积累相似受众。</p><p><strong>第三阶段：Lookalike 扩量（Custom Audience 500+ 人）</strong></p><p>以”触发过 ViewContent 的访客”为种子，建 1% Lookalike。这批人统计上和你最好的读者最像，CPM 可能比兴趣定向高，但 ROAS 通常高 2–4 倍。</p><h3 id="在-Next-js-章节页接入-scroll-50-ViewContent"><a href="#在-Next-js-章节页接入-scroll-50-ViewContent" class="headerlink" title="在 Next.js 章节页接入 scroll-50% ViewContent"></a>在 Next.js 章节页接入 scroll-50% ViewContent</h3><p>在章节页的 Client Component 里：</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&#x27;use client&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; useEffect &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">ChapterScrollPixel</span>(<span class="params">&#123; chapterTitle &#125;: &#123; chapterTitle: <span class="built_in">string</span> &#125;</span>) &#123;</span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> fired = <span class="literal">false</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">onScroll</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">      <span class="keyword">const</span> scrolled =</span><br><span class="line">        <span class="variable language_">window</span>.<span class="property">scrollY</span> / (<span class="variable language_">document</span>.<span class="property">body</span>.<span class="property">scrollHeight</span> - <span class="variable language_">window</span>.<span class="property">innerHeight</span>)</span><br><span class="line">      <span class="keyword">if</span> (!fired &amp;&amp; scrolled &gt;= <span class="number">0.5</span>) &#123;</span><br><span class="line">        fired = <span class="literal">true</span></span><br><span class="line">        <span class="variable language_">window</span>.<span class="property">fbq</span>?.(<span class="string">&#x27;track&#x27;</span>, <span class="string">&#x27;ViewContent&#x27;</span>, &#123;</span><br><span class="line">          <span class="attr">content_type</span>: <span class="string">&#x27;chapter&#x27;</span>,</span><br><span class="line">          <span class="attr">content_name</span>: chapterTitle,</span><br><span class="line">        &#125;)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;scroll&#x27;</span>, onScroll, &#123; <span class="attr">passive</span>: <span class="literal">true</span> &#125;)</span><br><span class="line">    <span class="keyword">return</span> <span class="function">() =&gt;</span> <span class="variable language_">window</span>.<span class="title function_">removeEventListener</span>(<span class="string">&#x27;scroll&#x27;</span>, onScroll)</span><br><span class="line">  &#125;, [chapterTitle])</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="literal">null</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在章节页 Server Component 里引用它：</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// app/book/[slug]/chapter/[ch]/page.tsx</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">ChapterScrollPixel</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@/components/ChapterScrollPixel&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">ChapterPage</span>(<span class="params">&#123; params &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> chapter = <span class="title function_">getChapter</span>(params.<span class="property">slug</span>, params.<span class="property">ch</span>)</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">ChapterScrollPixel</span> <span class="attr">chapterTitle</span>=<span class="string">&#123;chapter.title&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;/* 章节内容 */&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>用标准 <code>ViewContent</code> 事件名而不是自定义事件，是因为 Conversion 目标可以直接用它优化——用 <code>trackCustom</code> 就只能配成”自定义转化”，多一步且算法支持的优化模型更少。</p><h3 id="与电商-Pixel-的核心区别"><a href="#与电商-Pixel-的核心区别" class="headerlink" title="与电商 Pixel 的核心区别"></a>与电商 Pixel 的核心区别</h3><p>电商的转化漏斗有明确终点（Purchase），内容站没有。内容站的”转化”是<strong>会话深度</strong>：读者在站内翻了多少页，就决定了广告收入多少。因此：</p><ul><li>电商：把 Purchase 信号传得越准越好</li><li>内容站：把”真正读内容”的信号传得越准越好，<code>ViewContent</code> at scroll-50% 是当前最接近的近似</li></ul><hr><h2 id="要不要用-我的判断框架"><a href="#要不要用-我的判断框架" class="headerlink" title="要不要用 &#x2F; 我的判断框架"></a>要不要用 &#x2F; 我的判断框架</h2><p><strong>值得现在上</strong>：</p><ul><li>你在跑 Facebook &#x2F; Instagram 广告，或计划跑</li><li>独立站有真实交易，需要优化 Purchase 转化</li><li>想做再营销或相似受众扩量</li></ul><p><strong>先把基础做好再说</strong>：</p><ul><li>网站月 UV 不足 1 万，广告预算不足 $1000&#x2F;月——此时数据量太少，算法学不动</li><li>没有服务端能力接 CAPI——只装浏览器 Pixel 信号损失严重，建议用 Shopify&#x2F;WordPress 插件替代手写</li></ul><p><strong>重点关注</strong>：</p><ul><li>iOS 14+ AEM 配置是必选项，不是可选项</li><li><code>event_id</code> 去重是 CAPI 双打的基础，漏掉就会数据虚报</li><li>EMQ 分数低于 5 分，先补 <code>external_id</code> 和 email 再说其他优化</li></ul><hr><blockquote><p>我目前在 fwork 项目里做 AI Infra，Pixel 是独立站产品的基础设施之一。这篇文章把我踩过的坑和查过的文档整理成了一条可执行的链路——希望对同样在「前端转 Agent&#x2F;全栈」路上的你有用。</p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/36251c389fef/</id>
    <link href="https://blog.zisheng.pro/posts/36251c389fef/"/>
    <published>2026-07-04T12:00:00.000Z</published>
    <summary>本文从前端视角系统拆解 Facebook Pixel：从基础安装、标准事件到自定义事件、转化 API（CAPI）、再到与 GA4 协同、iOS 14+ 隐私限制应对、广告归因模型等进阶内容，覆盖独立站运营者和前端开发者在投放 Facebook 广告时必须掌握的全部技术细节，帮助你真正&quot;看懂数据、投好广告&quot;。</summary>
    <title>Facebook Pixel 从入门到精通</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="营销" scheme="https://blog.zisheng.pro/categories/%E8%90%A5%E9%94%80/"/>
    <category term="Facebook Pixel" scheme="https://blog.zisheng.pro/tags/Facebook-Pixel/"/>
    <category term="广告投放" scheme="https://blog.zisheng.pro/tags/%E5%B9%BF%E5%91%8A%E6%8A%95%E6%94%BE/"/>
    <category term="Meta Ads" scheme="https://blog.zisheng.pro/tags/Meta-Ads/"/>
    <category term="Conversions API" scheme="https://blog.zisheng.pro/tags/Conversions-API/"/>
    <category term="Events Manager" scheme="https://blog.zisheng.pro/tags/Events-Manager/"/>
    <category term="数字营销" scheme="https://blog.zisheng.pro/tags/%E6%95%B0%E5%AD%97%E8%90%A5%E9%94%80/"/>
    <content>
      <![CDATA[<p>我在做小说 H5 站的 Facebook 投流时，踩了一个坑：Pixel 装了，PageView 和 ViewContent 也在触发，但广告优化目标一直跑不起来，CPM 高、CTR 低，算法像是在盲投。</p><p>排查了两天，发现根本问题是：<strong>我的关键行为事件没有喂给 Meta</strong>。读者翻了多少章、停留多久、是否完成了阅读——这些数据 Meta 完全不知道，自然没法找到”高价值读者”来投。</p><p>这让我认真研究了一遍 Events Manager。以下是我拆解后的完整认知。</p><h2 id="一句话总结"><a href="#一句话总结" class="headerlink" title="一句话总结"></a>一句话总结</h2><p>Events Manager 是 Meta 广告的”数据大脑”——<strong>你告诉它用户做了什么，它帮你找更多做同样事情的人。</strong></p><p>数据质量越高，算法学得越精准，广告效率越高。反过来，数据残缺或质量差，钱就烧在错误的人身上。</p><h2 id="Events-Manager-是什么"><a href="#Events-Manager-是什么" class="headerlink" title="Events Manager 是什么"></a>Events Manager 是什么</h2><p>Meta Events Manager（事件管理工具）是 Meta Business Suite 下的一个模块，入口在 <code>business.facebook.com/events_manager</code>。</p><p>它做三件事：</p><ol><li><strong>管理数据源</strong>：Pixel（网站追踪代码）和 Conversions API（服务端追踪接口）</li><li><strong>监控事件质量</strong>：帮你发现事件丢失、重复、匹配质量低等问题</li><li><strong>配置自定义转化</strong>：把原始事件组合成广告优化目标</li></ol><p>你在这里配置好事件，投放时就能选”以 ViewContent 优化”或”以 Purchase 优化”——Meta 算法会去找最可能完成该行为的用户。</p><h2 id="两条数据通路：Pixel-vs-Conversions-API"><a href="#两条数据通路：Pixel-vs-Conversions-API" class="headerlink" title="两条数据通路：Pixel vs Conversions API"></a>两条数据通路：Pixel vs Conversions API</h2><p>这是 Events Manager 最核心的架构概念。</p><h3 id="Pixel（浏览器端）"><a href="#Pixel（浏览器端）" class="headerlink" title="Pixel（浏览器端）"></a>Pixel（浏览器端）</h3><p>Pixel 是一段 JS 代码，运行在<strong>用户浏览器里</strong>。用户访问页面时，浏览器向 Meta 服务器发送事件请求。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">fbq</span>(<span class="string">&#x27;track&#x27;</span>, <span class="string">&#x27;ViewContent&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">content_type</span>: <span class="string">&#x27;article&#x27;</span>,</span><br><span class="line">  <span class="attr">content_ids</span>: [<span class="string">&#x27;chapter-5&#x27;</span>],</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p><strong>优势</strong>：部署简单，实时触发，能捕捉页面行为。</p><p><strong>劣势</strong>：</p><ul><li>被广告拦截器屏蔽（iOS Safari、uBlock Origin 等）</li><li>iOS 14+ ATT 隐私限制，匹配率下降</li><li>浏览器关闭后事件丢失</li></ul><h3 id="Conversions-API（CAPI，服务端）"><a href="#Conversions-API（CAPI，服务端）" class="headerlink" title="Conversions API（CAPI，服务端）"></a>Conversions API（CAPI，服务端）</h3><p>CAPI 是从你的<strong>服务器直接向 Meta API 发送事件</strong>，绕过浏览器限制。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 服务端（Node.js 示例）</span></span><br><span class="line"><span class="keyword">const</span> serverEvent = <span class="keyword">new</span> bizSdk.<span class="title class_">ServerEvent</span>()</span><br><span class="line">  .<span class="title function_">setEventName</span>(<span class="string">&#x27;Purchase&#x27;</span>)</span><br><span class="line">  .<span class="title function_">setEventTime</span>(<span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title class_">Date</span>.<span class="title function_">now</span>() / <span class="number">1000</span>))</span><br><span class="line">  .<span class="title function_">setUserData</span>(userData)</span><br><span class="line">  .<span class="title function_">setCustomData</span>(customData);</span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">EventRequest</span>(<span class="variable constant_">PIXEL_ID</span>, [serverEvent]).<span class="title function_">execute</span>();</span><br></pre></td></tr></table></figure><p><strong>优势</strong>：不受广告拦截、iOS 限制影响，数据更完整可靠。</p><p><strong>劣势</strong>：需要后端开发；用户隐私数据需要哈希处理后发送。</p><h3 id="最佳实践：两者并用-去重"><a href="#最佳实践：两者并用-去重" class="headerlink" title="最佳实践：两者并用 + 去重"></a>最佳实践：两者并用 + 去重</h3><p>Meta 官方建议 <strong>Pixel + CAPI 同时部署</strong>，同一个事件从两条通路都发送：</p><ul><li>广告拦截器屏蔽了 Pixel？CAPI 兜底</li><li>服务端延迟高？Pixel 先到</li></ul><p>但两条通路都发，意味着同一个事件 Meta 会收到两次，必须加 <code>event_id</code> 去重：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Pixel 端</span></span><br><span class="line"><span class="title function_">fbq</span>(<span class="string">&#x27;track&#x27;</span>, <span class="string">&#x27;ViewContent&#x27;</span>, eventData, &#123; <span class="attr">eventID</span>: <span class="string">&#x27;chapter-5-view-1720051200&#x27;</span> &#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// CAPI 端（传同一个 event_id）</span></span><br><span class="line">serverEvent.<span class="title function_">setEventId</span>(<span class="string">&#x27;chapter-5-view-1720051200&#x27;</span>);</span><br></pre></td></tr></table></figure><p>Meta 看到同一 <code>event_id</code> 的两条记录，只计一次。</p><h2 id="标准事件-vs-自定义转化"><a href="#标准事件-vs-自定义转化" class="headerlink" title="标准事件 vs 自定义转化"></a>标准事件 vs 自定义转化</h2><h3 id="标准事件（Standard-Events）"><a href="#标准事件（Standard-Events）" class="headerlink" title="标准事件（Standard Events）"></a>标准事件（Standard Events）</h3><p>Meta 预定义的 17 个事件，算法天然理解其含义：</p><table><thead><tr><th>事件名</th><th>典型场景</th></tr></thead><tbody><tr><td><code>PageView</code></td><td>任何页面访问</td></tr><tr><td><code>ViewContent</code></td><td>查看具体内容（文章、商品详情页）</td></tr><tr><td><code>Search</code></td><td>搜索</td></tr><tr><td><code>AddToCart</code></td><td>加购</td></tr><tr><td><code>InitiateCheckout</code></td><td>开始结算</td></tr><tr><td><code>Purchase</code></td><td>完成购买</td></tr><tr><td><code>Lead</code></td><td>提交表单 &#x2F; 注册</td></tr><tr><td><code>Subscribe</code></td><td>订阅</td></tr><tr><td><code>CompleteRegistration</code></td><td>完成注册</td></tr></tbody></table><p>用标准事件的好处：<strong>Meta 算法直接懂你在优化什么</strong>，不需要额外学习。</p><p>对我的小说站，我这样映射：</p><ul><li><code>PageView</code> — 所有页面</li><li><code>ViewContent</code> — 每个章节页</li><li><code>Lead</code> — 模拟”深度阅读”（翻了 3 章以上触发一次）</li></ul><h3 id="自定义转化（Custom-Conversions）"><a href="#自定义转化（Custom-Conversions）" class="headerlink" title="自定义转化（Custom Conversions）"></a>自定义转化（Custom Conversions）</h3><p>当标准事件粒度不够时，自定义转化可以组合条件。</p><p><strong>示例</strong>：想优化”读完第 5 章的用户”</p><p>在 Events Manager → Custom Conversions 里配置：</p><ul><li>事件：<code>ViewContent</code></li><li>条件：URL 包含 <code>/chapters/ch-005</code></li></ul><p>这样 Meta 会专门找”最可能读到第 5 章”的用户来投放，而不是泛泛地找”会浏览内容的人”。</p><p>自定义转化的本质是<strong>在标准事件上加筛选漏斗</strong>，让算法优化目标更精准。</p><h2 id="Event-Match-Quality：决定你的钱烧得值不值"><a href="#Event-Match-Quality：决定你的钱烧得值不值" class="headerlink" title="Event Match Quality：决定你的钱烧得值不值"></a>Event Match Quality：决定你的钱烧得值不值</h2><p>EMQ（事件匹配质量）是 Events Manager 里最容易忽视但最重要的指标，1–10 分制。</p><p><strong>它衡量什么</strong>：Meta 能把多少比例的事件匹配回真实用户账号。</p><table><thead><tr><th>EMQ 分段</th><th>含义</th><th>广告效果</th></tr></thead><tbody><tr><td>8–10</td><td>优秀</td><td>算法学习精准，广告 ROI 高</td></tr><tr><td>5–7</td><td>一般</td><td>约 30–50% 事件无法归因</td></tr><tr><td>1–4</td><td>差</td><td>算法几乎在盲投</td></tr></tbody></table><p><strong>提高 EMQ 的核心手段</strong>：发送更多用户标识符（都需要 SHA-256 哈希后传）：</p><table><thead><tr><th>字段</th><th>说明</th><th>权重</th></tr></thead><tbody><tr><td><code>em</code></td><td>邮箱哈希</td><td>最高</td></tr><tr><td><code>ph</code></td><td>手机号哈希</td><td>高</td></tr><tr><td><code>external_id</code></td><td>你系统里的用户 ID</td><td>中</td></tr><tr><td><code>client_ip_address</code></td><td>用户 IP（CAPI 需手动传，Pixel 自动收集）</td><td>中</td></tr><tr><td><code>client_user_agent</code></td><td>浏览器 UA</td><td>中</td></tr><tr><td><code>fbp</code></td><td>Pixel cookie（<code>_fbp</code>）</td><td>中</td></tr></tbody></table><p>小说站的匿名访客没有邮箱，我的做法是至少传 IP + UA + <code>fbp</code>，EMQ 能维持在 5–7 分。如果有登录体系，传邮箱哈希能直接拉到 8 分以上。</p><h2 id="Event-Testing-Tool：上线前必跑"><a href="#Event-Testing-Tool：上线前必跑" class="headerlink" title="Event Testing Tool：上线前必跑"></a>Event Testing Tool：上线前必跑</h2><p>入口：Events Manager → Data Sources → 选你的 Pixel → Test Events。</p><p>输入网页 URL，打开浏览器模拟用户行为，右侧会<strong>实时显示 Meta 收到的每个事件</strong>，包括：</p><ul><li>事件名是否正确</li><li>参数是否完整</li><li>是否被去重</li><li>匹配到了哪些用户标识符</li></ul><p>这是 Debug 神器。每次部署新事件，必须先在 Test Events 里验证，再推生产。</p><p>常见问题排查：</p><table><thead><tr><th>现象</th><th>原因</th></tr></thead><tbody><tr><td>事件触发了但 Test Events 没出现</td><td>Pixel 代码没正确加载，检查 <code>window.fbq</code> 是否存在</td></tr><tr><td>事件出现但参数为空</td><td><code>fbq(&#39;track&#39;, ...)</code> 第二个参数写法有误</td></tr><tr><td>两条记录只计一次</td><td>去重生效，正常</td></tr><tr><td>两条记录都被计入</td><td><code>event_id</code> 没传，或 Pixel 端和 CAPI 端的值不一致</td></tr><tr><td>EMQ 显示 0 或 N&#x2F;A</td><td>没传任何用户标识符，补上 IP + UA + fbp</td></tr></tbody></table><h2 id="要不要用-我的判断框架"><a href="#要不要用-我的判断框架" class="headerlink" title="要不要用 &#x2F; 我的判断框架"></a>要不要用 &#x2F; 我的判断框架</h2><p><strong>要不要上 CAPI？</strong></p><table><thead><tr><th>场景</th><th>建议</th></tr></thead><tbody><tr><td>纯内容站、无登录、主要靠 Facebook 投流</td><td><strong>要上</strong>。iOS 拦截 Pixel 的比例越来越高，CAPI 是补全数据的唯一手段</td></tr><tr><td>有服务端但没后端开发资源</td><td>先用 Meta 的 Conversions API Gateway（托管版，不用写代码）</td></tr><tr><td>电商 &#x2F; 有 Purchase 事件</td><td><strong>必上</strong>。Purchase 数据残缺直接影响 ROAS</td></tr><tr><td>纯品牌曝光、不优化转化</td><td>可以暂缓，Pixel 够用</td></tr></tbody></table><p><strong>自定义转化值得配吗？</strong></p><p>当你的标准事件粒度不够精细，或者想区分”浅度访问”和”深度参与”时，自定义转化值得投入 15 分钟配置。成本极低，收益是算法优化目标的精度。</p><p><strong>EMQ 多少够用？</strong></p><p>5 分以上可以跑，7 分以上才算健康。低于 5 先排查是不是 IP&#x2F;UA 没传，再考虑引入登录体系收集邮箱哈希。</p><hr><p>Events Manager 本质上是 Meta 算法的”喂料接口”。数据喂得越准、越完整，算法的学习曲线越陡，广告效率越高。作为开发者，这是我们能直接控制的少数几个广告变量之一，值得认真对待。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/ddcdd7b9c6e2/</id>
    <link href="https://blog.zisheng.pro/posts/ddcdd7b9c6e2/"/>
    <published>2026-07-04T02:00:00.000Z</published>
    <summary>从 Pixel 到 Conversions API，从标准事件到自定义转化，系统拆解 Meta Events Manager 的核心机制。写给想把广告投放做精的开发者和运营，让你的每一个转化事件都能准确喂给 Meta 算法，把广告预算花在刀刃上。</summary>
    <title>Meta Events Manager 深度指南：让广告数据不再是黑盒</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="AI" scheme="https://blog.zisheng.pro/categories/AI/"/>
    <category term="fiction" scheme="https://blog.zisheng.pro/tags/fiction/"/>
    <category term="arbitrage" scheme="https://blog.zisheng.pro/tags/arbitrage/"/>
    <category term="cold-traffic" scheme="https://blog.zisheng.pro/tags/cold-traffic/"/>
    <category term="Facebook" scheme="https://blog.zisheng.pro/tags/Facebook/"/>
    <category term="AI Agent" scheme="https://blog.zisheng.pro/tags/AI-Agent/"/>
    <content>
      <![CDATA[<p>我在做一个小项目：用 AI 生成英文言情小说，搭小说 H5 站，跑 Facebook 广告买流量，靠 AdSense&#x2F;AdX 广告变现，赚中间差价。</p><p>听起来很野，但本质是个套利模型：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">利润 = 单次 session RPM × 每次 session 产生的 pageview 数 / 1000 − Facebook CPC</span><br></pre></td></tr></table></figure><p>为了跑通这个模型，我需要解决三个杠杆：L1 是让封面 CTR 够高（广告素材质量）；L2 是让读者翻更多章节（内容深度）；L3 是让广告展示 RPM 够高（广告布局和页面性能）。</p><p>这篇讲 L2 里一个具体问题：<strong>如何写第一章</strong>。</p><hr><h2 id="一句话总结"><a href="#一句话总结" class="headerlink" title="一句话总结"></a>一句话总结</h2><p>Ch1 的读者是冷流量，他们在前 200 字内决定走还是留——把第一章当 Landing Page 来设计，而不是当小说章节来写。</p><hr><h2 id="冷流量读者和暖流量读者的区别"><a href="#冷流量读者和暖流量读者的区别" class="headerlink" title="冷流量读者和暖流量读者的区别"></a>冷流量读者和暖流量读者的区别</h2><p>写小说一般假设读者有背景——他们知道这本书的简介，看过评论，或者是老读者回来续读。这是暖流量。</p><p>Facebook 广告带来的是冷流量：读者点进来之前只看到了封面图和一句 tagline。他们不知道主角叫什么，不知道故事发生在哪，没有任何情感投入。他们在 200 字内——大概 60 秒——决定：继续读，还是点 Back。</p><p>这个决策场景和一个付费广告的 Landing Page 完全一样。区别只是产品换成了小说章节。</p><p>所以 ch1 的写作目标不是”交代背景”，而是”让读者感到必须往下读”。</p><hr><h2 id="200-字节拍结构"><a href="#200-字节拍结构" class="headerlink" title="200 字节拍结构"></a>200 字节拍结构</h2><p>我给 AI 写内容时用了一张节拍检查表，把 ch1 切成 6 个区间，每个区间有明确的交付物：</p><table><thead><tr><th>字数区间</th><th>必须完成的事</th><th>为什么</th></tr></thead><tbody><tr><td>0–30 字</td><td>落入动作中，主角已经在做某事</td><td>建立 POV 和能量，不给读者走神的机会</td></tr><tr><td>30–100 字</td><td>一个具体细节点出主角的核心问题</td><td>让读者有东西在乎，无需解释</td></tr><tr><td>100–200 字</td><td>引入张力来源（人&#x2F;情境&#x2F;物件）</td><td>读者必须感到”这要出问题了”</td></tr><tr><td>200–400 字</td><td>用动作（非叙述）展示她想要什么、什么在阻挡</td><td>情感代入；读者必须看到自己或某个原型</td></tr><tr><td>400–700 字</td><td>第一个意外转折</td><td>证明故事会有惊喜，不是慢热消耗</td></tr><tr><td>700–结尾</td><td>再推一个节拍，用未解决的逆转收尾</td><td>强迫读者翻下一章</td></tr></tbody></table><p>这张表的核心逻辑：<strong>每个区间都在回答”读者为什么不能现在停下来”。</strong></p><hr><h2 id="Ch1-的专属硬规则"><a href="#Ch1-的专属硬规则" class="headerlink" title="Ch1 的专属硬规则"></a>Ch1 的专属硬规则</h2><p>除了节拍结构，还有几条只适用于 ch1 的约束：</p><h3 id="首行禁令"><a href="#首行禁令" class="headerlink" title="首行禁令"></a>首行禁令</h3><p>第一句话不能是天气、时刻或场景描写。这是最常见的 AI 生成小说 bug，也是最致命的——冷流量读者在第一句就判断”这不是我要读的东西”。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">❌ &quot;城市灯光在 Elena 驻立的顶层窗户下闪烁。&quot;</span><br><span class="line">❌ &quot;自从她最后一次见到他，已经过了三年。&quot;</span><br><span class="line">✅ 某个已经在动作、冲突或决策中的暗示</span><br></pre></td></tr></table></figure><h3 id="回忆上限"><a href="#回忆上限" class="headerlink" title="回忆上限"></a>回忆上限</h3><p>前 500 字内最多 2 句回忆&#x2F;背景叙述，且只有当它制造反讽时才允许（她以为的 vs 现在的现实）。背景叙述在 ch1 里是转化杀手。</p><h3 id="角色上限"><a href="#角色上限" class="headerlink" title="角色上限"></a>角色上限</h3><p>ch1 最多引入 2 个具名角色（主角 + 另一人）。开篇群像场景让冷流量读者无法代入任何人，直接跳出。</p><h3 id="对话时机"><a href="#对话时机" class="headerlink" title="对话时机"></a>对话时机</h3><p>第一段对话必须在第 300 字前出现。对话意味着人与人之间有事发生，这是言情小说的核心承诺——越晚兑现，跳出率越高。</p><h3 id="Facebook-Promise-检查"><a href="#Facebook-Promise-检查" class="headerlink" title="Facebook Promise 检查"></a>Facebook Promise 检查</h3><p>这是我加的一条，纯粹从投放角度来的：定稿 ch1 之前，把广告里的 tagline 重读一遍，确认 ch1 开篇 200 字的情绪基调和 tagline 承诺的一致。</p><p>典型违规：tagline 写”一场不该发生的相遇”，ch1 却从主角的日常办公室生活开始，过了 800 字才见面。这不是”悬念”，这是让为”相遇”付了 CPC 的流量失望。</p><hr><h2 id="字数目标：更短比更长好"><a href="#字数目标：更短比更长好" class="headerlink" title="字数目标：更短比更长好"></a>字数目标：更短比更长好</h2><p>常规章节目标是 1,400–1,800 字。但 ch1 建议取下限（1,400–1,500 字）。</p><p>原因很直接：冷流量读者在 7 分钟内读完 ch1 → 立即点”下一章” → 产生第二个 pageview。更长的 ch1 不会让他们更投入，只是延迟了那次点击。</p><hr><h2 id="200-字止损测试"><a href="#200-字止损测试" class="headerlink" title="200 字止损测试"></a>200 字止损测试</h2><p>写完 ch1 后，我用这个测试验收：</p><blockquote><p>只读前 200 字。假设有人从 Facebook 广告来，除此之外一无所知，他们会<strong>感受到什么</strong>并想知道接下来发生什么吗？</p></blockquote><p>如果答案是”他们会<strong>理解</strong>场景设定”——从第 1 字重写。</p><p>理解不等于感受。感受才能转化。</p><hr><h2 id="要不要用-我的判断框架"><a href="#要不要用-我的判断框架" class="headerlink" title="要不要用 &#x2F; 我的判断框架"></a>要不要用 &#x2F; 我的判断框架</h2><p>这套规范有没有价值，取决于你的流量结构：</p><table><thead><tr><th>场景</th><th>建议</th></tr></thead><tbody><tr><td>跑付费广告（Facebook&#x2F;TikTok）带流量</td><td><strong>值得上</strong>。ch1 就是你的 landing page，这些规则是 CRO 原则的小说翻译版</td></tr><tr><td>SEO 自然流量为主</td><td><strong>可以再等</strong>。来自搜索的读者有更强的意图，200 字紧迫感要求没那么高</td></tr><tr><td>纯粹写给自己看&#x2F;文学创作</td><td><strong>重点关注其他维度</strong>。这套规范优化的是转化，不是艺术性</td></tr></tbody></table><p>AI 写小说这件事，我越做越觉得它本质是内容产品工程，而不是创作本身。写作规范是 prompt engineering，节拍结构是信息架构，CPC vs RPM 是单元经济——只是产品换成了阅读体验。</p><p>这个视角切换之后，很多”写作建议”就能被精确化：不是”第一章要有吸引力”，而是”前 200 字的读者感受必须和广告 tagline 的承诺对齐，否则冷流量 bounce”。</p><p>量化之后，才能优化。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/bc743c6da846/</id>
    <link href="https://blog.zisheng.pro/posts/bc743c6da846/"/>
    <published>2026-07-04T02:00:00.000Z</published>
    <summary>我在做一个 Facebook 投流 + 小说站套利的 AI Agent 系统。这篇讲的是其中一个让我意识到&quot;写作即产品设计&quot;的规范——如何写第一章，让广告带来的冷流量读者在 200 字内决定继续读下去。不谈文学，只谈转化。</summary>
    <title>冷流量 Ch1 钩子：把第一章当 Landing Page 来写</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="AI" scheme="https://blog.zisheng.pro/categories/AI/"/>
    <category term="前端开发" scheme="https://blog.zisheng.pro/tags/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    <category term="Claude Code" scheme="https://blog.zisheng.pro/tags/Claude-Code/"/>
    <category term="Remotion" scheme="https://blog.zisheng.pro/tags/Remotion/"/>
    <category term="帧动画" scheme="https://blog.zisheng.pro/tags/%E5%B8%A7%E5%8A%A8%E7%94%BB/"/>
    <category term="RunCat" scheme="https://blog.zisheng.pro/tags/RunCat/"/>
    <category term="创意编程" scheme="https://blog.zisheng.pro/tags/%E5%88%9B%E6%84%8F%E7%BC%96%E7%A8%8B/"/>
    <content>
      <![CDATA[<p>macOS 菜单栏那只跟着 CPU 使用率越跑越快的小猫（<a href="https://github.com/Kyome22/menubar_runcat">RunCat</a>），大概是我用过最治愈的系统监控工具。作为一个从前端转 Agent 开发的人，我一直好奇：这种逐帧 sprite 动画，能不能用现代 React 工具链声明式地复刻出来？答案是 <strong>Remotion</strong>——一个用 React 写视频的框架。这篇文章我用 Claude Code + Remotion 把这只猫从头搭一遍，顺带把「帧动画」这件事讲透。</p><h2 id="一句话总结"><a href="#一句话总结" class="headerlink" title="一句话总结"></a>一句话总结</h2><p>RunCat 的猫本质只有 <strong>5 张 PNG</strong>，靠「取模循环 + 定时器间隔」跑起来。Remotion 的<strong>纯函数帧模型</strong>天生适合表达这种逐帧动画：把「第几帧显示哪张图」写成一个纯函数即可。但要认清边界——<strong>Remotion 产出的是渲染好的 GIF&#x2F;视频资产，不是常驻菜单栏的原生进程</strong>。想要真·菜单栏猫得靠 Tauri&#x2F;Electron，Remotion 负责的是把动画本身优雅地造出来。</p><h2 id="先扒开-RunCat：它到底怎么动"><a href="#先扒开-RunCat：它到底怎么动" class="headerlink" title="先扒开 RunCat：它到底怎么动"></a>先扒开 RunCat：它到底怎么动</h2><p>很多人以为菜单栏动画是什么黑魔法，扒完源码发现朴素得可爱。RunCat 的核心就三件事。</p><h3 id="1-5-帧-PNG-循环"><a href="#1-5-帧-PNG-循环" class="headerlink" title="1. 5 帧 PNG 循环"></a>1. 5 帧 PNG 循环</h3><p>仓库 <code>Assets.xcassets</code> 里躺着 <code>cat0.png</code> 到 <code>cat4.png</code>，一共 5 帧。帧循环逻辑一行搞定：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">index <span class="operator">=</span> (index <span class="operator">+</span> <span class="number">1</span>) <span class="operator">%</span> frames.count   <span class="comment">// 4 之后回到 0，无限循环</span></span><br><span class="line">statusItem.button<span class="operator">?</span>.image <span class="operator">=</span> frames[index]</span><br></pre></td></tr></table></figure><p>这个 <code>% frames.count</code>（取模）就是所有 sprite 循环动画的灵魂——记住它，等下 Remotion 里还是它。</p><h3 id="2-CPU-使用率决定「跑多快」"><a href="#2-CPU-使用率决定「跑多快」" class="headerlink" title="2. CPU 使用率决定「跑多快」"></a>2. CPU 使用率决定「跑多快」</h3><p>猫跑多快，取决于定时器的触发间隔。RunCat 的速度公式是：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">interval <span class="operator">=</span> <span class="number">0.2</span> <span class="operator">/</span> <span class="built_in">max</span>(<span class="number">1.0</span>, <span class="built_in">min</span>(<span class="number">20.0</span>, usage <span class="operator">/</span> <span class="number">5.0</span>))</span><br></pre></td></tr></table></figure><p>拆开看：</p><ul><li><code>usage / 5.0</code>：把 CPU 百分比缩放一下</li><li><code>min(20, max(1, ...))</code>：钳制在 1~20 倍之间，防止闲时卡成 PPT、满载时快到抽搐</li><li><code>0.2 / 倍率</code>：基础间隔 0.2 秒，倍率越大间隔越短</li></ul><p>所以 <strong>CPU 越高 → 倍率越大 → 间隔越短 → 换帧越快 → 猫跑得越急</strong>。空闲时 0.2 秒一帧，满载时能到 0.01 秒一帧。就这么个反比关系，体感却极其直观。</p><h3 id="3-定时器驱动"><a href="#3-定时器驱动" class="headerlink" title="3. 定时器驱动"></a>3. 定时器驱动</h3><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Timer</span>(timeInterval: <span class="keyword">self</span>.interval, repeats: <span class="literal">true</span>) &#123; <span class="keyword">self</span><span class="operator">?</span>.next() &#125;</span><br></pre></td></tr></table></figure><p>每隔 <code>interval</code> 触发一次 <code>next()</code>，<code>next()</code> 里就是上面那句取模换帧。<strong>这是典型的「有状态命令式」动画</strong>：一个 <code>index</code> 变量，定时 <code>+1</code>。</p><p>记住这个「命令式、有状态」的特征——因为 Remotion 的模型跟它<strong>恰好相反</strong>，这是本文最值得玩味的地方。</p><h2 id="为什么用-Remotion-做帧动画"><a href="#为什么用-Remotion-做帧动画" class="headerlink" title="为什么用 Remotion 做帧动画"></a>为什么用 Remotion 做帧动画</h2><p>Remotion 的核心信条只有一句：<strong>每一帧都是 <code>useCurrentFrame()</code> 的纯函数</strong>。</p><p>官方原话是——把你的组件想象成一个「把帧号转换成图像」的函数。每一帧独立渲染，不依赖上一帧的状态。这意味着 CSS 动画、<code>setInterval</code>、<code>requestAnimationFrame</code> 那套在 Remotion 里几乎全部失效，你必须重新用「帧号 → 画面」的思路建模。</p><p>对比一下就懂了：</p><table><thead><tr><th></th><th>RunCat（命令式）</th><th>Remotion（声明式）</th></tr></thead><tbody><tr><td>状态</td><td>维护一个 <code>index</code> 变量</td><td>无状态，只读 <code>frame</code></td></tr><tr><td>换帧</td><td>定时器 <code>index++</code></td><td><code>Math.floor(frame / n) % 5</code></td></tr><tr><td>时间</td><td>真实时钟（<code>Timer</code>）</td><td>逻辑帧号（<code>useCurrentFrame</code>）</td></tr><tr><td>产物</td><td>实时菜单栏画面</td><td>渲染成 GIF&#x2F;视频</td></tr></tbody></table><p>看出来了吗？RunCat 那句 <code>(index + 1) % 5</code> 的「取模」精神，在 Remotion 里变成了<strong>用当前帧号直接算出该显示第几张 sprite</strong>——不需要状态，因为帧号本身就是「时间的坐标」。这就是纯函数帧模型的优雅之处。</p><h2 id="动手：Claude-Code-Remotion-复刻奔跑的猫"><a href="#动手：Claude-Code-Remotion-复刻奔跑的猫" class="headerlink" title="动手：Claude Code + Remotion 复刻奔跑的猫"></a>动手：Claude Code + Remotion 复刻奔跑的猫</h2><h3 id="第一步：让-Claude-Code-起项目"><a href="#第一步：让-Claude-Code-起项目" class="headerlink" title="第一步：让 Claude Code 起项目"></a>第一步：让 Claude Code 起项目</h3><p>我的做法是直接把需求丢给 Claude Code，让它搭脚手架：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">初始化一个 Remotion 项目，创建一个叫 RunningCat 的 Composition：</span><br><span class="line">- 144x144，30fps</span><br><span class="line">- 加载 public/cat/ 下的 0.png~4.png 五帧 sprite</span><br><span class="line">- 根据传入的 load（模拟 CPU 使用率）参数，用 RunCat 的速度公式</span><br><span class="line">  interval = 0.2 / clamp(load/5, 1, 20) 换算成换帧节奏</span><br><span class="line">- 像素图，用 imageRendering: pixelated 保持锐利</span><br></pre></td></tr></table></figure><p><code>npx create-video@latest</code> 起模板后，Claude Code 会把组件、<code>Root.tsx</code> 注册、<code>public/</code> 资源路径一次性搭好。<strong>帧素材</strong>可以直接扒 RunCat 仓库 <code>resources/cat/png/</code> 里的 5 张图，或者让 AI 图像工具生成一套你自己的 5 帧奔跑序列。</p><h3 id="第二步：核心-sprite-组件"><a href="#第二步：核心-sprite-组件" class="headerlink" title="第二步：核心 sprite 组件"></a>第二步：核心 sprite 组件</h3><p>这是全文的重点。RunCat 的「取模换帧」在 Remotion 里长这样：</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/RunningCat.tsx</span></span><br><span class="line"><span class="keyword">import</span> &#123;</span><br><span class="line">  <span class="title class_">AbsoluteFill</span>, <span class="title class_">Img</span>, staticFile,</span><br><span class="line">  useCurrentFrame, useVideoConfig,</span><br><span class="line">&#125; <span class="keyword">from</span> <span class="string">&#x27;remotion&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">CAT_FRAMES</span> = [<span class="number">0</span>, <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>].<span class="title function_">map</span>(<span class="function">(<span class="params">i</span>) =&gt;</span> <span class="title function_">staticFile</span>(<span class="string">`cat/<span class="subst">$&#123;i&#125;</span>.png`</span>));</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title class_">RunningCat</span>: <span class="title class_">React</span>.<span class="property">FC</span>&lt;&#123; <span class="attr">load</span>: <span class="built_in">number</span> &#125;&gt; = <span class="function">(<span class="params">&#123; load &#125;</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> frame = <span class="title function_">useCurrentFrame</span>();</span><br><span class="line">  <span class="keyword">const</span> &#123; fps &#125; = <span class="title function_">useVideoConfig</span>();</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 照搬 RunCat 的速度公式</span></span><br><span class="line">  <span class="keyword">const</span> speed = <span class="title class_">Math</span>.<span class="title function_">max</span>(<span class="number">1</span>, <span class="title class_">Math</span>.<span class="title function_">min</span>(<span class="number">20</span>, load / <span class="number">5</span>));</span><br><span class="line">  <span class="keyword">const</span> secondsPerSprite = <span class="number">0.2</span> / speed;            <span class="comment">// 每张 sprite 停留的秒数</span></span><br><span class="line">  <span class="keyword">const</span> framesPerSprite = <span class="title class_">Math</span>.<span class="title function_">max</span>(               <span class="comment">// 换算成「视频帧」</span></span><br><span class="line">    <span class="number">1</span>,</span><br><span class="line">    <span class="title class_">Math</span>.<span class="title function_">round</span>(secondsPerSprite * fps),</span><br><span class="line">  );</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 灵魂取模：帧号 → 第几张 sprite</span></span><br><span class="line">  <span class="keyword">const</span> spriteIndex =</span><br><span class="line">    <span class="title class_">Math</span>.<span class="title function_">floor</span>(frame / framesPerSprite) % <span class="variable constant_">CAT_FRAMES</span>.<span class="property">length</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">AbsoluteFill</span> <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">justifyContent:</span> &#x27;<span class="attr">center</span>&#x27;, <span class="attr">alignItems:</span> &#x27;<span class="attr">center</span>&#x27; &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Img</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">src</span>=<span class="string">&#123;CAT_FRAMES[spriteIndex]&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">height:</span> <span class="attr">144</span>, <span class="attr">imageRendering:</span> &#x27;<span class="attr">pixelated</span>&#x27; &#125;&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">AbsoluteFill</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>关键就那一行 <code>Math.floor(frame / framesPerSprite) % CAT_FRAMES.length</code>：</p><ul><li><code>frame / framesPerSprite</code>：当前帧对应到「第几个 sprite 周期」</li><li><code>Math.floor(...)</code>：取整，同一张图在多个视频帧里保持不变</li><li><code>% 5</code>：跑完 5 张回到第 0 张——<strong>和 RunCat 那句 <code>% frames.count</code> 是同一个灵魂</strong></li></ul><h3 id="第三步：注册-Composition"><a href="#第三步：注册-Composition" class="headerlink" title="第三步：注册 Composition"></a>第三步：注册 Composition</h3><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/Root.tsx</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Composition</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;remotion&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">RunningCat</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;./RunningCat&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title class_">RemotionRoot</span>: <span class="title class_">React</span>.<span class="property">FC</span> = <span class="function">() =&gt;</span> (</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">Composition</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">id</span>=<span class="string">&quot;RunningCat&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">component</span>=<span class="string">&#123;RunningCat&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">durationInFrames</span>=<span class="string">&#123;60&#125;</span>     // <span class="attr">见下方</span>「<span class="attr">无缝循环</span>」<span class="attr">的坑</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">fps</span>=<span class="string">&#123;30&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">width</span>=<span class="string">&#123;144&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">height</span>=<span class="string">&#123;144&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">defaultProps</span>=<span class="string">&#123;&#123;</span> <span class="attr">load:</span> <span class="attr">50</span> &#125;&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">  /&gt;</span></span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p><strong>无缝循环的坑</strong>：GIF 要循环不跳帧，<code>durationInFrames</code> 必须是「一个完整动画周期」的整数倍，也就是 <code>framesPerSprite * 5</code> 的倍数。比如 <code>load=50</code> 时 <code>speed=10</code>、<code>framesPerSprite = round(0.02*30)=1</code>，一个周期 5 帧，那 <code>durationInFrames</code> 取 5 的倍数（如 60）才能首尾对齐。这个细节 Claude Code 不一定主动帮你算，得自己盯一下。</p><h3 id="第四步：渲染成-GIF"><a href="#第四步：渲染成-GIF" class="headerlink" title="第四步：渲染成 GIF"></a>第四步：渲染成 GIF</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 基础渲染</span></span><br><span class="line">npx remotion render RunningCat out/cat.gif --codec=gif</span><br><span class="line"></span><br><span class="line"><span class="comment"># 透明背景（sprite 是抠好的 PNG 时必备）</span></span><br><span class="line">npx remotion render RunningCat out/cat.gif --codec=gif --image-format=png</span><br><span class="line"></span><br><span class="line"><span class="comment"># 降帧压体积：只渲染每 2 帧，30fps → 15fps</span></span><br><span class="line">npx remotion render RunningCat out/cat.gif --codec=gif --every-nth-frame=2</span><br><span class="line"></span><br><span class="line"><span class="comment"># 无限循环（GIF 默认就是无限循环，这里显式写出）</span></span><br><span class="line">npx remotion render RunningCat out/cat.gif --codec=gif --number-of-gif-loops=null</span><br></pre></td></tr></table></figure><p>跑完你就得到一只无限循环、背景透明的奔跑猫 GIF，可以塞进博客、README、PPT 或者任何网页。</p><h2 id="进阶：让猫「真的」随负载变速"><a href="#进阶：让猫「真的」随负载变速" class="headerlink" title="进阶：让猫「真的」随负载变速"></a>进阶：让猫「真的」随负载变速</h2><p>上面 <code>load</code> 是个固定值，猫匀速跑。想复刻 RunCat 那种「负载飙升猫就抽风」的动态感，有两条路。</p><p><strong>渲染成动态 GIF</strong>：用 <code>interpolate</code> 让 <code>load</code> 随帧号变化，模拟一次 CPU 尖峰：</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; interpolate &#125; <span class="keyword">from</span> <span class="string">&#x27;remotion&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> load = <span class="title function_">interpolate</span>(</span><br><span class="line">  frame,</span><br><span class="line">  [<span class="number">0</span>, <span class="number">45</span>, <span class="number">90</span>, <span class="number">135</span>, <span class="number">180</span>],</span><br><span class="line">  [<span class="number">10</span>, <span class="number">95</span>, <span class="number">30</span>, <span class="number">100</span>, <span class="number">10</span>],   <span class="comment">// 闲 → 爆 → 缓 → 再爆 → 闲</span></span><br><span class="line">  &#123; <span class="attr">extrapolateRight</span>: <span class="string">&#x27;clamp&#x27;</span> &#125;,</span><br><span class="line">);</span><br></pre></td></tr></table></figure><blockquote><p><strong>一个容易被忽略的深度坑</strong>：当速度随帧动态变化时，<code>Math.floor(frame / framesPerSprite)</code> 里的 <code>framesPerSprite</code> 每帧都在变，除法结果不再单调递增，sprite 会跳帧。严格正确的做法是<strong>对速度做相位累积</strong>（把每帧的换帧增量累加起来），而不是简单除法。RunCat 靠有状态的 <code>index++</code> 天然规避了这个问题，但 Remotion 的纯函数模型逼你把「累积」显式写出来——这正是命令式转声明式最反直觉的地方。做匀速 demo 可以偷懒用除法，做严肃动态动画就得老实累积。</p></blockquote><p><strong>驱动实时网页版</strong>：如果你要的不是视频而是一个活的网页挂件，用 <code>@remotion/player</code> 把同一个组件搬进浏览器，再把真实性能指标喂给 <code>load</code>，就是一个跑在网页里的实时 RunCat。同一份组件，既能渲染成 GIF，又能实时播放——这是 Remotion 相比纯 CSS 动画的一大优势。</p><h2 id="Claude-Code-在这条流程里加速了什么"><a href="#Claude-Code-在这条流程里加速了什么" class="headerlink" title="Claude Code 在这条流程里加速了什么"></a>Claude Code 在这条流程里加速了什么</h2><p>说句实在话，Remotion 的 API 不难，难的是「纯函数帧模型」的思维转换。Claude Code 的价值分三层：</p><ul><li><strong>脚手架层（省时间）</strong>：起项目、配 <code>Composition</code>、连 <code>public/</code> 资源、写渲染脚本，这些样板活它一把梭。</li><li><strong>翻译层（省脑子）</strong>：把 RunCat 那句 Swift 的命令式 <code>index++</code> 翻译成 Remotion 的声明式取模，这种「换范式重写」是 AI 的强项——你给它源码逻辑，它给你目标框架的地道实现。</li><li><strong>调试层（省试错）</strong>：无缝循环的帧数对齐、动态变速的相位累积这类坑，描述清楚现象后它能直接给修法，比自己翻文档快得多。</li></ul><p>但**「取模换帧」的原理、「相位累积」的坑、无缝循环的数学**——这些判断你得心里有数，否则 AI 给的代码你既 review 不了也调不动。这也印证了我一直的观点：<strong>AI 拉满执行力，判断框架还得是自己的</strong>。</p><h2 id="要不要这么搞：我的判断"><a href="#要不要这么搞：我的判断" class="headerlink" title="要不要这么搞：我的判断"></a>要不要这么搞：我的判断</h2><ul><li><strong>值得动手</strong>：你想给博客&#x2F;README&#x2F;演示做一个有辨识度的循环动画，或者想亲手理解「逐帧 sprite 动画」和「声明式帧模型」——RunCat 复刻是绝佳的练手项目，5 帧图、一个取模，麻雀虽小五脏俱全。</li><li><strong>别用错工具</strong>：你要的是一个<strong>常驻 macOS 菜单栏、读真实 CPU</strong> 的原生小猫——那 Remotion 不是答案，老老实实用 Swift（RunCat 本体）或 Tauri&#x2F;Electron。Remotion 造的是动画资产，不是系统进程。</li><li><strong>重点收获</strong>：真正值钱的不是这只猫，而是**「命令式有状态动画 → 声明式纯函数帧」的思维转换**。理解了 <code>useCurrentFrame</code> 这套模型，你能用 React 声明式地造出几乎任何数据驱动的视频——这才是 Remotion 值得前端认真学一次的原因。</li></ul><p>一只 5 帧的猫，串起了 sprite 动画原理、纯函数帧模型、和 AI 辅助的范式翻译。有时候最好的学习项目，就是把一个你每天都在看的小东西，亲手拆开再装回去。</p><blockquote><p>参考来源：<a href="https://github.com/Kyome22/menubar_runcat">menubar_runcat 源码</a> · <a href="https://www.remotion.dev/docs/use-current-frame">Remotion useCurrentFrame 文档</a> · <a href="https://www.remotion.dev/docs/render-as-gif">Remotion 渲染 GIF 文档</a></p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/5e4b74eb9d6f/</id>
    <link href="https://blog.zisheng.pro/posts/5e4b74eb9d6f/"/>
    <published>2026-07-03T12:00:00.000Z</published>
    <summary>RunCat 菜单栏那只随 CPU 使用率加速奔跑的小猫，本质是 5 帧 PNG 的循环 sprite 动画。这篇文章先扒开它的源码，拆解帧循环和速度公式，再用 Claude Code + Remotion 把这套逐帧动画在 React 里声明式复刻出来、渲染成可无限循环的 GIF，并讲清楚 Remotion「纯函数帧模型」的适用边界，以及这条工作流里 AI 到底加速了什么。</summary>
    <title>用 Claude Code + Remotion 复刻 RunCat 奔跑的猫帧动画</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="AI" scheme="https://blog.zisheng.pro/categories/AI/"/>
    <category term="前端开发" scheme="https://blog.zisheng.pro/tags/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    <category term="AI Infra" scheme="https://blog.zisheng.pro/tags/AI-Infra/"/>
    <category term="Claude" scheme="https://blog.zisheng.pro/tags/Claude/"/>
    <category term="Anthropic" scheme="https://blog.zisheng.pro/tags/Anthropic/"/>
    <category term="Claude Design" scheme="https://blog.zisheng.pro/tags/Claude-Design/"/>
    <category term="设计工具" scheme="https://blog.zisheng.pro/tags/%E8%AE%BE%E8%AE%A1%E5%B7%A5%E5%85%B7/"/>
    <content>
      <![CDATA[<p>Anthropic Labs 在 2026 年 4 月发布了 <strong>Claude Design</strong>——一个用对话生成设计稿、交互原型、PPT 和营销物料的 AI 工具，由 <strong>Claude Opus 4.7</strong> 驱动。它被普遍解读为「冲着 Figma 来的」，但作为一个天天在 design-to-code 链路上打转的前端，我更关心的是另一件事：<strong>它能读你的 codebase，反过来又能把产物交接给 Claude Code</strong>。这篇文章从前端视角讲讲它到底是什么、怎么用，以及要不要现在就上。</p><h2 id="一句话总结"><a href="#一句话总结" class="headerlink" title="一句话总结"></a>一句话总结</h2><p>Claude Design 的核心卖点不是「又一个 AI 画图工具」，而是<strong>把设计系统和代码库打通</strong>：它在 onboarding 时读你的 codebase 和设计文件，自动抽出 colors、typography、components 建成一套 design system，之后每个项目都自动套用。对前端来说，这意味着 design-to-code 这条链路第一次有可能从两端同时被 AI 咬合。</p><h2 id="Claude-Design-是什么"><a href="#Claude-Design-是什么" class="headerlink" title="Claude Design 是什么"></a>Claude Design 是什么</h2><p>一句话：<strong>你用自然语言描述要什么，Claude 生成第一版，然后你通过多种方式细调。</strong></p><p>它能生成的东西比想象中广：</p><ul><li>真实可交互的 prototype（不是静态图，是能点的）</li><li>产品 wireframe 和 mockup</li><li>多方向的设计探索（一次给你几套不同风格）</li><li>Pitch deck、PPT 演示</li><li>营销物料：landing page、社媒素材、campaign 视觉</li><li>代码驱动的原型，甚至能带 voice、video、shader、3D 和内置 AI</li></ul><h2 id="核心工作流：生成之后怎么改"><a href="#核心工作流：生成之后怎么改" class="headerlink" title="核心工作流：生成之后怎么改"></a>核心工作流：生成之后怎么改</h2><p>AI 生成设计的工具不少，Claude Design 真正的差异在<strong>细调环节</strong>。它给了四种并行的修改通道：</p><ol><li><strong>对话（Conversation）</strong>：直接跟 Claude 说「这个标题再大一点、配色冷一点」</li><li><strong>Inline comments</strong>：像在 Figma 里评论一样，圈住某个具体元素批注</li><li><strong>直接编辑（Direct editing）</strong>：文字可以直接改，不用绕一圈跟 AI 说</li><li><strong>自定义 slider</strong>：这是最有意思的——<strong>Claude 会自己生成调节旋钮</strong>，让你实时拖动 spacing、color、layout</li></ol><p>第 4 点值得展开。传统 AI 生成图是「抽卡」——不满意就重 roll，全凭运气。而 Claude Design 给你的是<strong>参数化的连续控制</strong>，而且这些 slider 是它根据当前设计动态生成的，不是固定几个。这在交互范式上比「反复 prompt 重生成」高了一个维度。</p><h2 id="对前端最有价值的三个点"><a href="#对前端最有价值的三个点" class="headerlink" title="对前端最有价值的三个点"></a>对前端最有价值的三个点</h2><p>抛开「取代设计师」这种标题党，我觉得对前端工程师真正有价值的是这三个能力：</p><h3 id="1-从-codebase-生成-design-system"><a href="#1-从-codebase-生成-design-system" class="headerlink" title="1. 从 codebase 生成 design system"></a>1. 从 codebase 生成 design system</h3><p>onboarding 时 Claude 会读你的代码库和设计文件，自动建一套 design system——colors、typography、components 全抽出来，之后每个新项目自动套用，保证品牌一致性。团队可以持续打磨这套系统，还能同时维护多套。</p><p>对前端来说这解决了一个老大难：<strong>设计稿和代码的 design token 对不齐</strong>。以前是设计师在 Figma 定一套、前端在代码里再实现一套，两边长期漂移。现在 AI 直接从你已有的代码里反推设计系统，源头就是统一的。</p><h3 id="2-Web-capture：抓真实产品的元素"><a href="#2-Web-capture：抓真实产品的元素" class="headerlink" title="2. Web capture：抓真实产品的元素"></a>2. Web capture：抓真实产品的元素</h3><p>它有个 web capture 工具，能直接从线上网站抓取元素，让原型「长得像真实产品」。做增量需求时特别实用——不用从零搭页面骨架，直接抓现有页面来改。</p><h3 id="3-导出-HTML-交接给-Claude-Code"><a href="#3-导出-HTML-交接给-Claude-Code" class="headerlink" title="3. 导出 HTML + 交接给 Claude Code"></a>3. 导出 HTML + 交接给 Claude Code</h3><p>导出选项里有两个对开发者关键：</p><ul><li><strong>standalone HTML 文件</strong>：产物可以直接是能跑的 HTML</li><li><strong>handoff bundle 到 Claude Code</strong>：设计稿能打包交接给 Claude Code 继续实现</li></ul><p>这就是我开头说的「两端咬合」：<strong>Claude Design 从 codebase 读设计系统 → 生成设计和原型 → 再把产物交回 Claude Code 落地成生产代码</strong>。整条 design-to-code 链路，AI 从设计端和工程端同时介入。</p><p>其他导出格式也齐全：组织内部 URL 共享、保存为文件夹、导出到 Canva、PDF、PPTX。导入同样灵活：text prompt、图片、DOCX&#x2F;PPTX&#x2F;XLSX 文档，或直接指向 codebase。</p><h2 id="规格与可用性"><a href="#规格与可用性" class="headerlink" title="规格与可用性"></a>规格与可用性</h2><table><thead><tr><th>项目</th><th>详情</th></tr></thead><tbody><tr><td>驱动模型</td><td>Claude Opus 4.7（Anthropic 最强的 vision 模型）</td></tr><tr><td>状态</td><td>Research preview（逐步放量）</td></tr><tr><td>可用套餐</td><td>Claude Pro &#x2F; Max &#x2F; Team &#x2F; Enterprise</td></tr><tr><td>计费</td><td>包含在现有订阅内，走标准用量额度，可选额外用量</td></tr><tr><td>企业策略</td><td>默认关闭，需管理员在 Organization settings 开启</td></tr></tbody></table><p>注意它用的是 <strong>Opus 4.7</strong> 而不是最新的 Fable 5——因为 4.7 是专门强化过 vision 能力的模型，处理设计这种视觉密集任务更合适，这也印证了 Anthropic「按任务选模型」而非「无脑上最新」的思路。</p><h2 id="和-Figma-Canva-是什么关系"><a href="#和-Figma-Canva-是什么关系" class="headerlink" title="和 Figma &#x2F; Canva 是什么关系"></a>和 Figma &#x2F; Canva 是什么关系</h2><p>媒体普遍把它定位成「挑战 Figma」，但我的判断更克制：</p><ul><li><strong>它不是 Figma 替代品</strong>：Figma 强在多人实时协作、精细的矢量控制、成熟的插件生态和 handoff 流程，这些 Claude Design 现阶段替代不了。</li><li><strong>它是 0→1 阶段的加速器</strong>：从想法到第一版可交互原型，Claude Design 快得多。适合验证概念、快速出多个方向、做 pitch。</li><li><strong>它和 Canva 是导出关系而非竞争</strong>：能直接导出到 Canva，说明 Anthropic 没打算做全链路闭环，而是嵌入现有工作流。</li></ul><p>所以更准确的说法是：<strong>它抢的是「打开 Figma 从空白画布开始」的那部分时间</strong>，而不是整个 Figma。</p><h2 id="前端视角：这对我们意味着什么"><a href="#前端视角：这对我们意味着什么" class="headerlink" title="前端视角：这对我们意味着什么"></a>前端视角：这对我们意味着什么</h2><p>结合我自己从前端转 Agent 开发的路径，谈几点判断：</p><p><strong>短期</strong>，它是个提效工具。做 landing page、内部工具原型、demo、给 PPM 讲方案的可交互 mockup，这些以前要拉设计师排期的活，现在能自己快速出。前端的「全栈化」又往设计端延伸了一步。</p><p><strong>中期</strong>，值得关注的是 design system 的自动化。如果 AI 能可靠地从代码反推、并维护设计系统，那设计和研发之间那道「token 对齐」的墙会越来越薄。这对 design system 维护者是个信号。</p><p><strong>长期</strong>，我更在意的是它作为 <strong>agentic workflow 一环</strong>的定位——从 codebase 输入到 Claude Code 输出，Claude Design 是把「设计」这个此前很难被 agent 编排的环节，接进了自动化流水线。这才是 AI Infra 视角下真正的看点：不是又多了个工具，而是又一个人类专业环节被纳入了 agent 可调度的范围。</p><h2 id="要不要现在就用"><a href="#要不要现在就用" class="headerlink" title="要不要现在就用"></a>要不要现在就用</h2><p>我的判断框架：</p><ul><li><strong>值得现在就上</strong>：你是 Pro&#x2F;Max 订阅用户，需要快速出原型、demo、pitch deck，或者想验证「从代码生成设计系统」在自己项目上的效果。反正包含在订阅里，试错成本几乎为零。</li><li><strong>可以再等等</strong>：你的核心诉求是精细的生产级设计协作、复杂的多人 handoff 流程——现阶段 research preview 还不够成熟，继续用 Figma。</li><li><strong>重点关注</strong>：如果你在做 AI Infra &#x2F; agentic workflow，Claude Design 的「codebase ↔ 设计 ↔ Claude Code」闭环值得深入研究，它可能是未来 design-to-code 自动化的一块关键拼图。</li></ul><p>Anthropic 把它放在 Labs 下、以 research preview 形式发布，本身就说明这是个「探索方向」而非「成熟产品」。但方向选得很准——<strong>用 AI 咬合设计和代码的两端</strong>，这件事一旦跑通，改变的不只是设计工具市场，而是整个前端的工作方式。</p><blockquote><p>参考来源：<a href="https://www.anthropic.com/news/claude-design-anthropic-labs">Introducing Claude Design by Anthropic Labs</a> · <a href="https://claude.com/product/design">Turn ideas into designs with Claude</a></p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/66115f19c248/</id>
    <link href="https://blog.zisheng.pro/posts/66115f19c248/"/>
    <published>2026-07-03T10:00:00.000Z</published>
    <summary>Anthropic Labs 发布了 Claude Design，一个用对话生成设计稿、原型和 PPT 的 AI 工具，由 Claude Opus 4.7 驱动，主打「读你的 codebase 生成设计系统」并能一键交接给 Claude Code。这篇文章从前端工程师的视角，梳理它的核心工作流、对 design-to-code 链路的冲击，以及要不要现在就用。</summary>
    <title>Claude Design 上手：前端视角看 Anthropic 的 AI 设计工具</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="AI" scheme="https://blog.zisheng.pro/categories/AI/"/>
    <category term="LLM" scheme="https://blog.zisheng.pro/tags/LLM/"/>
    <category term="Agent" scheme="https://blog.zisheng.pro/tags/Agent/"/>
    <category term="Claude" scheme="https://blog.zisheng.pro/tags/Claude/"/>
    <category term="Anthropic" scheme="https://blog.zisheng.pro/tags/Anthropic/"/>
    <category term="Fable 5" scheme="https://blog.zisheng.pro/tags/Fable-5/"/>
    <content>
      <![CDATA[<p>Anthropic 发布了 <strong>Claude Fable 5</strong>（model ID：<code>claude-fable-5</code>），这是目前 Anthropic 能力最强的正式发布模型，定位是”最苛刻的推理任务和长程 agentic 工作”。我第一时间把 Claude Code 的默认模型切换了过去，这篇文章梳理一下它相比 Opus 4.8 的大更新，以及写代码接入时要注意的破坏性变更。</p><h2 id="一句话总结"><a href="#一句话总结" class="headerlink" title="一句话总结"></a>一句话总结</h2><p>Fable 5 的核心卖点不是”老任务做得更好”，而是<strong>能做以前模型做不了的事</strong>：单次请求自主运行几十分钟的长程任务、可靠的多 sub-agent 并行协作、端到端的企业级交付物。如果你的场景还是分类、摘要、单轮问答，Opus 4.8 依然是更划算的选择。</p><h2 id="能力升级"><a href="#能力升级" class="headerlink" title="能力升级"></a>能力升级</h2><h3 id="长程-agentic-任务（最大亮点）"><a href="#长程-agentic-任务（最大亮点）" class="headerlink" title="长程 agentic 任务（最大亮点）"></a>长程 agentic 任务（最大亮点）</h3><p>单次请求可以自主运行很长时间——复杂任务跑 15 分钟是正常现象。适合的场景包括：</p><ul><li>大型代码迁移、重构、通宵编码任务</li><li>端到端的企业交付物：财务分析、Excel 表格、PPT、文档</li><li>深度研究和长时间的自主工作流</li></ul><p>官方给的最佳实践是：<strong>在第一轮就给出完整、清晰的任务规格</strong>，然后放手让它执行，而不是挤牙膏式地多轮补充需求。</p><h3 id="多-sub-agent-协作"><a href="#多-sub-agent-协作" class="headerlink" title="多 sub-agent 协作"></a>多 sub-agent 协作</h3><p>以前的模型用 sub-agent 经常翻车，很多团队的 prompt 里都有”不要随便开 sub-agent”之类的压制性指令。Fable 5 反过来了：<strong>并行委派子任务非常可靠</strong>，能和长期运行的 sub-agent、peer agent 保持异步通信。官方甚至建议主动鼓励它委派：</p><blockquote><p>Delegate independent subtasks to sub-agents and keep working while they run.</p></blockquote><h3 id="其他提升"><a href="#其他提升" class="headerlink" title="其他提升"></a>其他提升</h3><ul><li><strong>Code review &#x2F; debugging</strong>：找真实 bug 的能力显著提升（recall 和 precision 都涨了）</li><li><strong>Vision</strong>：针对密集、模糊、翻转、有噪点的图像做了专门训练，会主动用 bash 和 crop 工具预处理图片</li><li><strong>Memory</strong>：给它一个可写的 memory 文件（哪怕就是个 <code>.md</code>），跨任务表现明显更好</li></ul><h2 id="规格与定价"><a href="#规格与定价" class="headerlink" title="规格与定价"></a>规格与定价</h2><table><thead><tr><th>项目</th><th>Claude Fable 5</th><th>Claude Opus 4.8</th></tr></thead><tbody><tr><td>Context window</td><td>1M tokens（默认即最大）</td><td>1M tokens</td></tr><tr><td>Max output</td><td>128K</td><td>128K</td></tr><tr><td>输入价格</td><td>$10 &#x2F; MTok</td><td>$5 &#x2F; MTok</td></tr><tr><td>输出价格</td><td>$50 &#x2F; MTok</td><td>$25 &#x2F; MTok</td></tr><tr><td>Tokenizer</td><td>与 Opus 4.8 相同</td><td>—</td></tr></tbody></table><p>价格是 Opus 4.8 的两倍。Tokenizer 没变，从 Opus 4.7&#x2F;4.8 迁移过来 token 数基本不变，只是单价不同。</p><h2 id="API-破坏性变更"><a href="#API-破坏性变更" class="headerlink" title="API 破坏性变更"></a>API 破坏性变更</h2><p>写代码接入时要注意这几点，都是会直接 400 的硬约束：</p><h3 id="1-Thinking-永远开启"><a href="#1-Thinking-永远开启" class="headerlink" title="1. Thinking 永远开启"></a>1. Thinking 永远开启</h3><p>不要传 <code>thinking</code> 参数（或只传 <code>{&quot;type&quot;: &quot;adaptive&quot;}</code>）。<code>{&quot;type&quot;: &quot;disabled&quot;}</code> 和 <code>budget_tokens</code> 都会返回 400。思考深度改用 <code>output_config.effort</code> 控制，档位从 <code>low</code> 到 <code>max</code>：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">client.messages.create(</span><br><span class="line">    model=<span class="string">&quot;claude-fable-5&quot;</span>,</span><br><span class="line">    max_tokens=<span class="number">16000</span>,</span><br><span class="line">    output_config=&#123;<span class="string">&quot;effort&quot;</span>: <span class="string">&quot;high&quot;</span>&#125;,  <span class="comment"># 不传 thinking 参数</span></span><br><span class="line">    messages=[...],</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>值得一提的是，Fable 5 的 <code>low</code> 档表现经常超过旧模型的 <code>max</code> 档，日常任务不用无脑拉满。</p><h3 id="2-原始思维链永不返回"><a href="#2-原始思维链永不返回" class="headerlink" title="2. 原始思维链永不返回"></a>2. 原始思维链永不返回</h3><p>默认 <code>display: &quot;omitted&quot;</code>，thinking 块的内容是空字符串。想看推理摘要要显式设置：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">thinking=&#123;<span class="string">&quot;type&quot;</span>: <span class="string">&quot;adaptive&quot;</span>, <span class="string">&quot;display&quot;</span>: <span class="string">&quot;summarized&quot;</span>&#125;</span><br></pre></td></tr></table></figure><h3 id="3-安全分类器与-refusal-fallback"><a href="#3-安全分类器与-refusal-fallback" class="headerlink" title="3. 安全分类器与 refusal fallback"></a>3. 安全分类器与 refusal fallback</h3><p>Fable 5 对生物研究和网络安全内容有前置安全分类器，被拒的请求返回 HTTP 200 + <code>stop_reason: &quot;refusal&quot;</code>。问题是<strong>良性的相邻工作也可能被误伤</strong>（比如安全工具开发、生命科学任务），所以官方建议默认开启 server-side fallbacks——被拒时同一个请求内自动降级到 Opus 4.8 重跑：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">response = client.beta.messages.create(</span><br><span class="line">    model=<span class="string">&quot;claude-fable-5&quot;</span>,</span><br><span class="line">    max_tokens=<span class="number">16000</span>,</span><br><span class="line">    betas=[<span class="string">&quot;server-side-fallback-2026-06-01&quot;</span>],</span><br><span class="line">    fallbacks=[&#123;<span class="string">&quot;model&quot;</span>: <span class="string">&quot;claude-opus-4-8&quot;</span>&#125;],</span><br><span class="line">    messages=[...],</span><br><span class="line">)</span><br></pre></td></tr></table></figure><h3 id="4-其他硬约束"><a href="#4-其他硬约束" class="headerlink" title="4. 其他硬约束"></a>4. 其他硬约束</h3><ul><li><strong>不支持 assistant prefill</strong>（和 4.6+ 系列一致），用 <code>output_config.format</code> 结构化输出替代</li><li><strong>Sampling 参数全部移除</strong>：<code>temperature</code> &#x2F; <code>top_p</code> &#x2F; <code>top_k</code> 都会 400</li><li><strong>要求 30 天数据保留</strong>：ZDR（零数据保留）组织的所有请求直接 400，排查时别只盯着请求体</li></ul><h2 id="Prompting-风格变化"><a href="#Prompting-风格变化" class="headerlink" title="Prompting 风格变化"></a>Prompting 风格变化</h2><p>这是迁移时最容易被忽略的一点：<strong>为旧模型写的 prompt 往往过于 prescriptive，反而会降低 Fable 5 的输出质量</strong>。</p><p>旧模型需要你把步骤掰开揉碎写清楚，Fable 5 更适合只描述目标和约束，把”怎么做”留给它自己规划。官方建议迁移后做一轮 A&#x2F;B 测试，把老的 step-by-step 脚手架删掉对比效果。</p><p>另外由于单轮任务可能跑很多分钟，工程上要提前规划好 timeout、streaming 和进度展示，把工作流设计成异步 check-in 的模式，而不是阻塞在一个请求里干等。</p><h2 id="要不要升级？"><a href="#要不要升级？" class="headerlink" title="要不要升级？"></a>要不要升级？</h2><p>我的判断框架：</p><ul><li><strong>值得上 Fable 5</strong>：长程自主任务、多 agent 编排、复杂重构、端到端交付物——这些是它独有的能力区间</li><li><strong>留在 Opus 4.8</strong>：常规编码、日常问答、成本敏感的高频调用——一半的价格，能力足够</li><li><strong>留在 Sonnet 4.6 &#x2F; Haiku 4.5</strong>：分类、摘要、大批量的简单任务</li></ul><p>对 Claude Code 用户来说，切到 Fable 5 最直观的感受是：单轮跑得更久但更完整，更愿意开 sub-agent 并行干活，最终总结更清晰。偶尔碰到 refusal 误伤时换个表述或临时降回 Opus 4.8 即可。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/357aa7a7ce38/</id>
    <link href="https://blog.zisheng.pro/posts/357aa7a7ce38/"/>
    <published>2026-07-03T04:00:00.000Z</published>
    <summary>Anthropic 发布了目前能力最强的正式版模型 Claude Fable 5。这篇文章从能力升级、规格定价、API 破坏性变更和 Prompting 风格四个维度，梳理它相比 Opus 4.8 的所有大更新，帮你判断要不要升级、怎么迁移。</summary>
    <title>Claude Fable 5 发布：有哪些大更新？</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="人工智能" scheme="https://blog.zisheng.pro/categories/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/"/>
    <category term="AI" scheme="https://blog.zisheng.pro/tags/AI/"/>
    <category term="Agent" scheme="https://blog.zisheng.pro/tags/Agent/"/>
    <category term="学习路线" scheme="https://blog.zisheng.pro/tags/%E5%AD%A6%E4%B9%A0%E8%B7%AF%E7%BA%BF/"/>
    <category term="职业规划" scheme="https://blog.zisheng.pro/tags/%E8%81%8C%E4%B8%9A%E8%A7%84%E5%88%92/"/>
    <content>
      <![CDATA[<p>最近在整理 AI Agent 方向的知识体系时，我和 GPT 反复聊了几个来回，生成了一张非常系统的 <strong>Agentic System Engineer 专家能力架构图</strong>。这张图把从底层工程基础到顶层专家影响力，拆成了七个层次（L1-L7），同时辅以横向的工程素养和业务素养两条维度。</p><p>作为全栈开发者，我大部分时间在 JavaScript&#x2F;TypeScript 和 Node.js 生态里工作，正在往 AI Agent 方向拓展。这张图恰好给了我一个清晰的坐标系——知道自己现在在哪儿，接下来该往哪儿走。这篇文章既是学习笔记，也是一份面向全栈开发者的成长计划。</p><span id="more"></span><h2 id="能力架构总览"><a href="#能力架构总览" class="headerlink" title="能力架构总览"></a>能力架构总览</h2><p>这张图的核心路径是：<strong>LLM 理解 → Agent 核心 → Runtime &#x2F; Infra → Grounding &amp; Execution → Evolution → 战略影响力</strong>。七个层级层层递进，每个层级下面都有具体的能力点。</p><table><thead><tr><th>层级</th><th>名称</th><th>核心关键词</th></tr></thead><tbody><tr><td>L1</td><td>通用工程与系统基础</td><td>编程语言、算法、OS、网络、数据库、云原生</td></tr><tr><td>L2</td><td>AI &#x2F; LLM 能力基础</td><td>Transformer、Prompt Engineering、Function Calling、多模态、模型评估</td></tr><tr><td>L3</td><td>Agent 核心认知</td><td>Planning、Memory、Tool Use、State&#x2F;Context、长期任务、安全约束</td></tr><tr><td>L4</td><td>Runtime 与基础设施</td><td>Agent Runtime、工作流、调度、Sandbox、Checkpoint、可观测性</td></tr><tr><td>L5</td><td>环境交互与执行能力</td><td>Browser Agent、Vision&#x2F;DOM Grounding、Tool&#x2F;API 编排、RAG、多 Agent 协同、Human-in-the-loop</td></tr><tr><td>L6</td><td>自进化与优化系统</td><td>Eval 评测、Trajectory&#x2F;Trace、反馈闭环、Skill Library、Self-Evolve、RL</td></tr><tr><td>L7</td><td>专家影响力与战略</td><td>技术战略、系统架构、方法论、开源影响力、团队带教</td></tr></tbody></table><p>两个横向维度同样关键：</p><ul><li><strong>横向工程素养</strong>：可靠性、可维护性、性能、成本、安全</li><li><strong>横向业务素养</strong>：产品理解、领域建模、沟通表达、文档写作、业务落地</li></ul><p>下面结合我的实际情况，逐层拆解学习计划。</p><h2 id="L1：通用工程与系统基础（已具备，持续巩固）"><a href="#L1：通用工程与系统基础（已具备，持续巩固）" class="headerlink" title="L1：通用工程与系统基础（已具备，持续巩固）"></a>L1：通用工程与系统基础（已具备，持续巩固）</h2><p>作为全栈开发者，L1 的大部分能力已经覆盖。这里需要重点补充的是：<strong>面向 Agent 系统的工程思维</strong>。</p><ul><li><strong>编程语言</strong>：JavaScript&#x2F;TypeScript 是主力，正在强化 Python（FastAPI、AI Agent 生态），Java 也在学习中。Agent 系统开发中，Python 是 AI 侧的主力，JS&#x2F;TS 适合前端集成和工具链。</li><li><strong>数据结构与算法</strong>：基础已具备，需要补充的是<strong>图遍历、状态空间搜索</strong>等 Agent Planning 相关算法。</li><li><strong>操作系统与网络</strong>：已有基础，重点看<strong>进程隔离、容器化</strong>等与 Sandbox 安全执行相关的内容。</li><li><strong>数据库&#x2F;存储</strong>：关系型和非关系型都有使用经验，需要补充的是<strong>向量数据库</strong>（如 Milvus、Pinecone）和 Agent 记忆持久化方案。</li><li><strong>云原生&#x2F;分布式系统</strong>：Docker、K8s 有基础，需要深入的是<strong>Serverless（FaaS）<strong>和</strong>事件驱动架构</strong>，这对 Agent Runtime 的弹性调度很重要。</li></ul><p><strong>行动项</strong>：</p><ol><li>系统学习向量数据库原理与选型</li><li>深入 Docker 安全隔离机制，为后续 Sandbox 设计打基础</li><li>补充图算法（DFS&#x2F;BFS、A*）在 Agent 路径规划中的应用</li></ol><h2 id="L2：AI-LLM-能力基础（正在强化）"><a href="#L2：AI-LLM-能力基础（正在强化）" class="headerlink" title="L2：AI &#x2F; LLM 能力基础（正在强化）"></a>L2：AI &#x2F; LLM 能力基础（正在强化）</h2><p>这是从传统工程师转向 AI 工程师的关键一层。</p><ul><li><strong>Transformer &#x2F; 推理基础</strong>：理解 Attention 机制、KV Cache、Token 生成过程。不需要推导所有公式，但要理解<strong>模型为什么会产生幻觉、为什么长文本会丢失上下文</strong>。</li><li><strong>Prompt &#x2F; Context Engineering</strong>：已经大量使用，但要体系化。需要掌握 Chain-of-Thought、ReAct、Self-Consistency 等 Prompt 范式。</li><li><strong>Function Calling</strong>：这是 Agent 调用工具的基石。要深入理解不同模型（GPT、Claude、Llama）的 Function Calling 实现差异。</li><li><strong>多模态理解</strong>：Vision 能力正在快速发展，需要了解 GPT-4V、Claude 3 的视觉输入处理机制。</li><li><strong>模型评估</strong>：掌握 BLEU、ROUGE、BERTScore 等传统指标，更要关注<strong>Agent 专用评估方法</strong>（如成功率、任务完成率）。</li><li><strong>推理成本意识</strong>：Token 计费、上下文窗口成本、模型选型策略。生产环境中成本意识决定方案可行性。</li></ul><p><strong>行动项</strong>：</p><ol><li>精读《Attention Is All You Need》，理解 Transformer 核心机制</li><li>系统学习 Prompt Engineering 体系，整理一份 Prompt 模式库</li><li>动手实现一个支持 Function Calling 的 Agent 原型</li><li>对比 GPT-4、Claude 3.5、Llama 3 在相同任务上的成本与效果</li></ol><h2 id="L3：Agent-核心认知（重点突破层）"><a href="#L3：Agent-核心认知（重点突破层）" class="headerlink" title="L3：Agent 核心认知（重点突破层）"></a>L3：Agent 核心认知（重点突破层）</h2><p>这是 Agent 系统的”内核”。如果说 L2 是理解模型，L3 就是理解 Agent 的本质。</p><ul><li><strong>Planning</strong>：Agent 如何拆解任务、制定计划。关键概念包括 Task Decomposition（任务分解）、Hierarchical Planning（层次规划）、ReAct（推理+行动循环）。</li><li><strong>Memory</strong>：短期记忆（上下文窗口管理）、长期记忆（向量存储、知识图谱）。需要理解<strong>记忆的检索、压缩、遗忘机制</strong>。</li><li><strong>Tool Use</strong>：Agent 如何发现、选择、调用工具。MCP（Model Context Protocol）是新兴标准，值得关注。</li><li><strong>State &#x2F; Context 管理</strong>：Agent 的状态机设计、上下文窗口的滑动窗口策略、多轮对话的状态保持。</li><li><strong>长期任务执行</strong>：多步骤任务的持久化执行、错误恢复、断点续传。这是从 Demo 走向生产的关键。</li><li><strong>安全与约束</strong>：输入过滤、输出审查、权限控制、沙箱隔离。Agent 的自主性越高，安全风险越大。</li></ul><p><strong>行动项</strong>：</p><ol><li>用 Python 实现一个 ReAct Agent，包含 Planning + Tool Use + Memory</li><li>研究 LangChain、Mastra 的 Memory 实现，对比不同方案</li><li>学习 MCP 协议，实现一个自定义 MCP Server</li><li>设计一个带状态持久化的 Agent，支持任务中断和恢复</li></ol><h2 id="L4：Runtime-与基础设施（从原型到生产）"><a href="#L4：Runtime-与基础设施（从原型到生产）" class="headerlink" title="L4：Runtime 与基础设施（从原型到生产）"></a>L4：Runtime 与基础设施（从原型到生产）</h2><p>L3 关注的是单个 Agent 的认知能力，L4 关注的是<strong>如何让 Agent 系统在生产环境中稳定运行</strong>。</p><ul><li><strong>Agent Runtime</strong>：Agent 的执行引擎。需要了解 LangChain、Mastra、AutoGen 等框架的 Runtime 设计。</li><li><strong>工作流 &#x2F; 状态机</strong>：用状态机或 DAG 编排 Agent 的执行流程。如 LangGraph、Temporal 等。</li><li><strong>调度与队列</strong>：多 Agent 任务的排队调度、优先级管理、并发控制。</li><li><strong>Sandbox &#x2F; 隔离</strong>：代码执行的安全沙箱（如 E2B、Docker Sandbox）、文件系统隔离、网络隔离。</li><li><strong>Checkpoint &#x2F; Durable Execution</strong>：执行状态持久化，支持故障恢复。Temporal、Durable Objects 等技术。</li><li><strong>可观测性 &#x2F; 监控</strong>：Agent 的调用链路追踪、成本监控、效果评估指标收集。OpenTelemetry、LangSmith 等工具。</li></ul><p><strong>行动项</strong>：</p><ol><li>学习 LangGraph，用状态机方式重构 L3 的 ReAct Agent</li><li>研究 E2B 或 Docker 的代码执行沙箱方案，实现安全代码运行环境</li><li>集成 LangSmith 或 Helicone，建立 Agent 的监控和追踪体系</li><li>学习 Temporal 或类似框架，实现 Durable Execution</li></ol><h2 id="L5：环境交互与执行能力（从对话到行动）"><a href="#L5：环境交互与执行能力（从对话到行动）" class="headerlink" title="L5：环境交互与执行能力（从对话到行动）"></a>L5：环境交互与执行能力（从对话到行动）</h2><p>这是 Agent “落地”的关键层。Agent 不仅要会思考，还要能与真实世界交互。</p><ul><li><strong>Browser Agent</strong>：用 Agent 控制浏览器完成网页操作。browser-use、Playwright、Puppeteer 是核心工具。UI-TARS 是新兴的视觉驱动方案。</li><li><strong>Vision &#x2F; DOM Grounding</strong>：让 Agent 理解网页视觉内容和 DOM 结构。结合多模态模型的视觉能力。</li><li><strong>Tool &#x2F; API Orchestration</strong>：编排多个 API 调用，处理依赖关系、错误回退。MCP 协议在这里扮演重要角色。</li><li><strong>RAG &#x2F; 检索增强</strong>：结合向量检索和 LLM，让 Agent 访问外部知识。Sirchmunk（Embedding-Free RAG）是值得关注的新方向。</li><li><strong>多 Agent 协同</strong>：多个 Agent 分工协作，如经理 Agent 分配任务、专家 Agent 执行。AutoGen、CrewAI 等框架。</li><li><strong>Human-in-the-loop</strong>：关键决策点引入人工确认，平衡自动化和可控性。</li></ul><p><strong>行动项</strong>：</p><ol><li>用 browser-use 实现一个自动化的网页数据抓取 Agent</li><li>集成 RAG 能力，让 Agent 能查询文档知识库（尝试 Sirchmunk）</li><li>用 AutoGen 或 CrewAI 搭建一个多 Agent 协作系统</li><li>实现 Human-in-the-loop 机制，设计关键决策的人工确认流程</li></ol><h2 id="L6：自进化与优化系统（从静态到动态）"><a href="#L6：自进化与优化系统（从静态到动态）" class="headerlink" title="L6：自进化与优化系统（从静态到动态）"></a>L6：自进化与优化系统（从静态到动态）</h2><p>让 Agent 系统能够持续学习和改进，而不是每次都从零开始。</p><ul><li><strong>Eval 评测体系</strong>：建立 Agent 任务的评估数据集和指标。这是优化的前提。</li><li><strong>Trajectory &#x2F; Trace</strong>：记录 Agent 的完整执行轨迹，用于分析和调试。</li><li><strong>反馈闭环</strong>：将执行结果反馈给 Agent，形成学习循环。成功&#x2F;失败案例的积累。</li><li><strong>Skill Library</strong>：Agent 学习的技能库，可复用的工具组合和策略模板。</li><li><strong>Self-Evolve</strong>：Agent 自主改进自身 Prompt、工具选择策略。</li><li><strong>策略优化 &#x2F; RL</strong>：用强化学习优化 Agent 的决策策略。DPO、PPO 等算法在 Agent 优化中的应用。</li></ul><p><strong>行动项</strong>：</p><ol><li>为自己的 Agent 项目建立 Eval 数据集和评估流程</li><li>设计反馈收集机制，记录每次任务的执行轨迹和结果</li><li>实现 Skill Library，积累可复用的 Agent 技能模板</li><li>了解 DPO（Direct Preference Optimization）在 Agent 优化中的应用</li></ol><h2 id="L7：专家影响力与战略（长期目标）"><a href="#L7：专家影响力与战略（长期目标）" class="headerlink" title="L7：专家影响力与战略（长期目标）"></a>L7：专家影响力与战略（长期目标）</h2><p>这是从工程师到专家、从技术执行到技术领导的跨越。</p><ul><li><strong>技术战略</strong>：Agent 技术的演进方向判断、技术选型决策、团队技术路线规划。</li><li><strong>系统架构</strong>：设计大规模 Agent 系统的整体架构，考虑扩展性、可靠性、成本。</li><li><strong>方法论输出</strong>：沉淀 Agent 开发的工程方法论，形成可复用的框架和最佳实践。</li><li><strong>开源影响力</strong>：参与或主导开源 Agent 项目，建立技术影响力。FastGPT、Mastra 等。</li><li><strong>团队带教</strong>：培养团队成员的 Agent 开发能力，建立知识传承机制。</li><li><strong>跨部门协同</strong>：与产品、运营、业务团队协作，推动 Agent 技术在实际业务中落地。</li></ul><p><strong>行动项</strong>：</p><ol><li>在 GitHub 上持续贡献 Agent 相关开源项目</li><li>沉淀 Agent 开发方法论，整理成内部文档或开源项目</li><li>主动承担团队 Agent 方向的技术规划和选型工作</li><li>结合飞猪业务场景，探索 Agent 在旅游&#x2F;出行领域的落地机会</li></ol><h2 id="横向素养：贯穿始终的两条隐线"><a href="#横向素养：贯穿始终的两条隐线" class="headerlink" title="横向素养：贯穿始终的两条隐线"></a>横向素养：贯穿始终的两条隐线</h2><h3 id="横向工程素养"><a href="#横向工程素养" class="headerlink" title="横向工程素养"></a>横向工程素养</h3><p>Agent 系统对工程素养的要求比传统应用更高：</p><ul><li><strong>可靠性</strong>：Agent 的自主性意味着错误可能被放大，需要有完善的熔断、降级机制。</li><li><strong>可维护性</strong>：Agent 的 Prompt 和策略需要版本管理，配置变更需要可追踪。</li><li><strong>性能</strong>：LLM 调用是瓶颈，需要优化调用次数、使用缓存、并行化非依赖步骤。</li><li><strong>成本</strong>：Token 消耗是持续成本，需要在效果和成本间做权衡。</li><li><strong>安全</strong>：Agent 能执行代码、访问外部系统，安全风险成倍放大。</li></ul><h3 id="横向业务素养"><a href="#横向业务素养" class="headerlink" title="横向业务素养"></a>横向业务素养</h3><p>技术最终要服务于业务：</p><ul><li><strong>产品理解</strong>：理解 Agent 产品的用户场景和价值主张。</li><li><strong>领域建模</strong>：将业务领域知识结构化，供 Agent 理解和使用。</li><li><strong>沟通表达</strong>：Agent 系统的复杂性要求清晰的文档和沟通能力。</li><li><strong>文档写作</strong>：Prompt 工程本质上就是”面向模型的文档写作”。</li><li><strong>业务落地</strong>：从 POC 到生产环境，推动 Agent 在真实业务中产生价值。</li></ul><h2 id="个人学习路线与时间安排"><a href="#个人学习路线与时间安排" class="headerlink" title="个人学习路线与时间安排"></a>个人学习路线与时间安排</h2><p>基于以上分析，结合日常工作节奏，制定如下学习计划：</p><h3 id="第一阶段（1-3-个月）：夯实基础"><a href="#第一阶段（1-3-个月）：夯实基础" class="headerlink" title="第一阶段（1-3 个月）：夯实基础"></a>第一阶段（1-3 个月）：夯实基础</h3><p><strong>重点：L2 + L3</strong></p><ul><li>周 1-2：系统学习 Transformer 原理，精读 Attention Is All You Need</li><li>周 3-4：Prompt Engineering 体系化学习，整理 Prompt 模式库</li><li>周 5-6：Function Calling 实现，动手写 Agent 原型</li><li>周 7-8：Memory 系统设计，实现短期+长期记忆</li><li>周 9-10：ReAct &#x2F; Planning 模式深入</li><li>周 11-12：安全与约束机制设计</li></ul><p><strong>产出物</strong>：一个具备 Planning + Memory + Tool Use 的 Agent 原型</p><h3 id="第二阶段（4-6-个月）：走向生产"><a href="#第二阶段（4-6-个月）：走向生产" class="headerlink" title="第二阶段（4-6 个月）：走向生产"></a>第二阶段（4-6 个月）：走向生产</h3><p><strong>重点：L4 + L5</strong></p><ul><li>月 4：学习 LangGraph &#x2F; Mastra，用状态机重构 Agent</li><li>月 5：集成 Sandbox 安全执行环境</li><li>月 6：建立可观测性体系（追踪 + 监控 + 日志）</li><li>月 7：Browser Agent 实战（browser-use &#x2F; UI-TARS）</li><li>月 8：RAG 系统集成（向量检索 + Embedding-Free 方案）</li><li>月 9：多 Agent 协同系统搭建</li></ul><p><strong>产出物</strong>：一个具备完整 Runtime + 环境交互能力的生产级 Agent 系统</p><h3 id="第三阶段（7-9-个月）：持续进化"><a href="#第三阶段（7-9-个月）：持续进化" class="headerlink" title="第三阶段（7-9 个月）：持续进化"></a>第三阶段（7-9 个月）：持续进化</h3><p><strong>重点：L6 + 横向素养</strong></p><ul><li>月 10：建立 Eval 评测体系</li><li>月 11：实现反馈闭环和 Skill Library</li><li>月 12：成本优化与性能调优</li><li>持续：文档写作、方法论沉淀、开源贡献</li></ul><p><strong>产出物</strong>：可自进化的 Agent 系统 + 一套完整的方法论文档</p><h3 id="第四阶段（10-12-个月）：建立影响力"><a href="#第四阶段（10-12-个月）：建立影响力" class="headerlink" title="第四阶段（10-12 个月）：建立影响力"></a>第四阶段（10-12 个月）：建立影响力</h3><p><strong>重点：L7</strong></p><ul><li>主导团队 Agent 技术方向</li><li>开源项目持续贡献</li><li>结合业务场景推动 Agent 落地</li></ul><h2 id="学习资源推荐"><a href="#学习资源推荐" class="headerlink" title="学习资源推荐"></a>学习资源推荐</h2><h3 id="基础理论"><a href="#基础理论" class="headerlink" title="基础理论"></a>基础理论</h3><ul><li>《Attention Is All You Need》— Transformer 起源</li><li>《ReAct: Synergizing Reasoning and Acting in Language Models》— Agent 基础范式</li><li>《Reflexion: Self-Reflective Agents》— 自我反思 Agent</li><li>《Let’s Verify Step by Step》— 推理过程验证</li></ul><h3 id="框架与工具"><a href="#框架与工具" class="headerlink" title="框架与工具"></a>框架与工具</h3><ul><li><strong>LangChain &#x2F; LangGraph</strong>：Agent 开发主流框架</li><li><strong>Mastra</strong>：新兴的 TypeScript-first Agent 框架</li><li><strong>AgentScope</strong>：阿里巴巴开源的多 Agent 框架，支持多模态、多智能体协同，内置丰富的对话管理和工具编排能力</li><li><strong>browser-use</strong>：浏览器自动化 Agent</li><li><strong>MCP（Model Context Protocol）</strong>：工具调用标准协议</li><li><strong>E2B</strong>：代码执行沙箱</li><li><strong>Temporal</strong>：Durable Execution 框架</li></ul><h3 id="GitHub-课程与实战仓库"><a href="#GitHub-课程与实战仓库" class="headerlink" title="GitHub 课程与实战仓库"></a>GitHub 课程与实战仓库</h3><p>以下仓库按学习阶段分类，从入门到进阶，覆盖理论与实战：</p><h4 id="系统入门课程"><a href="#系统入门课程" class="headerlink" title="系统入门课程"></a>系统入门课程</h4><table><thead><tr><th>仓库</th><th>层级对应</th><th>简介</th></tr></thead><tbody><tr><td><a href="https://github.com/datawhalechina/hello-agents">datawhalechina&#x2F;hello-agents</a></td><td>L2-L5</td><td>Datawhale 出品的系统教程（6.3k+ Star），中文社区最完善的 Agent 入门课程。5 部分 16 章：Agent 基础 → 手写 Agent 框架（ReAct&#x2F;Plan-and-Solve）→ 低代码平台（Coze&#x2F;Dify&#x2F;n8n）→ 进阶主题（Memory&#x2F;RAG、MCP&#x2F;A2A&#x2F;ANP、Agentic-RL）→ 实战项目（智能出行助手、DeepResearch Agent、Cyber Town）。含自研 <code>hello_agents</code> 教学框架，从第一性原理构建 Agent</td></tr><tr><td><a href="https://github.com/microsoft/ai-agents-for-beginners">microsoft&#x2F;ai-agents-for-beginners</a></td><td>L2-L4</td><td>微软官方 12 课入门课程，含视频、文档和代码。覆盖 Tool Use、Agentic RAG、多 Agent 设计模式、生产部署、MCP&#x2F;A2A 协议。使用 Azure AI Foundry、Semantic Kernel、AutoGen</td></tr><tr><td><a href="https://github.com/GeneArnold/AI-Agent-Engineering-Course">GeneArnold&#x2F;AI-Agent-Engineering-Course</a></td><td>L2-L5</td><td>7 周渐进式课程，专为 Claude Code 辅助学习设计。从单 Agent → Memory Agent → 多工具 Agent → 多 Agent 系统 → 视觉识别 Agent，含 MCP 覆盖</td></tr><tr><td><a href="https://github.com/zkzkGamal/Agentic-AI-Tutorial">zkzkGamal&#x2F;Agentic-AI-Tutorial</a></td><td>L2-L4</td><td>5 章综合教程：LLM Provider 基础 → LangChain 编排 → RAG&#x2F;Memory 基础设施 → Agent Graph 模式（ReAct、Human-in-the-loop）→ LangGraph + MCP Runtime</td></tr><tr><td><a href="https://github.com/NirDiamant/GenAI_Agents">NirDiamant&#x2F;GenAI_Agents</a></td><td>L3-L5</td><td>45+ 个 Jupyter Notebook 项目，从基础到高级，每个项目含详细说明和可运行代码。覆盖 RAG、多 Agent、工具调用等场景</td></tr></tbody></table><h4 id="实战工作坊（项目驱动）"><a href="#实战工作坊（项目驱动）" class="headerlink" title="实战工作坊（项目驱动）"></a>实战工作坊（项目驱动）</h4><table><thead><tr><th>仓库</th><th>层级对应</th><th>简介</th></tr></thead><tbody><tr><td><a href="https://github.com/iusztinpaul/designing-real-world-ai-agents-workshop">iusztinpaul&#x2F;designing-real-world-ai-agents-workshop</a></td><td>L4-L5</td><td>生产级多 Agent 系统实战：构建 Deep Research Agent + LinkedIn 写作工作流，以 MCP Server 方式提供服务。含代码、PPT、视频和自学路径</td></tr><tr><td><a href="https://github.com/antonacio/intro-to-ai-agents">antonacio&#x2F;intro-to-ai-agents</a></td><td>L3-L4</td><td>手把手构建和编排 Agent 系统：Research Agent、RAG Agent、Web Search Agent、Math Agent，适合快速上手</td></tr><tr><td><a href="https://github.com/coleam00/ai-agents-masterclass">coleam00&#x2F;ai-agents-masterclass</a></td><td>L3-L5</td><td>YouTube 视频系列的配套代码，逐步构建实用 Agent，适合跟着视频边做边学</td></tr><tr><td><a href="https://github.com/ed-donner/agents">ed-donner&#x2F;agents</a></td><td>L2-L4</td><td>6 周 Agent 工程课程，从编码到部署的完整流程</td></tr></tbody></table><h4 id="从零实现（不依赖框架）"><a href="#从零实现（不依赖框架）" class="headerlink" title="从零实现（不依赖框架）"></a>从零实现（不依赖框架）</h4><table><thead><tr><th>仓库</th><th>层级对应</th><th>简介</th></tr></thead><tbody><tr><td><a href="https://github.com/pguso/ai-agents-from-scratch">pguso&#x2F;ai-agents-from-scratch</a></td><td>L2-L3</td><td>用 Node.js + 本地 LLM（node-llama-cpp）从零实现 Agent，理解底层原理。无需 API Key，完全本地运行。配套网站 <a href="https://agentsfromscratch.com/">agentsfromscratch.com</a></td></tr><tr><td><a href="https://github.com/proflead/how-to-build-ai-agents-from-scratch">proflead&#x2F;how-to-build-ai-agents-from-scratch</a></td><td>L3</td><td>基于 Google ADK 和 Gemini 2.5 Flash Lite，构建 Research → Summarizer → Coordinator 多 Agent 系统</td></tr><tr><td><a href="https://github.com/SRafi007/ai-tutorial-crew">SRafi007&#x2F;ai-tutorial-crew</a></td><td>L3-L4</td><td>CrewAI + Ollama 本地 LLM 实现多 Agent 协作：自动研究、写作、审阅 Python 教程。零 API 成本</td></tr></tbody></table><h4 id="Awesome-Lists（资源导航）"><a href="#Awesome-Lists（资源导航）" class="headerlink" title="Awesome Lists（资源导航）"></a>Awesome Lists（资源导航）</h4><table><thead><tr><th>仓库</th><th>用途</th></tr></thead><tbody><tr><td><a href="https://github.com/artnitolog/awesome-agent-learning">artnitolog&#x2F;awesome-agent-learning</a></td><td>Agent 学习资源汇总，含课程、论文、框架教程、评测基准</td></tr><tr><td><a href="https://github.com/e2b-dev/awesome-ai-agents">e2b-dev&#x2F;awesome-ai-agents</a></td><td>AI Agent 框架、库、研究论文的精选列表，区分开源和闭源方案</td></tr><tr><td><a href="https://github.com/punkpeye/awesome-mcp-servers">punkpeye&#x2F;awesome-mcp-servers</a></td><td>按类别整理的 MCP Server 集合（浏览器自动化、代码执行、云服务等）</td></tr><tr><td><a href="https://github.com/Shubhamsaboo/awesome-llm-apps">Shubhamsaboo&#x2F;awesome-llm-apps</a></td><td>LLM 应用集合，含 Agent、RAG、MCP 等实际项目</td></tr></tbody></table><h3 id="评估与优化"><a href="#评估与优化" class="headerlink" title="评估与优化"></a>评估与优化</h3><ul><li><strong>LangSmith</strong>：Agent 监控和评估平台</li><li><strong>Helicone</strong>：LLM 可观测性工具</li><li><strong>OpenAI Evals</strong>：模型评估框架</li></ul><h3 id="博主与频道推荐"><a href="#博主与频道推荐" class="headerlink" title="博主与频道推荐"></a>博主与频道推荐</h3><p>持续跟踪 Agent 领域的最新动态，除了文档和论文，关注一线从业者的分享同样重要。以下按方向分类推荐：</p><h4 id="X-Twitter"><a href="#X-Twitter" class="headerlink" title="X (Twitter)"></a>X (Twitter)</h4><table><thead><tr><th>博主</th><th>方向</th><th>简介</th></tr></thead><tbody><tr><td><a href="https://x.com/hwchase17">@hwchase17</a></td><td>Agent 框架</td><td>LangChain &#x2F; LangGraph 创始人，Agent 框架演进的权威声音</td></tr><tr><td><a href="https://x.com/karpathy">@karpathy</a></td><td>AI 基础</td><td>Andrej Karpathy，AI 教育内容标杆，讲解深入浅出</td></tr><tr><td><a href="https://x.com/swyx">@swyx</a></td><td>AI 工程化</td><td>Shawn Wang，Latent Space 播客主理人，AI Engineering 趋势追踪</td></tr><tr><td><a href="https://x.com/goodside">@goodside</a></td><td>Prompt Engineering</td><td>Riley Goodside，OpenAI Prompt Engineering 专家</td></tr><tr><td><a href="https://x.com/jxnlco">@jxnlco</a></td><td>LLM 工程化</td><td>Jason Liu，OpenAI 工程师，LLM 工程实践最佳</td></tr><tr><td><a href="https://x.com/gregpr07">@gregpr07</a></td><td>Browser Agent</td><td>browser-use 联合创始人，第一手更新和实现思路</td></tr><tr><td><a href="https://x.com/karpulix">@karpulix</a></td><td>多 Agent 协同</td><td>CrewAI 创始人，多 Agent 协作的实践者</td></tr><tr><td><a href="https://x.com/rlancemartin">@rlancemartin</a></td><td>RAG</td><td>LangChain RAG 负责人，RAG 技术演进第一手资料</td></tr><tr><td><a href="https://x.com/BeibinLi">@BeibinLi</a></td><td>多 Agent 框架</td><td>AgentScope（阿里开源）核心作者</td></tr><tr><td><a href="https://x.com/simonw">@simonw</a></td><td>工具集成</td><td>Simon Willison，AI 工程化和工具集成领域的多产作者</td></tr></tbody></table><h4 id="YouTube"><a href="#YouTube" class="headerlink" title="YouTube"></a>YouTube</h4><table><thead><tr><th>频道</th><th>方向</th><th>简介</th></tr></thead><tbody><tr><td><a href="https://www.youtube.com/@AndrejKarpathy">Andrej Karpathy</a></td><td>AI 基础</td><td>从 Transformer 到 GPT 的底层原理讲解，适合夯实 L2</td></tr><tr><td><a href="https://www.youtube.com/@Deeplearningai">DeepLearning.AI</a></td><td>系统课程</td><td>Andrew Ng 的短课程系列，Prompt Engineering、RAG、Agent 都有</td></tr><tr><td><a href="https://www.youtube.com/@SamWitteveen">Sam Witteveen</a></td><td>LangChain 实战</td><td>Google Developer Expert，大量 LangGraph 实战教程</td></tr><tr><td><a href="https://www.youtube.com/@jamesbriggs">James Briggs</a></td><td>RAG &#x2F; 向量检索</td><td>Pinecone 开发者，向量数据库和 RAG 最佳实践</td></tr><tr><td><a href="https://www.youtube.com/@mastra-ai">Mastra</a></td><td>TypeScript Agent</td><td>官方频道，TypeScript-first Agent 框架，技术栈匹配度高</td></tr><tr><td><a href="https://www.youtube.com/@LatentSpacePodcast">Latent Space</a></td><td>播客访谈</td><td>swyx 主理，每期访谈一线从业者，了解行业真实进展</td></tr><tr><td><a href="https://www.youtube.com/@aiexplained-official">AI Explained</a></td><td>行业动态</td><td>每日 AI 新闻速递，适合快速了解行业热点</td></tr></tbody></table><p><strong>关注策略建议</strong>：日常刷 X 追踪框架更新和行业热点（@hwchase17、@gregpr07、@swyx），周末看 YouTube 深度内容夯实基础（Andrej Karpathy、DeepLearning.AI），项目驱动时跟着实战教程边做边学（Sam Witteveen、James Briggs）。</p><h2 id="每日2小时学习规划"><a href="#每日2小时学习规划" class="headerlink" title="每日2小时学习规划"></a>每日2小时学习规划</h2><p>以上路线图覆盖 12 个月，但大多数在职开发者每天能抽出的时间有限。以下是一份基于<strong>每天 2 小时</strong>、以<strong>工作日为主</strong>的执行方案，核心原则是<strong>小步快跑、项目驱动、周末做整合</strong>。</p><h3 id="时间块分配"><a href="#时间块分配" class="headerlink" title="时间块分配"></a>时间块分配</h3><table><thead><tr><th>时段</th><th>时长</th><th>内容</th></tr></thead><tbody><tr><td><strong>理论学习</strong></td><td>40 min</td><td>读文档、课程章节或论文，理解概念并记关键笔记</td></tr><tr><td><strong>动手实践</strong></td><td>60 min</td><td>写代码、跑示例、调 Prompt，必须产出可运行的代码</td></tr><tr><td><strong>复盘整理</strong></td><td>20 min</td><td>写学习日志、整理代码片段、画思维导图，沉淀为可复用知识</td></tr></tbody></table><p>建议固定时间段（如早 7:00-9:00 或晚 21:00-23:00），降低启动成本。</p><h3 id="周末弹性安排"><a href="#周末弹性安排" class="headerlink" title="周末弹性安排"></a>周末弹性安排</h3><p>周末不强制加量，但建议完成两件事：</p><ol><li><strong>周项目冲刺</strong>（2-3 小时）：把本周零散实践整合成一个完整功能点</li><li><strong>补进度&#x2F;深度阅读</strong>：处理本周遗留，或精读一篇论文</li></ol><h3 id="第一阶段（1-3-月）：L2-L3，打地基"><a href="#第一阶段（1-3-月）：L2-L3，打地基" class="headerlink" title="第一阶段（1-3 月）：L2 + L3，打地基"></a>第一阶段（1-3 月）：L2 + L3，打地基</h3><p><strong>目标</strong>：产出一个具备 Planning + Memory + Tool Use 的 Agent 原型。</p><table><thead><tr><th>周次</th><th>理论学习（40 min）</th><th>动手实践（60 min）</th><th>关键产出</th></tr></thead><tbody><tr><td>1-2</td><td>跟读 <code>hello-agents</code> Part 1（Agent &amp; LLM 基础），配合 Andrej Karpathy 的 Transformer 视频</td><td>用 Python 复现简单的 Attention 计算；搭建本地开发环境（uv + Python 3.11）</td><td>环境就绪 + Attention 草稿</td></tr><tr><td>3-4</td><td>系统学习 Prompt Engineering，整理 Chain-of-Thought、ReAct、Self-Consistency 模板</td><td>给同一个任务写 3 种不同 Prompt，对比输出效果；记录到个人 Prompt 模式库</td><td>Prompt 模式库（10+ 模板）</td></tr><tr><td>5-6</td><td>精读 <code>hello-agents</code> Part 2（手写 Agent 框架），理解 ReAct 循环</td><td>实现一个最简单的 ReAct Agent：输入 → Thought → Action → Observation → 输出</td><td>ReAct Agent v0.1</td></tr><tr><td>7-8</td><td>学习 Memory 设计：短期记忆（上下文窗口）+ 长期记忆（向量存储）</td><td>给 Agent 加短期对话历史；接入 ChromaDB&#x2F;FAISS 做长期记忆检索</td><td>带记忆的 Agent v0.2</td></tr><tr><td>9-10</td><td>学习 Tool Use 和 MCP 协议；读 <code>hello-agents</code> 的 MCP&#x2F;A2A 章节</td><td>给 Agent 注册 2-3 个工具（计算器、搜索、文件读取）；尝试写一个简易 MCP Server</td><td>带工具调用的 Agent v0.3</td></tr><tr><td>11-12</td><td>学习安全约束：输入过滤、输出审查、权限控制</td><td>给 Agent 加输入校验、输出敏感词过滤、工具调用权限白名单</td><td>带安全约束的 Agent v0.4</td></tr></tbody></table><p><strong>阶段产出</strong>：一个可运行的 ReAct Agent（Python），支持 Memory + Tool Use + 基础安全。</p><h3 id="第二阶段（4-6-月）：L4-L5，走通生产"><a href="#第二阶段（4-6-月）：L4-L5，走通生产" class="headerlink" title="第二阶段（4-6 月）：L4 + L5，走通生产"></a>第二阶段（4-6 月）：L4 + L5，走通生产</h3><p><strong>目标</strong>：把原型升级为具备 Runtime + 环境交互能力的系统。</p><table><thead><tr><th>月次</th><th>理论学习（40 min）</th><th>动手实践（60 min）</th><th>关键产出</th></tr></thead><tbody><tr><td>4</td><td>学习 LangGraph 状态机；读 <code>microsoft/ai-agents-for-beginners</code> 的 Design Patterns</td><td>用 LangGraph 重写第一阶段的 Agent，用状态机管理执行流程</td><td>LangGraph 版 Agent</td></tr><tr><td>5</td><td>研究 E2B&#x2F;Docker Sandbox；学习 Temporal&#x2F;Durable Execution</td><td>给 Agent 加一个代码执行沙箱；实现任务断点续传</td><td>带 Sandbox 的 Agent</td></tr><tr><td>6</td><td>学习可观测性：OpenTelemetry、LangSmith&#x2F;Helicone</td><td>接入 LangSmith，记录每次调用的 Token 消耗、延迟、执行轨迹</td><td>可监控的 Agent 系统</td></tr><tr><td>7</td><td>学习 browser-use&#x2F;UI-TARS；读 <code>hello-agents</code> 的实战项目章节</td><td>用 browser-use 实现一个自动登录 + 抓数据的 Agent</td><td>Browser Agent</td></tr><tr><td>8</td><td>学习 RAG：向量检索 + Sirchmunk（Embedding-Free）</td><td>给 Agent 接入文档知识库，支持基于自有文档的问答</td><td>RAG-enabled Agent</td></tr><tr><td>9</td><td>学习多 Agent 协同：AutoGen&#x2F;CrewAI；读 <code>hello-agents</code> 的 Cyber Town</td><td>搭建 2-3 个 Agent 协作：Planner + Worker + Critic</td><td>多 Agent 协作系统</td></tr></tbody></table><p><strong>阶段产出</strong>：一个生产级 Agent 系统，具备状态机、沙箱、可观测性、Browser 能力、RAG、多 Agent 协同。</p><h3 id="第三阶段（7-9-月）：L6-横向素养，持续进化"><a href="#第三阶段（7-9-月）：L6-横向素养，持续进化" class="headerlink" title="第三阶段（7-9 月）：L6 + 横向素养，持续进化"></a>第三阶段（7-9 月）：L6 + 横向素养，持续进化</h3><p><strong>目标</strong>：让系统能自我改进，沉淀方法论。</p><table><thead><tr><th>月次</th><th>理论学习（40 min）</th><th>动手实践（60 min）</th><th>关键产出</th></tr></thead><tbody><tr><td>10</td><td>学习 Eval 体系：成功率、任务完成率、轨迹质量</td><td>为自己的 Agent 设计 10-20 个测试用例，建立自动化评估</td><td>Eval 数据集 + 评估脚本</td></tr><tr><td>11</td><td>学习反馈闭环和 Skill Library 设计</td><td>实现执行轨迹记录；把常用工具组合抽象成可复用的 Skill 模板</td><td>Skill Library（5+ 模板）</td></tr><tr><td>12</td><td>学习成本优化：缓存策略、模型降级、并行化</td><td>给系统加 Redis 缓存、非关键步骤用便宜模型、并行调用无依赖工具</td><td>成本优化后的系统</td></tr></tbody></table><p><strong>阶段产出</strong>：可自进化的 Agent 系统 + 一套内部方法论文档。</p><h3 id="第四阶段（10-12-月）：L7，建立影响力"><a href="#第四阶段（10-12-月）：L7，建立影响力" class="headerlink" title="第四阶段（10-12 月）：L7，建立影响力"></a>第四阶段（10-12 月）：L7，建立影响力</h3><p><strong>目标</strong>：从个人学习转向团队价值和开源贡献。</p><table><thead><tr><th>行动</th><th>时间投入</th><th>具体做法</th></tr></thead><tbody><tr><td>开源贡献</td><td>每天 60 min</td><td>给 <code>hello-agents</code>、<code>Mastra</code>、<code>browser-use</code> 等项目提 PR（文档改进、Bug 修复、小功能）</td></tr><tr><td>方法论沉淀</td><td>每天 40 min</td><td>把学习笔记整理成博客文章或内部技术分享</td></tr><tr><td>业务探索</td><td>周末 3-4 小时</td><td>结合飞猪业务，设计一个 Agent Demo（如智能行程规划、客服辅助）</td></tr></tbody></table><h3 id="关键执行原则"><a href="#关键执行原则" class="headerlink" title="关键执行原则"></a>关键执行原则</h3><ol><li><strong>先跑起来再完美</strong>：第一周就写出能运行的代码，不要花两周只读理论</li><li><strong>一课一代码</strong>：每学完一个概念（如 ReAct、Memory），当天就必须有代码产出</li><li><strong>用 Git 管理</strong>：从第一天就建一个 <code>agent-learning</code> 仓库，每周末 commit</li><li><strong>输出倒逼输入</strong>：每两周写一篇学习笔记（哪怕只是 README），强迫自己消化</li><li><strong>卡住先跳过</strong>：2 小时很宝贵，卡超过 30 分钟就记 TODO，先往下走</li></ol><h3 id="各阶段推荐资源组合"><a href="#各阶段推荐资源组合" class="headerlink" title="各阶段推荐资源组合"></a>各阶段推荐资源组合</h3><table><thead><tr><th>阶段</th><th>主力课程</th><th>辅助资源</th><th>备用</th></tr></thead><tbody><tr><td>第一阶段</td><td><code>hello-agents</code> Part 1-2</td><td>Andrej Karpathy Transformer 视频</td><td><code>microsoft/ai-agents-for-beginners</code> 前 5 课</td></tr><tr><td>第二阶段</td><td><code>microsoft/ai-agents-for-beginners</code> 后 7 课 + LangGraph 官方文档</td><td><code>iusztinpaul/designing-real-world-ai-agents-workshop</code></td><td><code>zkzkGamal/Agentic-AI-Tutorial</code></td></tr><tr><td>第三阶段</td><td><code>hello-agents</code> Part 3-4</td><td>LangSmith 官方文档</td><td>OpenAI Evals 源码</td></tr><tr><td>第四阶段</td><td>开源项目源码阅读</td><td>Latent Space 播客</td><td>X 上追踪 @hwchase17、@gregpr07</td></tr></tbody></table><p>按这个节奏，12 个月后你会有一条清晰的成长轨迹：<strong>从”能写 Agent”到”能设计 Agent 系统”再到”能带团队做 Agent 落地”</strong>。每天 2 小时，关键是<strong>不中断</strong>。</p><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><p>成为 Agentic System Engineer 不是一蹴而就的，需要从工程基础到 AI 认知，再到系统架构，层层递进。这张图的价值在于它提供了一个完整的地图——你不需要一开始就掌握所有内容，但你需要知道每个能力点的位置和重要性。</p><p>作为全栈开发者，最大的优势是工程基础扎实、理解系统全貌。最大的挑战是从”确定性编程”转向”概率性系统”的思维转变。Agent 系统的行为不是完全可控的，需要在不确定性中做工程化。</p><p>这条路上没有捷径，但有地图。这张图就是我的地图。接下来，就是一步步走。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/7e7297c52e91/</id>
    <link href="https://blog.zisheng.pro/posts/7e7297c52e91/"/>
    <published>2026-06-08T14:00:00.000Z</published>
    <summary>从这张能力架构图出发，结合全栈开发背景，规划一条系统化的 Agent 工程师成长路径。</summary>
    <title>Agentic System Engineer 成长路线图与学习指南</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="云原生" scheme="https://blog.zisheng.pro/categories/%E4%BA%91%E5%8E%9F%E7%94%9F/"/>
    <category term="阿里云" scheme="https://blog.zisheng.pro/tags/%E9%98%BF%E9%87%8C%E4%BA%91/"/>
    <category term="Docker" scheme="https://blog.zisheng.pro/tags/Docker/"/>
    <category term="容器" scheme="https://blog.zisheng.pro/tags/%E5%AE%B9%E5%99%A8/"/>
    <category term="Kubernetes" scheme="https://blog.zisheng.pro/tags/Kubernetes/"/>
    <category term="PouchContainer" scheme="https://blog.zisheng.pro/tags/PouchContainer/"/>
    <content>
      <![CDATA[<p>如果你在阿里云或阿里内部系统中看到过”Pouch”这个词，可能会好奇它和 Docker 是什么关系。本文从历史、架构、核心特性三个维度，系统地介绍阿里巴巴开源的企业级容器运行时 PouchContainer。</p><h2 id="背景：从-T4-到-PouchContainer"><a href="#背景：从-T4-到-PouchContainer" class="headerlink" title="背景：从 T4 到 PouchContainer"></a>背景：从 T4 到 PouchContainer</h2><p>2011 年，阿里巴巴内部开始研发一套基于 LXC 的容器系统，代号 <strong>T4</strong>，用于支撑淘宝的海量在线交易。这比 Docker 公开发布（2013 年）还早了两年。</p><p>随着 Docker 的兴起和 OCI（Open Container Initiative）规范的确立，阿里将 Docker 的镜像格式和生态整合进 T4，逐步演化为兼容 OCI 规范的 <strong>PouchContainer</strong>，并于 2017 年 11 月正式对外开源（Apache 2.0）。</p><blockquote><p>GitHub 地址：<a href="https://github.com/AliyunContainerService/pouch">AliyunContainerService&#x2F;pouch</a></p></blockquote><p>Pouch 的设计目标不是”再做一个 Docker”，而是在<strong>阿里超大规模数据中心</strong>（几十万台机器、数亿容器实例）场景下，解决 Docker 难以满足的企业级痛点。</p><h2 id="与-Docker-的核心差异"><a href="#与-Docker-的核心差异" class="headerlink" title="与 Docker 的核心差异"></a>与 Docker 的核心差异</h2><table><thead><tr><th>维度</th><th>Docker</th><th>PouchContainer</th></tr></thead><tbody><tr><td>容器模型</td><td>单进程原则</td><td>支持多进程（Rich Container）</td></tr><tr><td>隔离手段</td><td>仅 cgroups + namespace</td><td>kernel + hypervisor 双轨</td></tr><tr><td>&#x2F;proc 视图</td><td>与宿主共享</td><td>通过 lxcfs 隔离</td></tr><tr><td>镜像分发</td><td>Registry 直拉</td><td>集成 Dragonfly P2P 分发</td></tr><tr><td>CRI 集成</td><td>需要 containerd-shim</td><td>CRI Manager 内嵌 Daemon</td></tr><tr><td>内核要求</td><td>较新内核</td><td>兼容低至 Linux 2.6.32</td></tr></tbody></table><h2 id="四大核心特性"><a href="#四大核心特性" class="headerlink" title="四大核心特性"></a>四大核心特性</h2><h3 id="1-Rich-Container（富容器模式）"><a href="#1-Rich-Container（富容器模式）" class="headerlink" title="1. Rich Container（富容器模式）"></a>1. Rich Container（富容器模式）</h3><p>Docker 强调”一个容器只跑一个进程”，这在云原生新应用上没问题。但阿里大量存量业务跑在传统 Linux 环境里，需要 <code>sshd</code>、<code>cron</code>、<code>supervisord</code> 等系统服务同时运行在同一个容器内，这就是 <strong>Rich Container</strong> 要解决的问题。</p><p>Rich Container 允许容器内运行完整的 init 系统（如 systemd），让传统应用无需改造就能容器化，极大降低了存量业务的迁移成本。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 以 Rich Container 模式启动容器（带 init 进程）</span></span><br><span class="line">pouch run -d --rich --initscript /tmp/initscript.sh ubuntu:16.04</span><br></pre></td></tr></table></figure><h3 id="2-LXCFS-提供更强的-proc-隔离"><a href="#2-LXCFS-提供更强的-proc-隔离" class="headerlink" title="2. LXCFS 提供更强的 &#x2F;proc 隔离"></a>2. LXCFS 提供更强的 &#x2F;proc 隔离</h3><p>Docker 容器在默认情况下，<code>cat /proc/meminfo</code> 显示的是<strong>宿主机</strong>的内存信息，而不是容器被分配的资源。这在多租户场景下会引发严重误判——Java 应用按宿主机内存大小设置 JVM 堆，实际分配却远低于此，直接 OOM。</p><p>PouchContainer 集成了 <strong>lxcfs</strong>（Linux Container Filesystem），通过 FUSE 在用户态提供虚拟 <code>/proc</code> 文件系统，让容器内的进程看到的 <code>meminfo</code>、<code>cpuinfo</code>、<code>uptime</code> 等都是<strong>容器视图</strong>，彻底解决 &#x2F;proc 隔离问题。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># 容器内看到的是分配给它的 2G 内存，而不是宿主机的 256G</span><br><span class="line">cat /proc/meminfo</span><br><span class="line">MemTotal:        2097152 kB</span><br><span class="line">MemFree:         1048576 kB</span><br></pre></td></tr></table></figure><h3 id="3-P2P-镜像分发（Dragonfly）"><a href="#3-P2P-镜像分发（Dragonfly）" class="headerlink" title="3. P2P 镜像分发（Dragonfly）"></a>3. P2P 镜像分发（Dragonfly）</h3><p>在阿里双 11 场景下，需要在几分钟内向几十万台机器推送同一个容器镜像。如果走传统的 Registry 直拉模式，Registry 带宽会瞬间被打爆。</p><p>PouchContainer 内置集成了阿里开源的 <strong>Dragonfly</strong>（蜻蜓），一个基于 P2P 协议的智能文件分发系统：</p><ul><li>首台机器从 Registry 拉取后，后续机器从已有节点 P2P 获取</li><li>带宽消耗降低 60% 以上</li><li>支持秒级扩容数万容器</li></ul><p>Dragonfly 已捐给 CNCF，成为独立项目：<a href="https://d7y.io/">d7y.io</a></p><h3 id="4-Hypervisor-级隔离（双轨隔离）"><a href="#4-Hypervisor-级隔离（双轨隔离）" class="headerlink" title="4. Hypervisor 级隔离（双轨隔离）"></a>4. Hypervisor 级隔离（双轨隔离）</h3><p>普通容器（runc）本质上还是共享内核，对安全敏感场景（金融、多租户）有隐患。PouchContainer 支持接入 <strong>hypervisor 级容器运行时</strong>（如 runV &#x2F; Kata Containers），用轻量级 VM 提供更强隔离，同时保留容器的快速启动特性。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">内核级容器（runc）  → 启动快，隔离较弱</span><br><span class="line">VM 级容器（runV）  → 启动稍慢，隔离强，内核独立</span><br></pre></td></tr></table></figure><p>用户可以按业务场景选择不同的 runtime，同一套 Pouch Daemon 统一管理。</p><h2 id="架构概览"><a href="#架构概览" class="headerlink" title="架构概览"></a>架构概览</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">┌─────────────────────────────────────┐</span><br><span class="line">│            Kubernetes               │</span><br><span class="line">└──────────────┬──────────────────────┘</span><br><span class="line">               │ CRI (gRPC)</span><br><span class="line">┌──────────────▼──────────────────────┐</span><br><span class="line">│             Pouchd                  │</span><br><span class="line">│  ┌──────────┐  ┌──────────────────┐ │</span><br><span class="line">│  │CRI Manager│  │  Image Manager   │ │</span><br><span class="line">│  └──────────┘  └──────────────────┘ │</span><br><span class="line">│  ┌──────────┐  ┌──────────────────┐ │</span><br><span class="line">│  │Container  │  │   CNI Manager    │ │</span><br><span class="line">│  │ Manager  │  └──────────────────┘ │</span><br><span class="line">│  └────┬─────┘                       │</span><br><span class="line">└───────┼─────────────────────────────┘</span><br><span class="line">        │</span><br><span class="line">   ┌────┴────┐</span><br><span class="line">   │  runc   │  runV / kata</span><br><span class="line">   └─────────┘</span><br></pre></td></tr></table></figure><p>与 Docker 需要 <code>dockerd → containerd → containerd-shim → runc</code> 多层链路不同，Pouchd 将 CRI Manager 内嵌，减少了进程层级和 IPC 开销。</p><h2 id="Kubernetes-集成"><a href="#Kubernetes-集成" class="headerlink" title="Kubernetes 集成"></a>Kubernetes 集成</h2><p>PouchContainer 原生实现了 Kubernetes CRI（Container Runtime Interface），启动时加上 <code>--enable-cri=true</code> 即可作为 kubelet 的容器运行时：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># kubelet 配置</span></span><br><span class="line"><span class="string">--container-runtime=remote</span></span><br><span class="line"><span class="string">--container-runtime-endpoint=unix:///var/run/pouchcri.sock</span></span><br></pre></td></tr></table></figure><p>这一点早于 Docker 通过 cri-dockerd 桥接的方案，架构上更简洁。</p><h2 id="现状与影响"><a href="#现状与影响" class="headerlink" title="现状与影响"></a>现状与影响</h2><p>PouchContainer 目前已是阿里巴巴内部最核心的容器基础设施之一，支撑了：</p><ul><li>淘宝、天猫、支付宝的核心交易链路</li><li>双 11 期间数十万节点的弹性扩缩容</li><li>阿里云 ECI（弹性容器实例）的底层支撑</li></ul><p>作为开源项目，Pouch 的 GitHub 活跃度近年有所下降（阿里内部逐步向 containerd&#x2F;Kata 生态收敛），但它在 2017-2020 年间对推动国内企业级容器化进程有重要贡献，也促成了 CNCF 多个项目（Dragonfly、Sealer）的诞生。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>PouchContainer 的价值不在于”又一个容器运行时”，而在于它系统性地解决了超大规模、存量业务迁移、多租户安全隔离三类企业痛点：</p><ul><li><strong>Rich Container</strong> → 存量业务低成本容器化</li><li><strong>LXCFS</strong> → &#x2F;proc 视图正确隔离，Java 应用友好</li><li><strong>Dragonfly P2P</strong> → 大规模镜像分发不打爆 Registry</li><li><strong>双轨隔离</strong> → 按安全需求灵活选择 runc 或 VM</li></ul><p>如果你在阿里云上做容器化，了解 Pouch 的设计思路能帮你更好地理解底层基础设施的行为；如果你在设计私有云容器平台，这些特性也值得借鉴。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/1aca4422b4f2/</id>
    <link href="https://blog.zisheng.pro/posts/1aca4422b4f2/"/>
    <published>2026-05-11T02:00:00.000Z</published>
    <summary>深入了解阿里巴巴开源的企业级容器运行时 PouchContainer：从 T4 的历史沿革，到 Rich Container、LXCFS、P2P 镜像分发等核心特性，再到与 Docker 和 Kubernetes 的关系。</summary>
    <title>阿里云 PouchContainer 容器技术详解</title>
    <updated>2026-07-12T09:21:36.911Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="建站教程" scheme="https://blog.zisheng.pro/categories/%E5%BB%BA%E7%AB%99%E6%95%99%E7%A8%8B/"/>
    <category term="服务器运维" scheme="https://blog.zisheng.pro/categories/%E5%BB%BA%E7%AB%99%E6%95%99%E7%A8%8B/%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%BF%90%E7%BB%B4/"/>
    <category term="LAMP" scheme="https://blog.zisheng.pro/tags/LAMP/"/>
    <category term="LNMP" scheme="https://blog.zisheng.pro/tags/LNMP/"/>
    <category term="宝塔面板" scheme="https://blog.zisheng.pro/tags/%E5%AE%9D%E5%A1%94%E9%9D%A2%E6%9D%BF/"/>
    <category term="WordPress" scheme="https://blog.zisheng.pro/tags/WordPress/"/>
    <category term="建站" scheme="https://blog.zisheng.pro/tags/%E5%BB%BA%E7%AB%99/"/>
    <category term="网站搭建" scheme="https://blog.zisheng.pro/tags/%E7%BD%91%E7%AB%99%E6%90%AD%E5%BB%BA/"/>
    <category term="服务器管理" scheme="https://blog.zisheng.pro/tags/%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%AE%A1%E7%90%86/"/>
    <category term="域名绑定" scheme="https://blog.zisheng.pro/tags/%E5%9F%9F%E5%90%8D%E7%BB%91%E5%AE%9A/"/>
    <content>
      <![CDATA[<h2 id="环境准备"><a href="#环境准备" class="headerlink" title="环境准备"></a>环境准备</h2><p>确保安装了 Nginx、MySQL、PHP 环境。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1753541490034.png"></p><h2 id="新建站点"><a href="#新建站点" class="headerlink" title="新建站点"></a>新建站点</h2><p>进入宝塔面板，点击左侧的“网站”，然后点击“添加站点”。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1753542436855.png"></p><p>点击 “一键部署”，选择 WordPress 模版，输入域名。建议 www 和非 www 都部署。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1753542516497.png"></p><h2 id="SSL-证书"><a href="#SSL-证书" class="headerlink" title="SSL 证书"></a>SSL 证书</h2><p>网站部署完成后，点击 “SSL”，选择 “Let’s Encrypt”，点击 “提交”。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1753542750792.png"></p><p>记得勾选 “强制 HTTPS” 和到期提醒。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1753542996550.png"></p><h2 id="伪静态"><a href="#伪静态" class="headerlink" title="伪静态"></a>伪静态</h2><p>选择 wordpress 模版，点击 “提交”。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1753543586057.png"></p><h2 id="301-重定向"><a href="#301-重定向" class="headerlink" title="301 重定向"></a>301 重定向</h2><p>重定向设置，选择 “301 重定向”，点击 “提交”。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1753543929126.png"></p><h2 id="wordpress-初始化"><a href="#wordpress-初始化" class="headerlink" title="wordpress 初始化"></a>wordpress 初始化</h2><p>数据库名选择新建网站时的：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1753545846823.png"></p><h2 id="wordpress-设置"><a href="#wordpress-设置" class="headerlink" title="wordpress 设置"></a>wordpress 设置</h2><p>permalink 设置为 <code>/%category%/%postname%/</code>（电商、新闻门户） 或者 <code>/%postname%/</code>（博客、企业站）：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1753603318808.png"></p><h2 id="wordpress-主题"><a href="#wordpress-主题" class="headerlink" title="wordpress 主题"></a>wordpress 主题</h2><ul><li><a href="https://www.xhtheme.com/themes/meteor-theme">Meteor</a>：一款专为自媒体人、博主和内容创作者精心打造的WordPress主题</li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/a9b37883b6d3/</id>
    <link href="https://blog.zisheng.pro/posts/a9b37883b6d3/"/>
    <published>2025-07-26T10:07:05.000Z</published>
    <summary>详细介绍如何使用宝塔面板快速安装 WordPress，包括环境配置、数据库创建、域名绑定等完整建站流程。适合新手站长的宝塔 WordPress 搭建指南。</summary>
    <title>宝塔面板安装 WordPress 详细教程：从零开始搭建网站</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="Ant Design Pro" scheme="https://blog.zisheng.pro/tags/Ant-Design-Pro/"/>
    <category term="React" scheme="https://blog.zisheng.pro/tags/React/"/>
    <category term="Umi" scheme="https://blog.zisheng.pro/tags/Umi/"/>
    <category term="中后台" scheme="https://blog.zisheng.pro/tags/%E4%B8%AD%E5%90%8E%E5%8F%B0/"/>
    <category term="实战记录" scheme="https://blog.zisheng.pro/tags/%E5%AE%9E%E6%88%98%E8%AE%B0%E5%BD%95/"/>
    <content>
      <![CDATA[<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>Ant Design Pro 是基于 Ant Design 和 umi 的封装的一整套企业级中后台前端&#x2F;设计解决方案，致力于在设计规范和基础组件的基础上，继续向上构建，提炼出典型模板&#x2F;业务组件&#x2F;配套设计资源，进一步提升企业级中后台产品设计研发过程中的『用户』和『设计者』的体验。随着『设计者』的不断反馈。</p><h2 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h2><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">npm i @ant-design/pro-cli -g</span><br><span class="line">pro create myapp</span><br></pre></td></tr></table></figure><p>然后选择脚手架，simple 是基础模板，只提供了框架运行的基本内容，complete 包含所有区块，不太适合当基础模板来进行二次开发</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">? 🚀 要全量的还是一个简单的脚手架? (Use arrow keys)</span><br><span class="line">❯ simple</span><br><span class="line">  complete</span><br></pre></td></tr></table></figure><blockquote><p>我这里选择是 complete 脚手架，方便全量的最佳实践</p></blockquote><p>安装依赖：</p><p>脚手架初始化成功之后就可以开始进行开发了，Ant Design Pro 提供了一些命令来辅助开发。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> myapp &amp;&amp; pnpm install</span><br></pre></td></tr></table></figure><h2 id="运行"><a href="#运行" class="headerlink" title="运行"></a>运行</h2><p>complete 脚手架运行会报错：<a href="https://github.com/ant-design/ant-design-pro/issues/11189">🐛 [BUG] 路由错误 #11189</a>，需要修改 <code>routes.ts</code> 文件，注释掉 <code>/user</code> 路由下的 404 路由（全局有一个 404 可以兜底）：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">path</span>: <span class="string">&#x27;/user&#x27;</span>,</span><br><span class="line">  <span class="attr">layout</span>: <span class="literal">false</span>,</span><br><span class="line">  <span class="attr">routes</span>: [</span><br><span class="line">    <span class="comment">// &#123;</span></span><br><span class="line">    <span class="comment">//   component: &#x27;404&#x27;,</span></span><br><span class="line">    <span class="comment">//   path: &#x27;/*&#x27;,</span></span><br><span class="line">    <span class="comment">// &#125;,</span></span><br><span class="line">  ],</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后执行 <code>yarn start</code> 即可启动项目。</p><h3 id="修改调试-MOCK"><a href="#修改调试-MOCK" class="headerlink" title="修改调试 MOCK"></a>修改调试 MOCK</h3><p><code>app.tsx</code> 文件中的 <code>baseURL</code> 注释掉，或者根据 <code>isDev</code> 判断：</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@name</span> request 配置，可以配置错误处理</span></span><br><span class="line"><span class="comment"> * 它基于 axios 和 ahooks 的 useRequest 提供了一套统一的网络请求和错误处理方案。</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@doc</span> https://umijs.org/docs/max/request#配置</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="attr">request</span>: <span class="title class_">RequestConfig</span> = &#123;</span><br><span class="line">  <span class="attr">baseURL</span>: isDev ? <span class="string">&#x27;&#x27;</span> : <span class="string">&#x27;https://proapi.azurewebsites.net&#x27;</span>,</span><br><span class="line">  ...errorConfig,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><ul><li><a href="https://github.com/ant-design/ant-design-pro/issues/11446">🧐[问题 | question] 如何修改调试Mock</a></li></ul><h2 id="登录"><a href="#登录" class="headerlink" title="登录"></a>登录</h2><p>在 Umi Max 框架中，登录后的 ​Token 存储方案​ 通常结合全局状态管理与浏览器本地存储机制实现。以下是具体实现方式及安全权衡：</p><p>1、在运行时定义 <code>getInitialState</code> 方法，用于获取初始状态：</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">getInitialState</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> token = <span class="variable language_">localStorage</span>.<span class="title function_">getItem</span>(<span class="string">&#x27;token&#x27;</span>);</span><br><span class="line">  <span class="keyword">if</span> (token) &#123;</span><br><span class="line">    <span class="keyword">const</span> userInfo = <span class="keyword">await</span> <span class="title function_">fetchUserInfo</span>();</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      <span class="attr">currentUser</span>: &#123;...userInfo, token &#125; <span class="keyword">as</span> <span class="variable constant_">API</span>.<span class="property">CurrentUser</span>,</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> &#123;&#125;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>2、登录成功后，获取 Token 并存储到浏览器的 Local Storage 中，并使用 <code>setInitialState</code> 方法把 token 更新全局状态：</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useModel &#125; <span class="keyword">from</span> <span class="string">&#x27;@umijs/max&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; flushSync &#125; <span class="keyword">from</span> <span class="string">&#x27;react-dom&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">Login</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; initialState &#125; = <span class="title function_">useModel</span>(<span class="string">&#x27;@@initialState&#x27;</span>);</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">fetchUserInfo</span> = <span class="keyword">async</span> (<span class="params">token: <span class="built_in">string</span></span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> userInfo = <span class="keyword">await</span> initialState?.<span class="property">fetchUserInfo</span>?.();</span><br><span class="line">    <span class="variable language_">localStorage</span>.<span class="title function_">setItem</span>(<span class="string">&#x27;token&#x27;</span>, token);</span><br><span class="line">    <span class="keyword">if</span> (userInfo) &#123;</span><br><span class="line">      <span class="title function_">flushSync</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="title function_">setInitialState</span>(<span class="function">(<span class="params">s</span>) =&gt;</span> (&#123;</span><br><span class="line">          ...s,</span><br><span class="line">          <span class="attr">currentUser</span>: &#123; ...userInfo, token &#125; <span class="keyword">as</span> <span class="variable constant_">API</span>.<span class="property">CurrentUser</span>,</span><br><span class="line">        &#125;));</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">handleSubmit</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> token = <span class="keyword">await</span> <span class="title function_">login</span>(&#123; <span class="attr">account</span>: <span class="string">&#x27;admin&#x27;</span>, <span class="attr">password</span>: <span class="string">&#x27;123456&#x27;</span> &#125;);</span><br><span class="line">    <span class="keyword">if</span> (token) &#123;</span><br><span class="line">      message.<span class="title function_">success</span>(<span class="string">&#x27;登录成功！&#x27;</span>);</span><br><span class="line">      <span class="keyword">await</span> <span class="title function_">fetchUserInfo</span>();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Login</span>;</span><br></pre></td></tr></table></figure><h2 id="部署"><a href="#部署" class="headerlink" title="部署"></a>部署</h2><h3 id="netlify"><a href="#netlify" class="headerlink" title="netlify"></a>netlify</h3><p>如果不使用 github pages，需要把模版中 <code>gh-pages</code> 删掉：</p><ul><li>删除 <code>public</code> 目录下的 <code>CNAME</code> 文件</li><li>删除 <code>gh-pages</code> 依赖和 npm script 脚本</li></ul><p>然后在 <code>public</code> 目录下添加 <code>_redirects</code> 文件，内容：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&lt;!-- https://gist.github.com/dance2die/acf9a148ec8025364bf0b6cf37367294 --&gt;</span><br><span class="line">/*    /index.html   200</span><br></pre></td></tr></table></figure><ul><li><a href="https://github.com/ant-design/ant-design-pro/issues/7227">🐛[BUG] Page not found when reloading Netlify</a></li><li><a href="https://www.sung.codes/blog/2018/page-not-found-on-netlify-with-react-router">Page Not Found on Netlify with React Router</a></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/b800604eca6e/</id>
    <link href="https://blog.zisheng.pro/posts/b800604eca6e/"/>
    <published>2025-05-17T08:04:15.000Z</published>
    <summary>本文记录了使用 Ant Design Pro 开发中后台的一些经验和问题</summary>
    <title>Ant Design Pro 实战记录</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="AGI" scheme="https://blog.zisheng.pro/categories/AGI/"/>
    <category term="Agent" scheme="https://blog.zisheng.pro/tags/Agent/"/>
    <category term="TARS" scheme="https://blog.zisheng.pro/tags/TARS/"/>
    <category term="字节跳动" scheme="https://blog.zisheng.pro/tags/%E5%AD%97%E8%8A%82%E8%B7%B3%E5%8A%A8/"/>
    <category term="Electron" scheme="https://blog.zisheng.pro/tags/Electron/"/>
    <category term="MCP" scheme="https://blog.zisheng.pro/tags/MCP/"/>
    <content>
      <![CDATA[<h2 id="依赖"><a href="#依赖" class="headerlink" title="依赖"></a>依赖</h2><ul><li><code>@tavily/core</code>：Tavily 的 JavaScript SDK 允许与 Tavily API 轻松交互，直接在 JavaScript 和 TypeScript 程序中使用 Tavily 的全部搜索和提取功能。利用强大的 Tavily Search 和 Tavily Extract API，轻松将智能搜索和内容提取功能集成到应用程序中。</li><li><code>@computer-use/node-mac-permissions</code>：用来管理 macOS 权限的 nodejs 包。</li><li><code>@agent-infra/mcp-server-commands</code>：执行任意命令的 MCP 服务器。</li><li><code>@agent-infra/mcp-server-filesystem</code>：访问文件系统的 MCP 服务器。</li><li><code>@agent-infra/mcp-server-browser</code>：操作浏览器的 MCP 服务器。</li><li><code>jotai</code>：轻量状态管理库</li></ul><h2 id="架构"><a href="#架构" class="headerlink" title="架构"></a>架构</h2><h3 id="Monorepo"><a href="#Monorepo" class="headerlink" title="Monorepo"></a>Monorepo</h3><ul><li>pnpm workspace</li></ul><h3 id="Electron"><a href="#Electron" class="headerlink" title="Electron"></a>Electron</h3><p>agent-tars 是一个基于 Electron 的桌面应用，Electron 的主进程（Main Process）和渲染进程（Renderer Process）是其双进程架构的核心设计，两者的职责、运行环境和交互方式决定了 Electron 应用的性能与安全性。</p><h4 id="主进程（Main-Process）"><a href="#主进程（Main-Process）" class="headerlink" title="主进程（Main Process）"></a><strong>主进程（Main Process）</strong></h4><ul><li><strong>​入口与生命周期管理</strong>：作为应用的入口（<code>src/main/index.ts</code>），负责启动&#x2F;关闭应用、管理窗口生命周期（如创建&#x2F;销毁 <code>BrowserWindow</code> 实例）。</li><li><strong>​系统级交互</strong>：直接访问 Node.js API 和操作系统资源（如文件系统、网络请求、系统通知），执行敏感操作（如加密数据处理）</li><li><strong>​全局状态控制</strong>：管理跨窗口的全局数据（如用户配置、缓存），协调多渲染进程的通信。</li></ul><h4 id="渲染进程（Renderer-Process）"><a href="#渲染进程（Renderer-Process）" class="headerlink" title="渲染进程（Renderer Process）"></a><strong>渲染进程（Renderer Process）</strong></h4><ul><li><strong>UI 渲染与交互</strong>：每个窗口对应一个独立的渲染进程，基于 Chromium 实现网页渲染，处理 HTML、CSS、JavaScript 的执行及用户交互（如点击事件）。</li><li><strong>部分 Node.js能力</strong>：默认不启用 Node.js 集成，但可通过 <code>preload</code> 脚本和 <code>contentBridge</code> 暴露有限 API（如 <code>fs</code> 模块的部分功能）。</li><li><strong>沙箱环境</strong>：出于安全考虑，渲染进程默认隔离，无法直接访问系统资源或执行危险操作。</li></ul><h4 id="进程间通信（IPC）机制"><a href="#进程间通信（IPC）机制" class="headerlink" title="进程间通信（IPC）机制"></a>进程间通信（IPC）机制</h4><p>主进程与渲染进程通过 IPC（Inter-Process Communication）实现安全通信：</p><p>1、<strong>异步通信</strong>：</p><ul><li>渲染进程通过 <code>ipcRenderer.send()</code> 发送消息，主进程通过 <code>ipcMain.on()</code> 监听并响应。</li><li>示例：渲染进程请求打开文件 → 主进程调用 <code>fs</code> 模块读取 → 结果返回渲染进程。</li></ul><p>2、<strong>同步通信</strong>：</p><ul><li>渲染进程通过 <code>ipcRenderer.sendSync()</code> 发送同步请求，主进程处理后返回结果（可能阻塞 UI 线程，慎用）</li></ul><p>3、<strong>预加载脚本（Preload Script）</strong></p><ul><li>在渲染进程加载前注入，通过 <code>contextBridge.exposeInMainWorld()</code> 安全暴露主进程 API（如 <code>sendMessageToMain</code>）</li><li>示例：预加载脚本桥接 <code>ipcRenderer</code>，避免直接暴露 Node.js 功能。</li></ul><h2 id="渲染进程（src-renderer）"><a href="#渲染进程（src-renderer）" class="headerlink" title="渲染进程（src&#x2F;renderer）"></a>渲染进程（src&#x2F;renderer）</h2><h3 id="main-tsx"><a href="#main-tsx" class="headerlink" title="main.tsx"></a>main.tsx</h3><p>渲染进程的入口在 <code>src/main.tsx</code>，主要作用：</p><ol><li>执行 <code>initMonacoWorkers()</code> 方法初始化 MonacoWorkers，具体分析看 <a href="#initMonacoWorkers">initMonacoWorkers</a></li><li>引入 <code>./components/App.tsx</code>，具体分析看 <a href="#App.tsx">App.tsx</a></li></ol><h3 id="App-tsx"><a href="#App-tsx" class="headerlink" title="App.tsx"></a>App.tsx</h3><p><code>main.tsx</code> 一般都是执行一些和 UI 无关的全局初始化操作，<code>App.tsx</code> 则是处理一些 UI 相关的全局初始化操作：</p><ol><li>调用 <code>useMainProcessErrorHandler()</code> 注册主进程错误处理程序，这不是我们关注的重点，请查看<a href="https://github.com/bytedance/UI-TARS-desktop/blob/main/apps/agent-tars/src/renderer/src/services/errorHandlerService.ts#L88">源码</a></li><li>引入 <code>./components/AgentApp</code> 进行展示</li></ol><h3 id="AgentApp-tsx"><a href="#AgentApp-tsx" class="headerlink" title="AgentApp.tsx"></a>AgentApp.tsx</h3><ol><li><a href="https://github.com/bytedance/UI-TARS-desktop/tree/main/apps/agent-tars/src/renderer/src/components/LeftSidebar"><code>&lt;LeftSidebar /&gt;</code></a>：放置主题切换、会话管理和设置，这不是本文关注的重点。</li><li><a href="#OpenAgentChatUI"><code>&lt;OpenAgentChatUI /&gt;</code></a>：负责指令输入和 Agent 执行链展示</li><li><a href="#CanvasPanel"><code>&lt;CanvasPanel&gt;</code></a>：负责展示 Agent 执行细节的展示</li></ol><h3 id="OpenAgentChatUI"><a href="#OpenAgentChatUI" class="headerlink" title="OpenAgentChatUI"></a>OpenAgentChatUI</h3><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">ChatUI</span> <span class="keyword">as</span> <span class="title class_">BaseChatUI</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@vendor/chat-ui&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">MenuHeader</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;./MenuHeader&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">AgentStatusTip</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;./AgentStatusTip&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">BeforeInputContainer</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;./BeforeInputContainer&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; useAgentFlow &#125; <span class="keyword">from</span> <span class="string">&#x27;@renderer/hooks/useAgentFlow&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">WelcomeScreen</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;../WelcomeScreen&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">OpenAgentChatUI</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> launchAgentFlow = <span class="title function_">useAgentFlow</span>();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> sendMessage = <span class="title function_">useCallback</span>(</span><br><span class="line">    <span class="keyword">async</span> (<span class="attr">inputText</span>: <span class="built_in">string</span>, <span class="attr">inputFiles</span>: <span class="title class_">InputFile</span>[]) =&gt; &#123;</span><br><span class="line">      <span class="title function_">setIsSending</span>(<span class="literal">true</span>);</span><br><span class="line">      <span class="comment">// 截止发稿日，暂不支持文件上传</span></span><br><span class="line">      <span class="keyword">await</span> <span class="title function_">launchAgentFlow</span>(inputText, inputFiles);</span><br><span class="line">      <span class="title function_">setIsSending</span>(<span class="literal">false</span>);</span><br><span class="line">    &#125;,</span><br><span class="line">    [addUserMessage, launchAgentFlow],</span><br><span class="line">  );</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">BaseChatUI</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">onMessageSend</span>=<span class="string">&#123;sendMessage&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">slots</span>=<span class="string">&#123;&#123;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">beforeMessageList:</span> (</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            &lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">              <span class="tag">&lt;<span class="name">MenuHeader</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">              &#123;isInitialized &amp;&amp; messages.length &gt;= 0 &amp;&amp; <span class="tag">&lt;<span class="name">WelcomeScreen</span> /&gt;</span>&#125;</span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;/&gt;</span></span></span><br><span class="line"><span class="language-xml">          ),</span></span><br><span class="line"><span class="language-xml"></span></span><br><span class="line"><span class="language-xml">          beforeInputContainer: <span class="tag">&lt;<span class="name">BeforeInputContainer</span> /&gt;</span>,</span></span><br><span class="line"><span class="language-xml">          customFeatures: (</span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">              <span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">&quot;flex gap-2&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">                &#123;isSending ? <span class="tag">&lt;<span class="name">AgentStatusTip</span> /&gt;</span> : null&#125;</span></span><br><span class="line"><span class="language-xml">              <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">          ),</span><br><span class="line">        &#125;&#125;</span><br><span class="line">      /&gt;</span><br><span class="line">    &lt;/&gt;</span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>以上是去掉和 ChatUI 交互后的简化的核心代码，<code>@vender/chat-ui</code> 由于内部原因，暂未公开，可以关注 <a href="https://github.com/bytedance/UI-TARS-desktop/issues/417">issues#417</a>。核心逻辑如下：</p><ol><li>用户输入完成后，触发 <code>sendMessage</code>，将用户输入发送给 <code>launchAgentFlow</code></li><li><code>launchAgentFlow</code> 触发 Agent 执行，详细代码请查看 <a href="#useAgentFlow">useAgentFlow</a></li></ol><p>slots 插槽的配置分别是：</p><ol><li><code>beforeMessageList</code>，MessageList 前面的槽位，这里放了菜单栏和欢迎页</li><li><code>beforeInputContainer</code>，InputContainer 前面的槽位，这里放了 <a href="#BeforeInputContainer"><code>&lt;BeforeInputContainer /&gt;</code></a>，</li><li><code>customFeatures</code>，自定义功能槽位，这里放了 <code>&lt;AgentStatusTip /&gt;</code>，用来展示 Agent 的状态</li></ol><h4 id="BeforeInputContainer"><a href="#BeforeInputContainer" class="headerlink" title="BeforeInputContainer"></a>BeforeInputContainer</h4><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">PlanTaskStatus</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;./PlanTaskStatus&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">UserInterruptArea</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;./UserInterruptArea&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">BeforeInputContainer</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">PlanTaskStatus</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">UserInterruptArea</span> <span class="attr">isDark</span>=<span class="string">&#123;isDarkMode.value&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li><code>&lt;PlanTaskStatus /&gt;</code>，计划任务状态，展示计划执行列表的进度</li><li><code>&lt;UserInterruptArea /&gt;</code>，用户中断区域，消息发送或执行中，允许用户发送新的消息或中断</li></ol><h3 id="CanvasPanel"><a href="#CanvasPanel" class="headerlink" title="CanvasPanel"></a>CanvasPanel</h3><h3 id="hooks"><a href="#hooks" class="headerlink" title="hooks"></a>hooks</h3><h4 id="useAgentFlow"><a href="#useAgentFlow" class="headerlink" title="useAgentFlow"></a>useAgentFlow</h4><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useCallback &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">AgentFlow</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;../agent/AgentFlow&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; v4 <span class="keyword">as</span> uuid &#125; <span class="keyword">from</span> <span class="string">&#x27;uuid&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">useAgentFlow</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">useCallback</span>(</span><br><span class="line">    <span class="title function_">async</span>(<span class="attr">inputText</span>: <span class="built_in">string</span>; <span class="attr">inputFiles</span>: <span class="title class_">InputFile</span>[]) =&gt; &#123;</span><br><span class="line">      <span class="keyword">const</span> agentFlowId = <span class="title function_">uuid</span>();</span><br><span class="line">      <span class="keyword">const</span> agentFlow = <span class="keyword">new</span> <span class="title class_">AgentFlow</span>(&#123;</span><br><span class="line">        agentFlowId,</span><br><span class="line">        <span class="attr">request</span>: &#123;</span><br><span class="line">          inputText,</span><br><span class="line">          <span class="comment">// 截止发稿日，暂不支持文件上传</span></span><br><span class="line">          inputFiles</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;);</span><br><span class="line">      <span class="keyword">await</span> agentFlow.<span class="title function_">run</span>();</span><br><span class="line">    &#125;,</span><br><span class="line">    []</span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li><code>useAgentFow</code> hook 接收用户输入后，创建一个 <a href="#AgentFlow"><code>AgentFlow</code></a> 实例，并调用 <code>agentFlow.run()</code> 触发 Agent 执行</li></ol><h3 id="agent"><a href="#agent" class="headerlink" title="agent"></a>agent</h3><h4 id="AgentFlow-ts"><a href="#AgentFlow-ts" class="headerlink" title="AgentFlow.ts"></a>AgentFlow.ts</h4><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">ToolCallType</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@renderer/type/agent&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Aware</span>, <span class="title class_">AwareResult</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;./Aware&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">EventManager</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;./EventManager&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Executor</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;./Executor&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Greeter</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;./Greeter&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; ipcClient &#125; <span class="keyword">from</span> <span class="string">&#x27;@renderer/api&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="variable constant_">SNAPSHOT_BROWSER_ACTIONS</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@renderer/constants&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">AgentFlow</span> &#123;</span><br><span class="line">  <span class="keyword">private</span> <span class="attr">abortController</span>: <span class="title class_">AbortController</span>;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"><span class="keyword">private</span> appContext: AppContext</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">eventManager</span> = <span class="keyword">new</span> <span class="title class_">EventManager</span>(omegaHistoryEvents);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">async</span> <span class="title function_">run</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> greeter = <span class="keyword">new</span> <span class="title class_">Greeter</span>(<span class="variable language_">this</span>.<span class="property">appContext</span>, <span class="variable language_">this</span>.<span class="property">abortController</span>.<span class="property">signal</span>);</span><br><span class="line">    <span class="keyword">const</span> aware = <span class="keyword">new</span> <span class="title class_">Aware</span>(</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">appContext</span>,</span><br><span class="line">      agentContext,</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">interruptController</span>.<span class="property">signal</span>,</span><br><span class="line">    );</span><br><span class="line">    <span class="comment">// 构建执行器</span></span><br><span class="line">    <span class="keyword">const</span> executor = <span class="keyword">new</span> <span class="title class_">Executor</span>(</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">appContext</span>,</span><br><span class="line">      agentContext,</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">interruptController</span>.<span class="property">signal</span>,</span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 展示 ai 打招呼消息</span></span><br><span class="line">    <span class="keyword">const</span> preparePromise = greeter.<span class="title function_">run</span>().<span class="title function_">then</span>(<span class="keyword">async</span> () =&gt; &#123;&#125;);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">all</span>([</span><br><span class="line">      preparePromise,</span><br><span class="line">      <span class="comment">// 启动 Agent 循环</span></span><br><span class="line">      <span class="variable language_">this</span>.<span class="title function_">launchAgentLoop</span>(executor, aware, agentContext, preparePromise),</span><br><span class="line">    ]);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">async</span> <span class="title function_">launchAgentLoop</span>(<span class="params"></span></span><br><span class="line"><span class="params">    executor: Executor,</span></span><br><span class="line"><span class="params">    aware: Aware,</span></span><br><span class="line"><span class="params">    agentContext: AgentContext,</span></span><br><span class="line"><span class="params">    preparePromise: <span class="built_in">Promise</span>&lt;<span class="built_in">void</span>&gt;</span></span><br><span class="line"><span class="params">  </span>) &#123;</span><br><span class="line">    <span class="comment">//  Agent 循环</span></span><br><span class="line">    <span class="keyword">while</span>(!<span class="variable language_">this</span>.<span class="property">abortController</span>.<span class="property">signal</span>.<span class="property">aborted</span> &amp;&amp; !<span class="variable language_">this</span>.<span class="property">hasFinished</span>) &#123;</span><br><span class="line">      <span class="comment">// 当任务没有被取消或完成时执行 Agent 循环</span></span><br><span class="line">      <span class="keyword">const</span> awareResult = <span class="keyword">await</span> aware.<span class="title function_">run</span>();</span><br><span class="line">      <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">abortController</span>.<span class="property">signal</span>.<span class="property">aborted</span>) &#123;</span><br><span class="line">        <span class="comment">// 如果任务被取消，则跳出 Agent 循环</span></span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (</span><br><span class="line">        awareResult.<span class="property">plan</span> &amp;&amp;</span><br><span class="line">        awareResult.<span class="property">plan</span>.<span class="title function_">every</span>(</span><br><span class="line">          <span class="function">(<span class="params">task</span>) =&gt;</span> task.<span class="property">status</span> === <span class="title class_">PlanTaskStatus</span>.<span class="property">Done</span>,</span><br><span class="line">        )</span><br><span class="line">      ) &#123;</span><br><span class="line">        <span class="comment">// 如果所有任务都完成，则跳出 Agent 循环</span></span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">hasFinished</span> = <span class="literal">true</span>;</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">const</span> toolCallList = (<span class="keyword">await</span> executor.<span class="title function_">run</span>(awareResult.<span class="property">status</span>)).<span class="title function_">filter</span>(</span><br><span class="line">        <span class="title class_">Boolean</span>,</span><br><span class="line">      );</span><br><span class="line">      <span class="keyword">const</span> mcpTools = <span class="keyword">await</span> ipcClient.<span class="title function_">listMcpTools</span>();</span><br><span class="line">      <span class="keyword">const</span> customServerTools = <span class="keyword">await</span> ipcClient.<span class="title function_">listCustomTools</span>();</span><br><span class="line"></span><br><span class="line">      <span class="comment">// 执行 工具</span></span><br><span class="line">      <span class="keyword">for</span> (<span class="keyword">const</span> toolCall <span class="keyword">of</span> toolCallList) &#123;</span><br><span class="line">        <span class="keyword">const</span> toolName = toolCall.<span class="property">function</span>.<span class="property">name</span>;</span><br><span class="line">        <span class="keyword">const</span> isMCPToolCall = mcpTools.<span class="title function_">some</span>(</span><br><span class="line">          <span class="function">(<span class="params">tool</span>) =&gt;</span> tool.<span class="property">name</span> === toolCall.<span class="property">function</span>.<span class="property">name</span>,</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 如果是执行 MCP 工具</span></span><br><span class="line">        <span class="keyword">if</span> (isMCPToolCall) &#123;</span><br><span class="line">          <span class="keyword">if</span> (</span><br><span class="line">            <span class="comment">// 如果是编辑和写入文件</span></span><br><span class="line">            toolName === <span class="title class_">ToolCallType</span>.<span class="property">EditFile</span> ||</span><br><span class="line">            toolName === <span class="title class_">ToolCallType</span>.<span class="property">WriteFile</span></span><br><span class="line">          ) &#123;</span><br><span class="line">            <span class="keyword">const</span> params = <span class="title class_">JSON</span>.<span class="title function_">parse</span>(</span><br><span class="line">               toolCall.<span class="property">function</span>.<span class="property">arguments</span>,</span><br><span class="line">             ) <span class="keyword">as</span> <span class="title class_">ToolCallParam</span>[<span class="string">&#x27;edit_file&#x27;</span>];</span><br><span class="line">             originalFileContent = <span class="keyword">await</span> ipcClient.<span class="title function_">getFileContent</span>(&#123;</span><br><span class="line">               <span class="attr">filePath</span>: params.<span class="property">path</span>,</span><br><span class="line">             &#125;);</span><br><span class="line">          &#125;</span><br><span class="line">          <span class="keyword">const</span> callResult = (<span class="keyword">await</span> executor.<span class="title function_">executeTools</span>([toolCall]))[<span class="number">0</span>];</span><br><span class="line">          <span class="keyword">await</span> <span class="variable language_">this</span>.<span class="property">eventManager</span>.<span class="title function_">handleToolExecution</span>(&#123;</span><br><span class="line">            toolName,</span><br><span class="line">            <span class="attr">toolCallId</span>: toolCall.<span class="property">id</span>,</span><br><span class="line">            <span class="attr">params</span>: toolCall.<span class="property">function</span>.<span class="property">arguments</span>,</span><br><span class="line">            <span class="attr">result</span>: callResult.<span class="property">content</span>,</span><br><span class="line">            <span class="attr">isError</span>: callResult.<span class="property">isError</span> <span class="keyword">as</span> <span class="built_in">boolean</span>,</span><br><span class="line">          &#125;);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 如果是执行浏览器快照动作</span></span><br><span class="line">        <span class="keyword">if</span> (<span class="variable constant_">SNAPSHOT_BROWSER_ACTIONS</span>.<span class="title function_">includes</span>(toolName)) &#123;</span><br><span class="line">          <span class="comment">//</span></span><br><span class="line">          <span class="keyword">const</span> screenshotPath = <span class="keyword">await</span> ipcClient.<span class="title function_">saveBrowserSnapshot</span>();</span><br><span class="line">          <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;screenshotPath&#x27;</span>, screenshotPath);</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="Aware"><a href="#Aware" class="headerlink" title="Aware"></a>Aware</h4><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">Aware</span> &#123;</span><br><span class="line">  <span class="keyword">private</span> systemPrompt = <span class="string">`You are an AI agent with the ability to analyze the current environment, decide the next task status, tell user the next specific action.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;task_description&gt;</span></span><br><span class="line"><span class="string">You must call the aware_analysis tool.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">You should give the insights of current environment according to the various context information, and then decide the next task status.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">If the task is none or current step is done, you should increment the step number and update system status. Please return the json output in the tool call part:</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">\`\`\`json</span></span><br><span class="line"><span class="string">&#123;</span></span><br><span class="line"><span class="string">  &quot;reflection&quot;: &quot;[your reflection about current environment]&quot;,</span></span><br><span class="line"><span class="string">  &quot;step&quot;: &quot;[next step number]&quot;,</span></span><br><span class="line"><span class="string">  &quot;plan&quot;: &quot;[steps array with id and title fields]&quot;,</span></span><br><span class="line"><span class="string">  &quot;status&quot;: &quot;[next task description, a complete sentence tell user what to do next]&quot;,</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string">\`\`\`</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">You should output the reflection first.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">You should not output any response text and only return the tool call.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">Only when there is no existing plan in the current environment, you should return plan field with the following format:</span></span><br><span class="line"><span class="string">- id: string (format: &quot;step_XXX&quot; where XXX is a sequential number starting from 001)</span></span><br><span class="line"><span class="string">- title: string (clear, concise description of the step)</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;/task_description&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;think_steps&gt;</span></span><br><span class="line"><span class="string">For any given task or problem:</span></span><br><span class="line"><span class="string">1. Analyze the requirements thoroughly</span></span><br><span class="line"><span class="string">2. Create a systematic, step-by-step solution</span></span><br><span class="line"><span class="string">3. Ensure each step is concrete and actionable</span></span><br><span class="line"><span class="string">4. Maintain logical progression between steps</span></span><br><span class="line"><span class="string">&lt;/think_steps&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;limitation&gt;</span></span><br><span class="line"><span class="string">You must follow these limitations:</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">- If there is plan exist, you should not return the plan field.</span></span><br><span class="line"><span class="string">- Don&#x27;t ask user anything, just tell user what to do next. If some points is not very clear, you should tell user your solution. Remember, you are a agent for human.</span></span><br><span class="line"><span class="string">- Don&#x27;t output any response text and only return the tool call.</span></span><br><span class="line"><span class="string">- You should not repeat the same behavior or mean with previous steps.</span></span><br><span class="line"><span class="string">- Don&#x27;t output any file path in current machine and ensure the security in your message. Don&#x27;t output any absolute path in your message.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;/limitation&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;update_plan_in_process&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">Only except user interrupt or start a new session, you CANNOT update the plan!</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">If you reset the plan to a new one, you should also reset the step to number 1.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;/update_plan_in_process&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;status_field&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">In the \`status\` field, you should only return a sentence to tell user what you will do next, and don&#x27;t need to return the reason and other information.Please the the first person perspective to answer, indicating that you are work for the user.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;/status_field&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;end_step&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">If in the last step, but we still have issues to solve, you cannot increment the step number and should continue to solve the issue.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;/end_step&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;user_interrupt&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">For user interrupt input in the middle of the event stream, you should handle it in the first important level and handle it as soon as possible.If current plan tasks cannot match the new user input, you should reset the plan.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;/user_interrupt&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;event_stream&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">The event stream result record the complete response of the agent, you should make next decision base on the history, if current step has not been done, please don&#x27;t increment the step number. If you meet the \`ended\` message, that means you entered a new session and you should reset the plan from scratch.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">In the event stream, the \`observation\` type message is the observation of the tool use, you should attention to the field and judge the task status according to it.When the observer represent the error message, you should reflect the error and solve it in the next step.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;/event_stream&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;after_web_search&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">After \`web_search\` called, then you must select web page from the search result, then you must see the detail of the page, call navigate to get detail. See the detail right away after get search result!</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;/after_web_search&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;write_file&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">When you want to write file, you should list allowed directories and write the file to the allowed directory.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;/write_file&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;language&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">You should use the same language as the user input by default.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;/language&gt;`</span>;</span><br><span class="line">  <span class="keyword">async</span> <span class="title function_">run</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">// 获取环境信息</span></span><br><span class="line">    <span class="keyword">const</span> environmentInfo = <span class="keyword">await</span> <span class="variable language_">this</span>.<span class="property">agentContext</span>.<span class="title function_">getEnvironmentInfo</span>(</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">appContext</span>,</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">agentContext</span>,</span><br><span class="line">    );</span><br><span class="line">    <span class="comment">// 默认结果</span></span><br><span class="line">    <span class="keyword">const</span> defaultResult = &#123;</span><br><span class="line">      <span class="attr">reflection</span>: <span class="string">&#x27;No plan&#x27;</span>,</span><br><span class="line">      <span class="attr">step</span>: <span class="variable language_">this</span>.<span class="property">agentContext</span>.<span class="property">currentStep</span>,</span><br><span class="line">      <span class="attr">status</span>: <span class="string">&#x27;No plan&#x27;</span>,</span><br><span class="line">      <span class="attr">plan</span>: [],</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(resolve, reject) =&gt; &#123;</span><br><span class="line">      <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 获取执行器工具列表</span></span><br><span class="line">        <span class="keyword">const</span> executorTools = <span class="keyword">await</span> ipcClient.<span class="title function_">listTools</span>();</span><br><span class="line">        <span class="comment">// 调用 LLM</span></span><br><span class="line">        <span class="keyword">const</span> result = <span class="keyword">await</span> ipcClient.<span class="title function_">askLLMTool</span>(&#123;</span><br><span class="line">          <span class="attr">messages</span>: [</span><br><span class="line">            <span class="title class_">Message</span>.<span class="title function_">systemMessage</span>(<span class="variable language_">this</span>.<span class="property">systemPrompt</span>),</span><br><span class="line">            <span class="title class_">Message</span>.<span class="title function_">systemMessage</span>(</span><br><span class="line">              <span class="string">`You are working with executor agent, here is the executor tools: <span class="subst">$&#123;executorTools</span></span></span><br><span class="line"><span class="subst"><span class="string">                .map((tool) =&gt; <span class="string">`<span class="subst">$&#123;tool.name&#125;</span>: <span class="subst">$&#123;tool.description&#125;</span>`</span>)</span></span></span><br><span class="line"><span class="subst"><span class="string">                .join(<span class="string">&#x27;, &#x27;</span>)&#125;</span>`</span>,</span><br><span class="line">            ),</span><br><span class="line">            <span class="title class_">Message</span>.<span class="title function_">userMessage</span>(environmentInfo),</span><br><span class="line">            <span class="title class_">Message</span>.<span class="title function_">userMessage</span>(</span><br><span class="line">              <span class="string">`Please call aware_analysis tool to give me next decision.`</span>,</span><br><span class="line">            ),</span><br><span class="line">          ],</span><br><span class="line">          <span class="attr">tools</span>: [</span><br><span class="line">            &#123;</span><br><span class="line">              <span class="attr">type</span>: <span class="string">&#x27;function&#x27;</span>,</span><br><span class="line">              <span class="attr">function</span>: &#123;</span><br><span class="line">                <span class="attr">name</span>: <span class="string">&#x27;aware_analysis&#x27;</span>,</span><br><span class="line">                <span class="attr">description</span>:</span><br><span class="line">                  <span class="string">&#x27;Analyze the current environment with user input, and decide the next task status&#x27;</span>,</span><br><span class="line">                <span class="attr">parameters</span>: <span class="variable language_">this</span>.<span class="property">awareSchema</span>,</span><br><span class="line">              &#125;,</span><br><span class="line">            &#125;,</span><br><span class="line">          ],</span><br><span class="line">          <span class="attr">requestId</span>: streamId,</span><br><span class="line">        &#125;);</span><br><span class="line">        <span class="keyword">const</span> awareResult = <span class="title class_">JSON</span>.<span class="title function_">parse</span>(</span><br><span class="line">          result.<span class="property">tool_calls</span>.<span class="title function_">filter</span>(<span class="title class_">Boolean</span>)[<span class="number">0</span>].<span class="property">function</span>.<span class="property">arguments</span>,</span><br><span class="line">        ) <span class="keyword">as</span> <span class="title class_">AwareResult</span>;</span><br><span class="line">        <span class="title function_">resolve</span>(awareResult);</span><br><span class="line">      &#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">        <span class="title function_">reject</span>(error);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>src/main/ipcRoutes/llm.ts</code></p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> llmRoute = t.<span class="title function_">route</span>(&#123;</span><br><span class="line">  <span class="attr">askLLMTool</span>: t.<span class="property">procedure</span>.<span class="title function_">input</span>().<span class="title function_">handle</span>(<span class="keyword">async</span> (&#123; input &#125;) =&gt; &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> messages = input.<span class="property">messages</span>.<span class="title function_">map</span>(<span class="function">(<span class="params">msg</span>) =&gt;</span> <span class="keyword">new</span> <span class="title class_">Message</span>(msg));</span><br><span class="line">      <span class="keyword">const</span> response = <span class="keyword">await</span> llm.<span class="title function_">askTool</span>(&#123;</span><br><span class="line">        messages,</span><br><span class="line">        <span class="attr">tools</span>: input.<span class="property">tools</span>,</span><br><span class="line">        <span class="attr">mcpServerKeys</span>: input.<span class="property">mcpServerKeys</span>,</span><br><span class="line">        <span class="attr">requestId</span>: input.<span class="property">requestId</span>,</span><br><span class="line">      &#125;);</span><br><span class="line">      <span class="keyword">return</span> response;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (error) &#123;&#125;</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p><code>src/main/llmProvider/index.ts</code></p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">LLM</span> &#123;</span><br><span class="line">  <span class="keyword">async</span> <span class="title function_">askTool</span>(<span class="params">&#123;</span></span><br><span class="line"><span class="params">    messages,</span></span><br><span class="line"><span class="params">    tools,</span></span><br><span class="line"><span class="params">    requestId,</span></span><br><span class="line"><span class="params">    toolChoice,</span></span><br><span class="line"><span class="params">  &#125;</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> allTools = [...tools];</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">await</span> <span class="variable language_">this</span>.<span class="property">provider</span>.<span class="title function_">askTool</span>(&#123;</span><br><span class="line">      messages,</span><br><span class="line">      <span class="attr">tools</span>: allTools,</span><br><span class="line">      requestId,</span><br><span class="line">      <span class="attr">toolChoice</span>: toolChoice || <span class="string">&#x27;auto&#x27;</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>src/main/llmProvider/providers/OpenAIProvider.ts</code>：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">OpenAIProvider</span> <span class="keyword">extends</span> <span class="title class_ inherited__">BaseProvider</span> &#123;</span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * Convert Message objects to OpenAI API format</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="keyword">protected</span> <span class="title function_">formatMessages</span>(</span><br><span class="line">    <span class="attr">messages</span>: <span class="title class_">Message</span>[],</span><br><span class="line">  ): <span class="title class_">OpenAI</span>.<span class="property">Chat</span>.<span class="property">ChatCompletionMessageParam</span>[] &#123;</span><br><span class="line">    <span class="keyword">return</span> messages.<span class="title function_">map</span>(<span class="function">(<span class="params">item</span>) =&gt;</span> (&#123;</span><br><span class="line">      <span class="attr">role</span>: item.<span class="property">role</span> <span class="keyword">as</span> <span class="built_in">any</span>,</span><br><span class="line">      <span class="attr">content</span>: item.<span class="property">content</span>,</span><br><span class="line">      ...(item.<span class="property">tool_call_id</span> &amp;&amp; &#123; <span class="attr">tool_call_id</span>: item.<span class="property">tool_call_id</span> &#125;),</span><br><span class="line">      ...(item.<span class="property">tool_calls</span> &amp;&amp; &#123;</span><br><span class="line">        <span class="attr">tool_calls</span>:</span><br><span class="line">          item.<span class="property">tool_calls</span> <span class="keyword">as</span> <span class="title class_">OpenAI</span>.<span class="property">Chat</span>.<span class="property">ChatCompletionMessageToolCall</span>[],</span><br><span class="line">      &#125;),</span><br><span class="line">      ...(item.<span class="property">name</span> &amp;&amp; &#123; <span class="attr">name</span>: item.<span class="property">name</span> &#125;),</span><br><span class="line">    &#125;));</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * Send a message to the LLM with tools and get a response with potential tool calls</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="keyword">async</span> <span class="title function_">askTool</span>(&#123;</span><br><span class="line">    messages,</span><br><span class="line">    tools,</span><br><span class="line">    requestId,</span><br><span class="line">    toolChoice = <span class="string">&#x27;auto&#x27;</span>,</span><br><span class="line">  &#125;: &#123;</span><br><span class="line">    <span class="attr">messages</span>: <span class="title class_">Message</span>[];</span><br><span class="line">    <span class="attr">tools</span>: <span class="title class_">ChatCompletionTool</span>[];</span><br><span class="line">    <span class="attr">requestId</span>: <span class="built_in">string</span>;</span><br><span class="line">    toolChoice?: <span class="title class_">ToolChoice</span>;</span><br><span class="line">  &#125;): <span class="title class_">Promise</span>&lt;<span class="title class_">LLMResponse</span>&gt; &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> formattedMessages = <span class="variable language_">this</span>.<span class="title function_">formatMessages</span>(messages);</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">activeRequests</span>.<span class="title function_">set</span>(requestId, controller);</span><br><span class="line"></span><br><span class="line">      <span class="keyword">const</span> response = <span class="keyword">await</span> <span class="variable language_">this</span>.<span class="property">client</span>.<span class="property">chat</span>.<span class="property">completions</span>.<span class="title function_">create</span>(</span><br><span class="line">        &#123;</span><br><span class="line">          <span class="attr">model</span>: <span class="variable language_">this</span>.<span class="property">model</span>,</span><br><span class="line">          <span class="attr">messages</span>: formattedMessages,</span><br><span class="line">          <span class="attr">temperature</span>: <span class="variable language_">this</span>.<span class="property">config</span>.<span class="property">temperature</span>,</span><br><span class="line">          <span class="attr">max_tokens</span>: <span class="variable language_">this</span>.<span class="property">config</span>.<span class="property">maxTokens</span>,</span><br><span class="line">          tools,</span><br><span class="line">          <span class="attr">tool_choice</span>: toolChoice,</span><br><span class="line">          <span class="attr">top_p</span>: <span class="variable language_">this</span>.<span class="property">config</span>.<span class="property">topP</span>,</span><br><span class="line">          <span class="attr">frequency_penalty</span>: <span class="variable language_">this</span>.<span class="property">config</span>.<span class="property">frequencyPenalty</span>,</span><br><span class="line">          <span class="attr">presence_penalty</span>: <span class="variable language_">this</span>.<span class="property">config</span>.<span class="property">presencePenalty</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">          <span class="attr">signal</span>: controller.<span class="property">signal</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">      );</span><br><span class="line">      <span class="keyword">const</span> content = response.<span class="property">choices</span>[<span class="number">0</span>].<span class="property">message</span>.<span class="property">content</span>;</span><br><span class="line">      <span class="keyword">const</span> toolCalls = <span class="variable language_">this</span>.<span class="title function_">processToolCalls</span>(response);</span><br><span class="line">      <span class="keyword">return</span> &#123; content, <span class="attr">tool_calls</span>: toolCalls &#125;;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (<span class="attr">error</span>: <span class="built_in">unknown</span>) &#123;&#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="Executor"><a href="#Executor" class="headerlink" title="Executor"></a>Executor</h4><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">Executor</span> &#123;</span><br><span class="line">  <span class="keyword">private</span> systemPrompt = <span class="string">`You are a tool use expert. You should call the appropriate tools according to the aware status and environment information.You should not output any response text and only return the JSON.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;overall_principal&gt;</span></span><br><span class="line"><span class="string">- Must respond with a tool use (function calling); plain text responses are forbidden</span></span><br><span class="line"><span class="string">- Do not mention any specific tool names to users in messages</span></span><br><span class="line"><span class="string">- Carefully verify available tools; do not fabricate non-existent tools</span></span><br><span class="line"><span class="string">- Follow the instructions carefully in the aware status.</span></span><br><span class="line"><span class="string">- Don&#x27;t repeat the same mean with aware status, you should select the appropriate tool.</span></span><br><span class="line"><span class="string">- Don&#x27;t ask user anything, just tell user what you will do next.If some points is not very clear, you should tell user your solution.Don&#x27;t ask user anything, remember, you are a agent for user.</span></span><br><span class="line"><span class="string">- You should only respond chat message after you have finished some tools and return the summary in chat message.</span></span><br><span class="line"><span class="string">- You should not output any response text and only return the tool call.</span></span><br><span class="line"><span class="string">- Don&#x27;t output any file path in current machine and ensure the security in your message. Don&#x27;t output any absolute path in your message.</span></span><br><span class="line"><span class="string">&lt;/overall_principal&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;chat_message_tool&gt;</span></span><br><span class="line"><span class="string">message as summary in current step.Don&#x27;t return message first when the step just started.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">Notice, you should not output a lot of words in chat message, because the chat message is always summary words. If you want to write something in detail, please use \`write_file\` to write in by markdown file by default.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">In chat message tool, you should add the files that has been created in the past steps, and put the complete file path in the \`attachments\` param.</span></span><br><span class="line"><span class="string">&lt;chat_message_tool&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;file_system_tool&gt;</span></span><br><span class="line"><span class="string">If you meet the file system permission denied, you should check if the dir or file exists and create it if not.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">Before you interact with filesystem, you must list the allowed dirs and files and check if the dir or file exists and create it if not.Don&#x27;t write file directly. You should write the file into a safe directory.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;/file_system_tool&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;web_search_tool&gt;</span></span><br><span class="line"><span class="string">After \`web_search\` called, then you must select web page from the search result, then you see the detail of the page, please call browser tool to do it.</span></span><br><span class="line"><span class="string">&lt;/web_search_tool&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;browser_tools&gt;</span></span><br><span class="line"><span class="string">use \`browser_navigate\` to enter the page detail.</span></span><br><span class="line"><span class="string">use \`browser_scroll\` to scroll the page.When you use browser to enter the page detail, if the page content is partially visible, you should call browser tool to scroll to get more content, until the page content is fully visible.</span></span><br><span class="line"><span class="string">use \`browser_click\` to click the element.</span></span><br><span class="line"><span class="string">use \`browser_form_input_fill\` to fill the form.</span></span><br><span class="line"><span class="string">use \`browser_select\` to select the element.</span></span><br><span class="line"><span class="string">use \`browser_hover\` to hover the element.</span></span><br><span class="line"><span class="string">use \`browser_evaluate\` to evaluate the element.</span></span><br><span class="line"><span class="string">use \`browser_get_text\` to get the text of the element.</span></span><br><span class="line"><span class="string">&lt;/browser_tool&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;commands_tool&gt;</span></span><br><span class="line"><span class="string">When you use commands, you must cd the allowed dir instead of cwd.</span></span><br><span class="line"><span class="string">&lt;/commands_tool&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;language&gt;</span></span><br><span class="line"><span class="string">You should use the same language as the user input by default.</span></span><br><span class="line"><span class="string">&lt;/language&gt;`</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>构建出的 executor 结构，我们主要关注</p><h4 id="EventManager"><a href="#EventManager" class="headerlink" title="EventManager"></a>EventManager</h4><p><code>EventManager</code> 类主要是负责和 chat-ui 交互的，由于 chat-ui 没有开源，这块是黑盒的。后续开源之后详细讲解。</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">EventManager</span> &#123;</span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * Get all events</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="keyword">public</span> <span class="title function_">getAllEvents</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> [...<span class="variable language_">this</span>.<span class="property">events</span>];</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * Notify subscribers about event updates</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="keyword">private</span> <span class="title function_">notifyUpdate</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">onEventsUpdate</span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="title function_">onEventsUpdate</span>(<span class="variable language_">this</span>.<span class="title function_">getAllEvents</span>());</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * Add a generic event</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">async</span> <span class="title function_">addEvent</span>(<span class="params"></span></span><br><span class="line"><span class="params">    <span class="keyword">type</span>,</span></span><br><span class="line"><span class="params">    content,</span></span><br><span class="line"><span class="params">    willNotifyUpdate = <span class="literal">true</span>,</span></span><br><span class="line"><span class="params">  </span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> event = &#123;</span><br><span class="line">      <span class="attr">id</span>: <span class="title function_">nanoid</span>(),</span><br><span class="line">      <span class="keyword">type</span>,</span><br><span class="line">      content,</span><br><span class="line">      <span class="attr">timestamp</span>: <span class="title class_">Date</span>.<span class="title function_">now</span>(),</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">events</span>.<span class="title function_">push</span>(event);</span><br><span class="line">    willNotifyUpdate &amp;&amp; (<span class="keyword">await</span> <span class="variable language_">this</span>.<span class="title function_">notifyUpdate</span>());</span><br><span class="line">    <span class="keyword">return</span> event;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * Add an observation event</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">async</span> <span class="title function_">addObservation</span>(<span class="params">content</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="title function_">addEvent</span>(<span class="title class_">EventType</span>.<span class="property">Observation</span>, content);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * Handle tool execution result and add related events</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">async</span> <span class="title function_">handleToolExecution</span>(<span class="params">&#123;</span></span><br><span class="line"><span class="params">    toolName,</span></span><br><span class="line"><span class="params">    toolCallId,</span></span><br><span class="line"><span class="params">    params,</span></span><br><span class="line"><span class="params">    result,</span></span><br><span class="line"><span class="params">    isError,</span></span><br><span class="line"><span class="params">  &#125;</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> normalizedInfo = <span class="title function_">normalizeToolUsedInfo</span>(</span><br><span class="line">      toolName,</span><br><span class="line">      params,</span><br><span class="line">      isError ? <span class="title class_">ActionStatus</span>.<span class="property">Failed</span> : <span class="title class_">ActionStatus</span>.<span class="property">Success</span>,</span><br><span class="line">      result,</span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">    <span class="keyword">await</span> <span class="variable language_">this</span>.<span class="title function_">addEvent</span>(<span class="title class_">EventType</span>.<span class="property">ToolUsed</span>, &#123;</span><br><span class="line">      <span class="attr">actionId</span>: toolCallId,</span><br><span class="line">      ...normalizedInfo,</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">await</span> <span class="variable language_">this</span>.<span class="title function_">addObservation</span>(<span class="title class_">JSON</span>.<span class="title function_">stringify</span>(result));</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>src/renderer/src/type/event.ts</code>：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">enum</span> <span class="title class_">EventType</span> &#123;</span><br><span class="line">  <span class="title class_">UserMessage</span> = <span class="string">&#x27;user-message&#x27;</span>,</span><br><span class="line">  <span class="title class_">LoadingStatus</span> = <span class="string">&#x27;loading-status&#x27;</span>,</span><br><span class="line">  <span class="title class_">ToolUsed</span> = <span class="string">&#x27;tool-used&#x27;</span>,</span><br><span class="line">  <span class="title class_">ToolCallStart</span> = <span class="string">&#x27;tool-call-start&#x27;</span>,</span><br><span class="line">  <span class="title class_">PlanUpdate</span> = <span class="string">&#x27;plan-update&#x27;</span>,</span><br><span class="line">  <span class="title class_">AgentStatus</span> = <span class="string">&#x27;agent-status&#x27;</span>,</span><br><span class="line">  <span class="title class_">ChatText</span> = <span class="string">&#x27;chat-text&#x27;</span>,</span><br><span class="line">  <span class="title class_">Observation</span> = <span class="string">&#x27;observation&#x27;</span>,</span><br><span class="line">  <span class="title class_">NewPlanStep</span> = <span class="string">&#x27;new-plan-step&#x27;</span>,</span><br><span class="line">  <span class="title class_">UserInterruption</span> = <span class="string">&#x27;user-interruption&#x27;</span>,</span><br><span class="line">  <span class="title class_">End</span> = <span class="string">&#x27;end&#x27;</span>,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="api"><a href="#api" class="headerlink" title="api"></a>api</h3><h4 id="ipcClient"><a href="#ipcClient" class="headerlink" title="ipcClient"></a>ipcClient</h4><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; createClient &#125; <span class="keyword">from</span> <span class="string">&#x27;@ui-tars/electron-ipc/renderer&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> ipcClient = <span class="title function_">createClient</span>(&#123;</span><br><span class="line">  <span class="attr">ipcInvoke</span>: <span class="variable language_">window</span>.<span class="property">electron</span>.<span class="property">ipcRenderer</span>.<span class="property">invoke</span>,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>createClient 代码如下：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">IpcRenderer</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;electron&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">createClient</span> = (<span class="params">&#123; ipcInvoke &#125;</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Proxy</span>(&#123;&#125;, &#123;</span><br><span class="line">    <span class="attr">get</span>: <span class="function">(<span class="params">_, prop</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> <span class="title function_">invoke</span> = (<span class="params">input</span>) =&gt; &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="title function_">ipcInvoke</span>(prop.<span class="title function_">toString</span>(), input);</span><br><span class="line">      &#125;;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">return</span> invoke;</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="utils"><a href="#utils" class="headerlink" title="utils"></a>utils</h3><h4 id="initMonacoWorkers"><a href="#initMonacoWorkers" class="headerlink" title="initMonacoWorkers"></a>initMonacoWorkers</h4><p><code>initMonacoWorkers</code> 方法位于 <code>src/utils/monacoConfig</code>：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> monaco <span class="keyword">from</span> <span class="string">&#x27;monaco-editor&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> editorWorker <span class="keyword">from</span> <span class="string">&#x27;monaco-editor/esm/vs/editor/editor.worker?worker&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> jsonWorker <span class="keyword">from</span> <span class="string">&#x27;monaco-editor/esm/vs/language/json/json.worker?worker&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> cssWorker <span class="keyword">from</span> <span class="string">&#x27;monaco-editor/esm/vs/language/css/css.worker?worker&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> htmlWorker <span class="keyword">from</span> <span class="string">&#x27;monaco-editor/esm/vs/language/html/html.worker?worker&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> tsWorker <span class="keyword">from</span> <span class="string">&#x27;monaco-editor/esm/vs/language/typescript/ts.worker?worker&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">initMonacoWorkers</span>(<span class="params"></span>): <span class="built_in">void</span> &#123;</span><br><span class="line">  self.<span class="property">MonacoEnvironment</span> = &#123;</span><br><span class="line">    <span class="title function_">getWorker</span>(<span class="params">_, label</span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (label === <span class="string">&#x27;json&#x27;</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title function_">jsonWorker</span>();</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">if</span> (label === <span class="string">&#x27;css&#x27;</span> || label === <span class="string">&#x27;scss&#x27;</span> || label === <span class="string">&#x27;less&#x27;</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title function_">cssWorker</span>();</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">if</span> (label === <span class="string">&#x27;html&#x27;</span> || label === <span class="string">&#x27;handlebars&#x27;</span> || label === <span class="string">&#x27;razor&#x27;</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title function_">htmlWorker</span>();</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">if</span> (label === <span class="string">&#x27;typescript&#x27;</span> || label === <span class="string">&#x27;javascript&#x27;</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title function_">tsWorker</span>();</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">new</span> <span class="title function_">editorWorker</span>();</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="attr">defaultEditorOptions</span>: monaco.<span class="property">editor</span>.<span class="property">IStandaloneEditorConstructionOptions</span> =</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">readOnly</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">minimap</span>: &#123; <span class="attr">enabled</span>: <span class="literal">false</span> &#125;,</span><br><span class="line">    <span class="attr">scrollBeyondLastLine</span>: <span class="literal">false</span>,</span><br><span class="line">    <span class="attr">fontSize</span>: <span class="number">14</span>,</span><br><span class="line">    <span class="attr">renderLineHighlight</span>: <span class="string">&#x27;all&#x27;</span>,</span><br><span class="line">    <span class="attr">scrollbar</span>: &#123;</span><br><span class="line">      <span class="attr">vertical</span>: <span class="string">&#x27;visible&#x27;</span>,</span><br><span class="line">      <span class="attr">horizontal</span>: <span class="string">&#x27;visible&#x27;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">automaticLayout</span>: <span class="literal">true</span>,</span><br><span class="line">  &#125;;</span><br></pre></td></tr></table></figure><h2 id="主进程（src-main）"><a href="#主进程（src-main）" class="headerlink" title="主进程（src&#x2F;main）"></a>主进程（src&#x2F;main）</h2><h3 id="ipcRoutes"><a href="#ipcRoutes" class="headerlink" title="ipcRoutes"></a>ipcRoutes</h3><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; initIpc, createServer &#125; <span class="keyword">from</span> <span class="string">&#x27;@ui-tars/electron-ipc/main&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> ipcRoutes = t.<span class="title function_">router</span>(&#123;</span><br><span class="line">  ...agentRoute,</span><br><span class="line">  ...llmRoute,</span><br><span class="line">  <span class="comment">// 这里定义了 actionRoute</span></span><br><span class="line">  ...actionRoute,</span><br><span class="line">  ...browserRoute,</span><br><span class="line">  ...fileSystemRoute,</span><br><span class="line">  ...searchRoute,</span><br><span class="line">  ...settingsRoute,</span><br><span class="line">  ...mcpRoute,</span><br><span class="line">&#125;);</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> server = <span class="title function_">createServer</span>(ipcRoutes);</span><br></pre></td></tr></table></figure><h4 id="actionRoute"><a href="#actionRoute" class="headerlink" title="actionRoute"></a>actionRoute</h4><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">MCPServerName</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@agent-infra/shared&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; initIpc &#125; <span class="keyword">from</span> <span class="string">&#x27;@ui-tars/electron-ipc/main&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> t = initIpc.<span class="title function_">create</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> actionRoute = t.<span class="title function_">router</span>(&#123;</span><br><span class="line">  <span class="attr">listTools</span>: t.<span class="property">procedure</span>.<span class="title function_">handle</span>(<span class="keyword">async</span> () =&gt; &#123;&#125;),</span><br><span class="line"></span><br><span class="line">  <span class="attr">listMcpTools</span>: t.<span class="property">procedure</span>.<span class="title function_">handle</span>(<span class="keyword">async</span> () =&gt; &#123;&#125;),</span><br><span class="line"></span><br><span class="line">  <span class="attr">listCustomTools</span>: t.<span class="property">procedure</span>.<span class="title function_">handle</span>(<span class="keyword">async</span> () =&gt; &#123;&#125;),</span><br><span class="line"></span><br><span class="line">  <span class="attr">executeTool</span>: t.<span class="property">procedure</span>.<span class="title function_">input</span>().<span class="title function_">handle</span>(<span class="keyword">async</span> () =&gt; &#123;&#125;),</span><br><span class="line"></span><br><span class="line">  <span class="attr">saveBrowserSnapshot</span>: t.<span class="property">procedure</span>.<span class="title function_">input</span>().<span class="title function_">handle</span>(<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">    <span class="comment">// 通过 mcpClient 调用浏览器截图工具</span></span><br><span class="line">    <span class="keyword">const</span> result = <span class="keyword">await</span> mcpClient,<span class="title function_">callTool</span>(&#123;</span><br><span class="line">      <span class="attr">client</span>: <span class="title class_">MCPServerName</span>.<span class="property">Browser</span>,</span><br><span class="line">      <span class="attr">name</span>: <span class="string">&#x27;browser_screenshot&#x27;</span>,</span><br><span class="line">      <span class="attr">args</span>: &#123;</span><br><span class="line">        <span class="attr">highlight</span>: <span class="literal">true</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">    <span class="keyword">const</span> screenshotMeta = (</span><br><span class="line">      result.<span class="property">content</span> <span class="keyword">as</span> [</span><br><span class="line">        &#123; <span class="attr">type</span>: <span class="string">&#x27;text&#x27;</span>; <span class="attr">text</span>: <span class="built_in">string</span> &#125;,</span><br><span class="line">        &#123; <span class="attr">type</span>: <span class="string">&#x27;image&#x27;</span>; <span class="attr">data</span>: <span class="built_in">string</span>; <span class="attr">mimeType</span>: <span class="built_in">string</span> &#125;,</span><br><span class="line">      ]</span><br><span class="line">    )[<span class="number">1</span>];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> omegaDir = <span class="keyword">await</span> <span class="title function_">getOmegaDir</span>();</span><br><span class="line">    <span class="keyword">const</span> screenshotPath = path.<span class="title function_">join</span>(omegaDir, <span class="string">&#x27;screenshots&#x27;</span>);</span><br><span class="line">    <span class="keyword">await</span> fs.<span class="title function_">mkdirSync</span>(screenshotPath, &#123; <span class="attr">recursive</span>: <span class="literal">true</span> &#125;);</span><br><span class="line">    <span class="keyword">const</span> ext = screenshotMeta.<span class="property">mimeType</span>.<span class="title function_">split</span>(<span class="string">&#x27;/&#x27;</span>)[<span class="number">1</span>] || <span class="string">&#x27;png&#x27;</span>;</span><br><span class="line">    <span class="keyword">const</span> timestamp = <span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">getTime</span>();</span><br><span class="line">    <span class="keyword">const</span> filename = <span class="string">`screenshot_<span class="subst">$&#123;timestamp&#125;</span>.<span class="subst">$&#123;ext&#125;</span>`</span>;</span><br><span class="line">    <span class="keyword">const</span> filepath = path.<span class="title function_">join</span>(screenshotPath, filename);</span><br><span class="line">    <span class="keyword">const</span> imageBuffer = <span class="title class_">Buffer</span>.<span class="title function_">from</span>(screenshotMeta.<span class="property">data</span>, <span class="string">&#x27;base64&#x27;</span>);</span><br><span class="line">    <span class="keyword">await</span> fs.<span class="title function_">writeFile</span>(filepath, imageBuffer);</span><br><span class="line">    <span class="keyword">return</span> &#123; filepath &#125;;</span><br><span class="line">  &#125;),</span><br><span class="line"></span><br><span class="line">  <span class="attr">saveReportHtml</span>: t.<span class="property">procedure</span>.<span class="title function_">input</span>().<span class="title function_">handle</span>(<span class="keyword">async</span> (&#123; input &#125;) =&gt; &#123;&#125;),</span><br><span class="line"></span><br><span class="line">  <span class="attr">cleanup</span>: t.<span class="property">procedure</span>.<span class="title function_">handle</span>(<span class="keyword">async</span> () =&gt; &#123;&#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h3 id="mcp-client"><a href="#mcp-client" class="headerlink" title="mcp client"></a>mcp client</h3><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">MCPServerName</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@agent-infra/shared&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取OmegaDir</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">getOmegaDir</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="comment">// Create working directory in user&#x27;s home directory.</span></span><br><span class="line">  <span class="keyword">const</span> omegaDir = path.<span class="title function_">join</span>(os.<span class="title function_">homedir</span>(), <span class="string">&#x27;.omega&#x27;</span>);</span><br><span class="line">  <span class="keyword">if</span> (!fs.<span class="title function_">existsSync</span>(omegaDir)) &#123;</span><br><span class="line">    <span class="keyword">await</span> fs.<span class="title function_">mkdir</span>(omegaDir, &#123; <span class="attr">recursive</span>: <span class="literal">true</span> &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> omegaDir;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">//  <span class="doctag">TODO:</span> 这里我很奇为什么不直接用 import，有空和三元大佬请教下</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">dynamicImport</span> = (<span class="params">url</span>) =&gt; <span class="keyword">new</span> <span class="title class_">Function</span>(<span class="string">`return import(&#x27;<span class="subst">$&#123;url&#125;</span>&#x27;)`</span>)();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 实现一个基于 commands、filesystem 和 browser 的 MCP 客户端</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">createMcpClient</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="comment">// 动态导入 mcp server 模块</span></span><br><span class="line">  <span class="keyword">const</span> commandModule = <span class="keyword">await</span> <span class="title function_">dynamicImport</span>(<span class="string">&#x27;@agent-infra/mcp-server-commands&#x27;</span>);</span><br><span class="line">  <span class="keyword">const</span> fsModule = <span class="keyword">await</span> <span class="title function_">dynamicImport</span>(<span class="string">&#x27;@agent-infra/mcp-server-filesystem&#x27;</span>);</span><br><span class="line">  <span class="keyword">const</span> browserModule = <span class="keyword">await</span> <span class="title function_">dynamicImport</span>(<span class="string">&#x27;@agent-infra/mcp-server-browser&#x27;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> &#123; <span class="attr">client</span>: commandClient &#125; = commandModule.<span class="property">default</span>;</span><br><span class="line">  <span class="keyword">const</span> &#123; <span class="attr">client</span>: fsClient, setAllowedDirectories &#125; = fsModule.<span class="property">default</span>;</span><br><span class="line">  <span class="keyword">const</span> &#123; <span class="attr">client</span>: browserClient &#125; = browserModule.<span class="property">default</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 共享的 fClientModule 模块</span></span><br><span class="line">  fsClientModule = fsModule.<span class="property">default</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 设置允许的目录</span></span><br><span class="line">  <span class="keyword">const</span> omegaDir = <span class="keyword">await</span> <span class="title function_">getOmegaDir</span>();</span><br><span class="line">  <span class="title function_">setAllowedDirectories</span>([omegaDir]);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 工具 map</span></span><br><span class="line">  <span class="keyword">const</span> toolsMap = &#123;</span><br><span class="line">    [<span class="title class_">MCPServerName</span>.<span class="property">FileSystem</span>]: &#123;</span><br><span class="line">      <span class="attr">name</span>: <span class="title class_">MCPServerName</span>.<span class="property">FileSystem</span>,</span><br><span class="line">      <span class="attr">description</span>: <span class="string">&#x27;filesystem tool&#x27;</span>,</span><br><span class="line">      <span class="attr">localClient</span>: fsClient,</span><br><span class="line">    &#125;,</span><br><span class="line">    [<span class="title class_">MCPServerName</span>.<span class="property">Commands</span>]: &#123;</span><br><span class="line">      <span class="attr">name</span>: <span class="title class_">MCPServerName</span>.<span class="property">Commands</span>,</span><br><span class="line">      <span class="attr">description</span>: <span class="string">&#x27;commands tool&#x27;</span>,</span><br><span class="line">      <span class="attr">localClient</span>: commandClient,</span><br><span class="line">    &#125;,</span><br><span class="line">    [<span class="title class_">MCPServerName</span>.<span class="property">Browser</span>]: &#123;</span><br><span class="line">      <span class="attr">name</span>: <span class="title class_">MCPServerName</span>.<span class="property">Browser</span>,</span><br><span class="line">      <span class="attr">local</span>: <span class="literal">true</span>,</span><br><span class="line">      <span class="attr">description</span>: <span class="string">&#x27;browser tools&#x27;</span>,</span><br><span class="line">      <span class="attr">localClient</span>: browserClient,</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  logger.<span class="title function_">info</span>(<span class="string">&#x27;toolsMap&#x27;</span>, toolsMap);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> client = <span class="keyword">new</span> <span class="title class_">MCPClient</span>(<span class="title class_">Object</span>.<span class="title function_">values</span>(toolsMap));</span><br><span class="line">  mapClientRef.<span class="property">current</span> = client;</span><br><span class="line">  <span class="keyword">return</span> client;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="agent-infra-shared"><a href="#agent-infra-shared" class="headerlink" title="@agent-infra/shared"></a><code>@agent-infra/shared</code></h2><p>TODO</p><h2 id="agent-infra-mcp-client"><a href="#agent-infra-mcp-client" class="headerlink" title="@agent-infra/mcp-client"></a><code>@agent-infra/mcp-client</code></h2><p>基于 <code>@modelcontextprotocol/sdk/client/index.js</code> 和 <code>@modelcontextprotocol/sdk/client/sse.js</code> 实现的 MCP 客户端。负责管理 MCP 服务器和工具调用。</p><h2 id="agent-infra-mcp-server-browser"><a href="#agent-infra-mcp-server-browser" class="headerlink" title="@agent-infra/mcp-server-browser"></a><code>@agent-infra/mcp-server-browser</code></h2><blockquote><p>模块位于 <code>packages/agent-infra/mcp-servers/browser</code></p></blockquote><h3 id="server-ts"><a href="#server-ts" class="headerlink" title="server.ts"></a>server.ts</h3><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123;</span><br><span class="line">  getBuildDomTreeScript,</span><br><span class="line">  parseNode,</span><br><span class="line">  <span class="keyword">type</span> <span class="title class_">RawDomTreeNode</span>,</span><br><span class="line">  <span class="title class_">DOMElementNode</span>,</span><br><span class="line">  createSelectorMap,</span><br><span class="line">  removeHighlights,</span><br><span class="line">  waitForPageAndFramesLoad,</span><br><span class="line">  locateElement,</span><br><span class="line">  scrollIntoViewIfNeeded,</span><br><span class="line">&#125; <span class="keyword">from</span> <span class="string">&#x27;@agent-infra/browser-use&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> <span class="attr">globalBrowser</span>: <span class="title class_">LocalBrowser</span>[<span class="string">&#x27;browser&#x27;</span>] | <span class="literal">undefined</span>;</span><br><span class="line"><span class="keyword">let</span> <span class="attr">globalPage</span>: <span class="title class_">Page</span> | <span class="literal">undefined</span>;</span><br><span class="line"><span class="keyword">let</span> <span class="attr">selectorMap</span>: <span class="title class_">Map</span>&lt;<span class="built_in">number</span>, <span class="title class_">DOMElementNode</span>&gt; | <span class="literal">undefined</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> screenshots = <span class="keyword">new</span> <span class="title class_">Map</span>&lt;<span class="built_in">string</span>, <span class="built_in">string</span>&gt;();</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">getScreenshots</span> = (<span class="params"></span>) =&gt; screenshots;</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">buildDomTree</span>(<span class="params">page: Page</span>) &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> rawDomTree = <span class="keyword">await</span> page.evaluate(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="comment">// Access buildDomTree from the window context of the target page</span></span><br><span class="line">      <span class="keyword">return</span> <span class="variable language_">window</span>.<span class="title function_">buildDomTree</span>(&#123;</span><br><span class="line">        <span class="attr">doHighlightElements</span>: <span class="literal">true</span>,</span><br><span class="line">        <span class="attr">focusHighlightIndex</span>: -<span class="number">1</span>,</span><br><span class="line">        <span class="attr">viewportExpansion</span>: <span class="number">0</span>,</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">if</span> (rawDomTree !== <span class="literal">null</span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> elementTree = <span class="title function_">parseNode</span>(rawDomTree <span class="keyword">as</span> <span class="title class_">RawDomTreeNode</span>);</span><br><span class="line">      <span class="keyword">if</span> (elementTree !== <span class="literal">null</span> &amp;&amp; elementTree <span class="keyword">instanceof</span> <span class="title class_">DOMElementNode</span>) &#123;</span><br><span class="line">        <span class="keyword">const</span> clickableElements = elementTree.<span class="title function_">clickableElementsToString</span>();</span><br><span class="line">        selectorMap = <span class="title function_">createSelectorMap</span>(elementTree);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">          clickableElements,</span><br><span class="line">          elementTree,</span><br><span class="line">          selectorMap,</span><br><span class="line">        &#125;;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">  &#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">    logger.<span class="title function_">error</span>(<span class="string">&#x27;Error building DOM tree:&#x27;</span>, error);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="attr">handleToolCall</span>: <span class="title class_">Client</span>[<span class="string">&#x27;callTool&#x27;</span>] = <span class="keyword">async</span> (&#123;</span><br><span class="line">  name,</span><br><span class="line">  <span class="attr">arguments</span>: toolArgs,</span><br><span class="line">&#125;) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> initialBrowser = <span class="keyword">await</span> <span class="title function_">setInitialBrowser</span>();</span><br><span class="line">  <span class="keyword">const</span> &#123; browser &#125; = initialBrowser;</span><br><span class="line">  <span class="keyword">let</span> &#123; page &#125; = initialBrowser;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (!page) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      <span class="attr">content</span>: [&#123; <span class="attr">type</span>: <span class="string">&#x27;text&#x27;</span>, <span class="attr">text</span>: <span class="string">&#x27;Page not found&#x27;</span> &#125;],</span><br><span class="line">      <span class="attr">isError</span>: <span class="literal">true</span>,</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> handlers = &#123;</span><br><span class="line">    <span class="comment">//  浏览器后退</span></span><br><span class="line">    <span class="attr">browser_go_back</span>: <span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">      <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">all</span>([<span class="title function_">waitForPageAndFramesLoad</span>(page), page.<span class="title function_">goBack</span>()]);</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">//  浏览器前进</span></span><br><span class="line">    <span class="attr">browser_go_forward</span>: <span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">      <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">all</span>([<span class="title function_">waitForPageAndFramesLoad</span>(page), page.<span class="title function_">goForward</span>()]);</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">browser_navigate</span>: <span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">      <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">all</span>([</span><br><span class="line">          <span class="title function_">waitForPageAndFramesLoad</span>(page),</span><br><span class="line">          page.<span class="title function_">goto</span>(args.<span class="property">url</span>),</span><br><span class="line">        ]);</span><br><span class="line">      <span class="comment">// 获取可点击元素</span></span><br><span class="line">      <span class="keyword">const</span> &#123; clickableElements &#125; = (<span class="keyword">await</span> <span class="title function_">buildDomTree</span>(page)) || &#123;&#125;;</span><br><span class="line">      <span class="keyword">return</span> &#123;</span><br><span class="line">          <span class="attr">content</span>: [</span><br><span class="line">            &#123;</span><br><span class="line">              <span class="attr">type</span>: <span class="string">&#x27;text&#x27;</span>,</span><br><span class="line">              <span class="attr">text</span>: <span class="string">`Navigated to <span class="subst">$&#123;args.url&#125;</span>\nclickable elements: <span class="subst">$&#123;clickableElements&#125;</span>`</span>,</span><br><span class="line">            &#125;,</span><br><span class="line">          ],</span><br><span class="line">          <span class="attr">isError</span>: <span class="literal">false</span>,</span><br><span class="line">        &#125;;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">browser_screenshot</span>: <span class="keyword">async</span> (args) =&gt; &#123;</span><br><span class="line">      <span class="keyword">if</span> (args.<span class="property">highlight</span>) &#123;</span><br><span class="line">        <span class="keyword">await</span> <span class="title function_">buildDomTree</span>(page);</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">await</span> <span class="title function_">removeHighlights</span>(page);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">const</span> width = args.<span class="property">width</span> ?? page.<span class="title function_">viewport</span>()?.<span class="property">width</span> ?? <span class="number">800</span>;</span><br><span class="line">      <span class="keyword">const</span> height = args.<span class="property">height</span> ?? page.<span class="title function_">viewport</span>()?.<span class="property">height</span> ?? <span class="number">600</span>;</span><br><span class="line">      <span class="keyword">await</span> page.<span class="title function_">setViewport</span>(&#123; width, height &#125;);</span><br><span class="line">      <span class="keyword">const</span> screenshot = <span class="keyword">await</span> (args.<span class="property">selector</span></span><br><span class="line">        ? (<span class="keyword">await</span> page.$(args.<span class="property">selector</span>))?.<span class="title function_">screenshot</span>(&#123; <span class="attr">encoding</span>: <span class="string">&#x27;base64&#x27;</span> &#125;)</span><br><span class="line">        : page.<span class="title function_">screenshot</span>(&#123; <span class="attr">encoding</span>: <span class="string">&#x27;base64&#x27;</span>, <span class="attr">fullPage</span>: <span class="literal">false</span> &#125;));</span><br><span class="line">      screenshots.<span class="title function_">set</span>(args.<span class="property">name</span>, screenshot <span class="keyword">as</span> <span class="built_in">string</span>);</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">browser_get_clickable_elements</span>: <span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">      <span class="keyword">const</span> &#123; clickableElements &#125; = (<span class="keyword">await</span> <span class="title function_">buildDomTree</span>(page)) || &#123;&#125;;</span><br><span class="line">      <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="attr">content</span>: [</span><br><span class="line">          &#123;</span><br><span class="line">            <span class="attr">type</span>: <span class="string">&#x27;text&#x27;</span>,</span><br><span class="line">            <span class="attr">text</span>: clickableElements,</span><br><span class="line">          &#125;,</span><br><span class="line">        ],</span><br><span class="line">        <span class="attr">isError</span>: <span class="literal">false</span>,</span><br><span class="line">      &#125;;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">browser_click</span>: <span class="title function_">async</span>() =&gt; &#123;</span><br><span class="line">      <span class="keyword">const</span> elementNode = selectorMap?.<span class="title function_">get</span>(<span class="title class_">Number</span>(args?.<span class="property">index</span>));</span><br><span class="line">      <span class="keyword">const</span> element = <span class="keyword">await</span> <span class="title function_">locateElement</span>(page, elementNode!);</span><br><span class="line">      <span class="keyword">await</span> <span class="title function_">scrollIntoViewIfNeeded</span>(element);</span><br><span class="line"></span><br><span class="line">      <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">race</span>([</span><br><span class="line">        element.<span class="title function_">click</span>(),</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">_, reject</span>) =&gt;</span></span><br><span class="line">          <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> <span class="title function_">reject</span>(<span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;Click timeout&#x27;</span>)), <span class="number">5000</span>),</span><br><span class="line">        ),</span><br><span class="line">      ]);</span><br><span class="line">      <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="attr">content</span>: [</span><br><span class="line">          &#123;</span><br><span class="line">            <span class="attr">type</span>: <span class="string">&#x27;text&#x27;</span>,</span><br><span class="line">            <span class="attr">text</span>: <span class="string">`Clicked element: <span class="subst">$&#123;args.index&#125;</span>`</span>,</span><br><span class="line">          &#125;,</span><br><span class="line">        ],</span><br><span class="line">        <span class="attr">isError</span>: <span class="literal">false</span>,</span><br><span class="line">      &#125;;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (handlers[name]) &#123;</span><br><span class="line">    <span class="keyword">return</span> handlers[name](toolArgs);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="attr">client</span>: <span class="title class_">Pick</span>&lt;<span class="title class_">Client</span>, <span class="string">&#x27;callTool&#x27;</span> | <span class="string">&#x27;listTools&#x27;</span> | <span class="string">&#x27;close&#x27;</span> | <span class="string">&#x27;ping&#x27;</span>&gt; =</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">callTool</span>: handleToolCall,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://www.rustc.cloud/mcp">基于 MCP 的 AI Agent 应用开发实践</a></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/b9eba41ae7e3/</id>
    <link href="https://blog.zisheng.pro/posts/b9eba41ae7e3/"/>
    <published>2025-04-13T12:14:59.000Z</published>
    <summary>深入阅读字节跳动开源的 agent-tars 源码，剖析其基于 Electron 的双进程架构、Aware/Executor/AgentFlow 三层 Agent 循环设计，以及 MCP 工具调用与事件管理机制。</summary>
    <title>字节 agent-tars 技术揭秘（硬啃）</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="工具" scheme="https://blog.zisheng.pro/categories/%E5%B7%A5%E5%85%B7/"/>
    <category term="Typora" scheme="https://blog.zisheng.pro/tags/Typora/"/>
    <category term="激活" scheme="https://blog.zisheng.pro/tags/%E6%BF%80%E6%B4%BB/"/>
    <content>
      <![CDATA[<p>1、首先去官网选择 Mac 版本下载安装 <a href="https://www.typoraio.cn/">typora</a></p><p>2、然后打开 Typora 包内容找到</p><p><code>/Applications/Typora.app/Contents/Resources/TypeMark/</code></p><p>3、编辑器打开上面文件夹，这里我拉到 <code>vscode</code> 找到 <code>page-dist/static/js/LicenseIndex.xxx.chunk.js</code>如下图</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1743758579051.png" alt="图 1"></p><p>4、输入 <code>hasActivated=&quot;true&quot;==e.hasActivated</code> 进行搜索，将它改为 <code>hasActivated=&quot;true&quot;==&quot;true&quot;</code></p><p>5、重新打开 Typora 看到成功激活！！！</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1743757036182.png"></p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/3d641fa333c2/</id>
    <link href="https://blog.zisheng.pro/posts/3d641fa333c2/"/>
    <published>2025-04-04T08:56:53.000Z</published>
    <summary>记录 Typora Mac 版本的激活方式，通过修改 LicenseIndex chunk 文件中的 hasActivated 判断逻辑，免费解锁 Typora 的全部功能。</summary>
    <title>Typora Mac 版本激活</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="投资理财" scheme="https://blog.zisheng.pro/categories/%E6%8A%95%E8%B5%84%E7%90%86%E8%B4%A2/"/>
    <category term="QDII" scheme="https://blog.zisheng.pro/tags/QDII/"/>
    <category term="理财" scheme="https://blog.zisheng.pro/tags/%E7%90%86%E8%B4%A2/"/>
    <category term="定投" scheme="https://blog.zisheng.pro/tags/%E5%AE%9A%E6%8A%95/"/>
    <category term="指数基金" scheme="https://blog.zisheng.pro/tags/%E6%8C%87%E6%95%B0%E5%9F%BA%E9%87%91/"/>
    <content>
      <![CDATA[<h2 id="QDII-基金是什么？"><a href="#QDII-基金是什么？" class="headerlink" title="QDII 基金是什么？"></a>QDII 基金是什么？</h2><p>QDII（Qualified Domestic Institutional Investor，合格境内机构投资者）是中国在资本项目未完全开放的背景下推出的跨境投资机制，允许境内投资者通过持牌机构间接参与境外证券市场（如美股、港股等）。其核心特点包括：</p><ol><li><strong>​投资范围广</strong>：可配置股票、债券、大宗商品等全球资产；</li><li><strong>汇率风险</strong>：需人民币兑换外币投资，收益受汇率波动影响；</li><li><strong>制度优势</strong>：突破个人换汇限额（5万美元&#x2F;年），提供合规境外投资渠道。</li></ol><h2 id="小白无脑定投产品推荐"><a href="#小白无脑定投产品推荐" class="headerlink" title="小白无脑定投产品推荐"></a>小白无脑定投产品推荐</h2><p>对于新手投资者，以下三类 QDII 基金及策略兼顾低风险与易操作性，适合长期定投：</p><h3 id="宽基指数型-QDII（50-）"><a href="#宽基指数型-QDII（50-）" class="headerlink" title="宽基指数型 QDII（50%）"></a>宽基指数型 QDII（50%）</h3><ul><li><strong>标普 500 指数基金</strong>（如易方达标普 500 指数基金）<br>追踪美股大盘，覆盖苹果、微软等龙头企业，管理费0.8%，2024年收益率达21.5%</li><li><strong>纳斯达克100 ETF</strong>（如华夏纳斯达克100 ETF）<br>聚焦科技巨头（如英伟达、亚马逊），受益于AI和数字经济浪潮，2024年溢价率需关注</li></ul><h3 id="中概股主题-QDII（30-）"><a href="#中概股主题-QDII（30-）" class="headerlink" title="中概股主题 QDII（30%）"></a>中概股主题 QDII（30%）</h3><ul><li><strong>中概互联网ETF</strong>（如易方达中概互联50）<br>一篮子持有阿里、腾讯等海外上市中国科技公司，估值处于历史低位，适合逢低布局</li></ul><h3 id="低波动稳健型QDII（20-）"><a href="#低波动稳健型QDII（20-）" class="headerlink" title="低波动稳健型QDII（20%）"></a>低波动稳健型QDII（20%）</h3><ul><li><strong>全球债券型QDII</strong>（如摩根全球新兴市场债券）<br>配置海外高评级债券，年化收益约4%-5%，波动率低于股票型基金。</li><li><strong>红利低波ETF</strong>（如招商中证红利ETF）<br>高股息策略，在降息周期中提供稳定现金流，适合风险厌恶型投资者。</li></ul><h3 id="定投实操建议"><a href="#定投实操建议" class="headerlink" title="定投实操建议"></a>定投实操建议</h3><ul><li>账户选择：通过支付宝、天天基金等平台直接购买，支持10元起投；</li><li>频率与金额：建议月定投，分配比例示例：50%宽基指数（如标普500）+30%中概股+20%债券基金；</li><li>​风险对冲：<ul><li>人民币升值周期加大换汇比例，降低汇率损失；</li><li>避免高溢价基金（部分原油、科技类QDII溢价超10%）。</li></ul></li></ul><h3 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h3><ul><li><strong>​申赎规则</strong>：多数 QDII 为 T+2 确认份额，赎回到账需 3-5 个工作日；</li><li><strong>​费用控制</strong>：选择管理费低于 0.5% 的产品，避免侵蚀长期收益；</li><li><strong>​长期视角</strong>：定投周期建议 3 年以上，穿越市场周期获取复利效应。</li></ul><h3 id="ETF-是什么？"><a href="#ETF-是什么？" class="headerlink" title="ETF 是什么？"></a>ETF 是什么？</h3><p>ETF（Exchange-Traded Fund），全称“交易型开放式指数基金”，是一种在证券交易所上市交易的开放式基金，结合了股票和基金的优点，通过跟踪特定指数、商品或资产组合的表现，为投资者提供高效、低成本的市场参与工具。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/38c2eb16ce85/</id>
    <link href="https://blog.zisheng.pro/posts/38c2eb16ce85/"/>
    <published>2025-03-27T20:39:55.000Z</published>
    <summary>QDII（合格境内机构投资者）是普通投资者参与美股、港股等境外市场的合规渠道。本文系统介绍 QDII 基金的核心机制，并推荐适合新手的宽基指数、中概股及低波动三类定投组合。</summary>
    <title>QDII 是什么？懒人无脑定投的产品有哪些？</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="后端" scheme="https://blog.zisheng.pro/categories/%E5%90%8E%E7%AB%AF/"/>
    <category term="Java" scheme="https://blog.zisheng.pro/categories/%E5%90%8E%E7%AB%AF/Java/"/>
    <category term="Mac" scheme="https://blog.zisheng.pro/tags/Mac/"/>
    <category term="Java" scheme="https://blog.zisheng.pro/tags/Java/"/>
    <category term="Maven" scheme="https://blog.zisheng.pro/tags/Maven/"/>
    <content>
      <![CDATA[<h2 id="Java"><a href="#Java" class="headerlink" title="Java"></a>Java</h2><h3 id="下载安装"><a href="#下载安装" class="headerlink" title="下载安装"></a>下载安装</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">brew install --cask zulu@17</span><br></pre></td></tr></table></figure><h3 id="实用命令"><a href="#实用命令" class="headerlink" title="实用命令"></a>实用命令</h3><h4 id="查看-jdk-版本及其安装目录"><a href="#查看-jdk-版本及其安装目录" class="headerlink" title="查看 jdk 版本及其安装目录"></a>查看 jdk 版本及其安装目录</h4><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ /usr/libexec/java_home -V</span><br><span class="line">Matching Java Virtual Machines (1):</span><br><span class="line">    17.0.14 (arm64) <span class="string">&quot;Azul Systems, Inc.&quot;</span> - <span class="string">&quot;Zulu 17.56.15&quot;</span> /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home</span><br><span class="line">/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home</span><br></pre></td></tr></table></figure><h4 id="查看-jre-版本"><a href="#查看-jre-版本" class="headerlink" title="查看 jre 版本"></a>查看 jre 版本</h4><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ java -version</span><br><span class="line">openjdk 17.0.14 2025-01-21 LTS</span><br><span class="line">OpenJDK Runtime Environment Zulu17.56+15-CA (build 17.0.14+7-LTS)</span><br><span class="line">OpenJDK 64-Bit Server VM Zulu17.56+15-CA (build 17.0.14+7-LTS, mixed mode, sharing)</span><br></pre></td></tr></table></figure><h4 id="查看-jdk-版本"><a href="#查看-jdk-版本" class="headerlink" title="查看 jdk 版本"></a>查看 jdk 版本</h4><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ javac --version</span><br><span class="line">javac 17.0.14</span><br></pre></td></tr></table></figure><h2 id="Maven"><a href="#Maven" class="headerlink" title="Maven"></a>Maven</h2><h3 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ brew install maven</span><br></pre></td></tr></table></figure><h3 id="配置阿里镜像"><a href="#配置阿里镜像" class="headerlink" title="配置阿里镜像"></a>配置阿里镜像</h3><p><code>setting.xml</code> 路径为 <code>${Maven Home}/conf/settings.xml</code> ，Maven Home 可以通过 <code>mvn --version</code></p><p>可将 <code>settings.xml</code> 直接拷贝到 <code>.m2</code> 文件夹下，进行配置。</p><p>如果没有 <code>.m2</code> 文件夹时,运行命令</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ mvn <span class="built_in">help</span>:system</span><br></pre></td></tr></table></figure><p>然后打开当前用户的目录，可以在其中找到 <code>.m2</code> 文件夹，在 <code>settings.xml</code> 的 <code>&lt;mirrors&gt;</code> 标签内添加：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">mirror</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">id</span>&gt;</span>alimaven<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">name</span>&gt;</span>aliyun maven<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">url</span>&gt;</span>http://maven.aliyun.com/nexus/content/groups/public/<span class="tag">&lt;/<span class="name">url</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">mirrorOf</span>&gt;</span>central<span class="tag">&lt;/<span class="name">mirrorOf</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">mirror</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="升级-Maven"><a href="#升级-Maven" class="headerlink" title="升级 Maven"></a>升级 Maven</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ brew update</span><br><span class="line">$ brew install maven</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/93892f1c6e2d/</id>
    <link href="https://blog.zisheng.pro/posts/93892f1c6e2d/"/>
    <published>2025-03-06T23:38:26.000Z</published>
    <summary>介绍如何在 Mac（Apple Silicon）上通过 Homebrew 安装 Zulu JDK 17、配置 Maven 阿里云镜像，快速搭建 Java 开发环境。</summary>
    <title>在 Mac 上搭建 Java 开发环境</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="浏览器" scheme="https://blog.zisheng.pro/categories/%E6%B5%8F%E8%A7%88%E5%99%A8/"/>
    <category term="mac" scheme="https://blog.zisheng.pro/tags/mac/"/>
    <category term="v8" scheme="https://blog.zisheng.pro/tags/v8/"/>
    <category term="安装" scheme="https://blog.zisheng.pro/tags/%E5%AE%89%E8%A3%85/"/>
    <content>
      <![CDATA[<blockquote><p>不要使用 <code>brew install v8</code>，因为可用命令是不完整的。</p></blockquote><h2 id="预备条件"><a href="#预备条件" class="headerlink" title="预备条件"></a>预备条件</h2><ul><li>安装 Xcode (Avaliable on the Mac App Store)</li><li>安装 Xcode Command Line Tools (Preferences &gt; Downloads)</li><li>安装 <a href="https://www.chromium.org/developers/how-tos/install-depot-tools">depot_tools</a><ul><li><code>cd ~ &amp;&amp; git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git</code></li><li><code>nano ~/.zshrc</code></li><li>添加 <code>export PATH=~/depot_tools:&quot;$PATH&quot;</code></li><li><code>source ~/.zshrc</code></li></ul></li><li>从你想要安装 V8 的目录中，运行 <code>gclient</code>。</li></ul><h2 id="Build-V8"><a href="#Build-V8" class="headerlink" title="Build V8"></a>Build V8</h2><ul><li><code>fetch v8</code></li><li><code>cd ~/v8</code></li><li><code>gclient sync</code></li></ul><p>Intel Macs:</p><ul><li><code>tools/dev/v8gen.py x64.optdebug</code></li><li><code>ninja -C out.gn/x64.optdebug</code></li></ul><p>ARM(M1) Macs:</p><ul><li><code>tools/dev/v8gen.py arm64.optdebug</code></li><li><code>ninja -C out/arm64.optdebug</code></li></ul><p>我建议你将这些添加到你的 <code>.zshrc</code> 文件中。</p><ul><li><code>sudo nano ~/.zshrc</code></li><li>添加 <code>alias d8=~/v8/out/arm64.optdebug/d8</code></li><li>添加 <code>alias tick-processor=~/v8/tools/mac-tick-processor</code></li><li>添加 <code>export D8_PATH=&quot;~/v8/out/arm64.optdebug&quot;</code></li><li><code>source ~/.zshrc</code></li></ul><h2 id="d8-shell-examples"><a href="#d8-shell-examples" class="headerlink" title="d8 shell examples"></a>d8 shell examples</h2><p>Note: many of these examples have become outdated as <code>v8</code> continues to evolve. Hoping to update them in the near future, but for now please be aware that some may not work as expected or may reference optimizations&#x2F;deoptimizations that no longer happen.</p><h3 id="Print-optimization-stats"><a href="#Print-optimization-stats" class="headerlink" title="Print optimization stats"></a>Print optimization stats</h3><p>Create <code>test.js</code> with the following code:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">test</span>(<span class="params"> obj </span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> obj.<span class="property">prop</span> + obj.<span class="property">prop</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> a = &#123; <span class="attr">prop</span>: <span class="string">&#x27;a&#x27;</span> &#125;, i = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> ( i++ &lt; <span class="number">10000</span> ) &#123;</span><br><span class="line">  <span class="title function_">test</span>( a );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Run <code>$ d8 --trace-opt-verbose test.js</code></p><p>You should see that the <code>test</code> function was optimized by V8, along with an explanation of why. “ICs” stands for <a href="http://en.wikipedia.org/wiki/Inline_caching">inline caches</a> – and are one of the ways that V8 performs optimizations. Generally speaking, the more “ICs with typeinfo” the better.</p><p>Now modify <code>test.js</code> to include the following code:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">test</span>(<span class="params"> obj </span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> obj.<span class="property">prop</span> + obj.<span class="property">prop</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> a = &#123; <span class="attr">prop</span>: <span class="string">&#x27;a&#x27;</span> &#125;, b = &#123; <span class="attr">prop</span>: [] &#125;, i = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> ( i++ &lt; <span class="number">10000</span> ) &#123;</span><br><span class="line">  <span class="title function_">test</span>( <span class="title class_">Math</span>.<span class="title function_">random</span>() &gt; <span class="number">0.5</span> ? a : b );</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>Run <code>$ d8 --trace-opt-verbose test.js</code></p><p>So, you’ll see that this time, the <code>test</code> function was never actually optimized. And the reason for that is because it’s being passed objects with different <a href="http://blog.twokul.io/hidden-classes-in-javascript-and-inline-caching/">hidden classes</a>. Try changing the value of <code>prop</code> in <code>a</code> to an integer and run it again. You should see that the function was able to be optimized.</p><h3 id="Print-deoptimization-stats"><a href="#Print-deoptimization-stats" class="headerlink" title="Print deoptimization stats"></a>Print deoptimization stats</h3><p>Modify the contents of <code>test.js</code>:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">test</span>(<span class="params"> obj </span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> obj.<span class="property">prop</span> + obj.<span class="property">prop</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> a = &#123; <span class="attr">prop</span>: <span class="string">&#x27;a&#x27;</span> &#125;, b = &#123; <span class="attr">prop</span>: [] &#125;, i = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> ( i++ &lt; <span class="number">10000</span> ) &#123;</span><br><span class="line">  <span class="title function_">test</span>( i !== <span class="number">8000</span> ? a : b );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Run <code>$ d8 --trace-opt --trace-deopt test.js</code></p><p>You should see that the optimized code for the <code>test</code> function was thrown out. What happened here was that V8 kept seeing <code>test</code> being passed an object that looked like <code>{prop: &lt;String&gt;}</code>. But on the 8000th round of the while loop, we gave it something different. So V8 had to throw away the optimized code, because its initial assumptions were wrong.</p><h3 id="Profiling"><a href="#Profiling" class="headerlink" title="Profiling"></a>Profiling</h3><p>Modify <code>test.js</code>:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">factorial</span>(<span class="params"> n </span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> n === <span class="number">1</span> ? n : n * <span class="title function_">factorial</span>( --n );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> i = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> ( i++ &lt; <span class="number">1e7</span> ) &#123;</span><br><span class="line">  <span class="title function_">factorial</span>( <span class="number">10</span> );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Run <code>$ time d8 --prof test.js</code> (Generates <code>v8.log</code>)</p><p>Run <code>$ tick-processor</code> (Reads <code>v8.log</code> and <code>cat</code>s the parsed output)</p><p>This’ll show you where the program was spending most of its time, by function. Most of it should be under <code>LazyCompile: *factorial test.js:1:19</code>. The asterisk before the function name means that it was optimized.</p><p>Make a note of the execution time that was logged to the terminal. Now try modifying the code to this dumb, contrived example:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">factorial</span>(<span class="params"> n </span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">equal</span>( n, <span class="number">1</span> ) ? n : <span class="title function_">multiply</span>( n, <span class="title function_">factorial</span>( --n ) );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">multiply</span>(<span class="params"> x, y </span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> x * y;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">equal</span>(<span class="params"> a, b </span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> a === b;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> i = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> ( i++ &lt; <span class="number">1e7</span> ) &#123;</span><br><span class="line">  <span class="title function_">factorial</span>( <span class="number">10</span> );</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>Run <code>$ time d8 --prof test.js</code></p><p>Run <code>$ tick-processor</code></p><p>Roughly the same execution time as the last function, which seems like it should have been faster. You’ll also notice that the <code>multiply</code> and <code>equal</code> functions are nowhere on the list. Weird, right?</p><p>Run <code>$ d8 --trace-inlining test.js</code></p><p>Okay. So, we can see that the optimizing compiler was smart here and completely eliminated the overhead of calling both of those functions by inlining them into the optimized code for <code>factorial</code>.</p><p>The optimized code for both versions ends up being basically identical (which you can check, if you know how to read assembly, by running <code>d8 --print-opt-code test.js</code>).</p><h3 id="Tracing-Garbage-Collection"><a href="#Tracing-Garbage-Collection" class="headerlink" title="Tracing Garbage Collection"></a>Tracing Garbage Collection</h3><p>Modify <code>test.js</code></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">strToArray</span>(<span class="params"> str </span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> i = <span class="number">0</span>,</span><br><span class="line">    len = str.<span class="property">length</span>,</span><br><span class="line">    arr = <span class="keyword">new</span> <span class="title class_">Uint16Array</span>( str.<span class="property">length</span> );</span><br><span class="line">  <span class="keyword">for</span> ( ; i &lt; len; ++i ) &#123;</span><br><span class="line">    arr[ i ] = str.<span class="title function_">charCodeAt</span>( i );</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> arr;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> i = <span class="number">0</span>, str = <span class="string">&#x27;V8 is the collest&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> ( i++ &lt; <span class="number">1e5</span> ) &#123;</span><br><span class="line">  <span class="title function_">strToArray</span>( str );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Run <code>$ d8 --trace-gc test.js</code></p><p>You’ll see a bunch of <code>Scavenge... [allocation failure]</code>.</p><p>Basically, V8’s GC heap has different “spaces”. Most objects are allocated in the “new space”. It’s super cheap to allocate here, but it’s also pretty small (usually somewhere between 1 and 8 MB). Once that space gets filled up, the GC does a “scavenge”.</p><p>Scavenging is the fast part of V8 garbage collection. Usually somewhere between 1 and 5ms from what I’ve seen – so it might not necessarily cause a noticeable GC pause.</p><p><strong>Scavenges can only be kicked off by allocations</strong>. If the “new space” never gets filled up, the GC never needs to reclaim space by scavenging.</p><p>Modify <code>test.js</code>:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">strToArray</span>(<span class="params"> str, bufferView </span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> i = <span class="number">0</span>,</span><br><span class="line">    len = str.<span class="property">length</span>;</span><br><span class="line">  <span class="keyword">for</span> ( ; i &lt; len; ++i ) &#123;</span><br><span class="line">    bufferView[ i ] = str.<span class="title function_">charCodeAt</span>( i );</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> bufferView;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> i = <span class="number">0</span>,</span><br><span class="line">  str = <span class="string">&#x27;V8 is the coolest&#x27;</span>,</span><br><span class="line">  buffer = <span class="keyword">new</span> <span class="title class_">ArrayBuffer</span>( str.<span class="property">length</span> * <span class="number">2</span> ),</span><br><span class="line">  bufferView = <span class="keyword">new</span> <span class="title class_">Uint16Array</span>( buffer );</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> ( i++ &lt; <span class="number">1e5</span> ) &#123;</span><br><span class="line">  <span class="title function_">strToArray</span>( str, bufferView );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Here, we use a preallocated <code>ArrayBuffer</code> and an associated <code>ArrayBufferView</code> (in this case a <code>Uint16Array</code>) in order to avoid reallocating a new object every time we run <code>strToArray()</code>. The result is that we’re hardly allocating anything.</p><p>Run <code>$ d8 --trace-gc test.js</code></p><p>Nothing. We never filled up the “new space”, so we never had to scavenge.</p><p>One more thing to try in <code>test.js</code>:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">strToArray</span>(<span class="params"> str </span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> i = <span class="number">0</span>,</span><br><span class="line">    len = str.<span class="property">length</span>,</span><br><span class="line">    arr = <span class="keyword">new</span> <span class="title class_">Uint16Array</span>( str.<span class="property">length</span> );</span><br><span class="line">  <span class="keyword">for</span> ( ; i &lt; len; ++i ) &#123;</span><br><span class="line">    arr[ i ] = str.<span class="title function_">charCodeAt</span>( i );</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> arr;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> i = <span class="number">0</span>, str = <span class="string">&#x27;V8 is the coolest&#x27;</span>, arr = [];</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> ( i++ &lt; <span class="number">1e6</span> ) &#123;</span><br><span class="line">  <span class="title function_">strToArray</span>( str );</span><br><span class="line">  <span class="keyword">if</span> ( i % <span class="number">100000</span> === <span class="number">0</span> ) &#123;</span><br><span class="line">    <span class="comment">// save a long-term reference to a random, huge object</span></span><br><span class="line">    arr.<span class="title function_">push</span>( <span class="keyword">new</span> <span class="title class_">Uint16Array</span>( <span class="number">100000000</span> ) );</span><br><span class="line">    <span class="comment">// release references about 5% of the time</span></span><br><span class="line">    <span class="title class_">Math</span>.<span class="title function_">random</span>() &gt; <span class="number">0.95</span> &amp;&amp; ( arr.<span class="property">length</span> = <span class="number">0</span> );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Run <code>$ d8 --trace-gc test.js</code></p><p>Lots of scavenges, which is expected since we’re no longer using a preallocated buffer. But there should also be a bunch of <code>Mark-sweep</code> lines.</p><p>Mark-sweep is the “full” GC. It gets run when the “old space” heap reaches a certain size, and it tends to take a lot longer than a scavenge. If you look at the logs, you’ll probably see <code>Scavenge</code> at around ~1.5ms and <code>Mark-sweep</code> closer to 25 or 30ms.</p><p>Since the frame budget in a web app is about 16ms, you’re pretty much guaranteed to drop at least 1 frame every time Mark-sweep runs.</p><h2 id="Random-stuff"><a href="#Random-stuff" class="headerlink" title="Random stuff"></a>Random stuff</h2><h3 id="d8-help-logs-all-available-d8-flags"><a href="#d8-help-logs-all-available-d8-flags" class="headerlink" title="$ d8 --help logs all available d8 flags"></a><code>$ d8 --help</code> logs all available d8 flags</h3><p>There’s a ton there, but you can usually find what you’re looking for with something like <code>d8 --help | grep memory</code> or whatever.</p><h3 id="d8-allow-natives-syntax-file-js"><a href="#d8-allow-natives-syntax-file-js" class="headerlink" title="$ d8 --allow-natives-syntax file.js"></a><code>$ d8 --allow-natives-syntax file.js</code></h3><p>This actually lets you call V8 internal methods from within your JS file, like this:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">factorial</span>(<span class="params"> n </span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> n === <span class="number">1</span> ? n : <span class="title function_">factorial</span>( --n );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> i = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> ( i++ &lt; <span class="number">1e8</span> ) &#123;</span><br><span class="line">  <span class="title function_">factorial</span>( <span class="number">10</span> );</span><br><span class="line">  <span class="comment">// run a full Mark-sweep pass every 10MM iterations</span></span><br><span class="line">  i % <span class="number">1e7</span> === <span class="number">0</span> &amp;&amp; %<span class="title class_">CollectGarbage</span>( <span class="literal">null</span> );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>…and run <code>$ d8 --allow-natives-syntax --trace-gc test.js</code></p><p>Native functions are prefixed with the <code>%</code> symbol. A (somewhat incomplete) list of native functions are listed <a href="https://github.com/Nathanaela/v8-Natives/blob/master/lib/v8-native-calls.js">here</a>.</p><h3 id="Logging"><a href="#Logging" class="headerlink" title="Logging"></a>Logging</h3><p>d8 doesn’t have a <code>console</code> object (or a <code>window</code> object, for that matter). But you can log to the terminal using <code>print()</code>.</p><h3 id="Comparing-Hidden-Classes"><a href="#Comparing-Hidden-Classes" class="headerlink" title="Comparing Hidden Classes"></a>Comparing Hidden Classes</h3><p>This is probably my favorite one. I actually <em>just</em> found it.</p><p>So in V8, there’s this concept of <a href="http://www.html5rocks.com/en/tutorials/speed/v8/">“hidden classes”</a> (Good explanation a couple paragraphs in). You should read that article – but basically, hidden classes are how V8 (SpiderMonkey and JavaScript Core use similar techniques, too) determine whether or not two objects have the same “shape”.</p><p>All things considered, you always want to pass objects of the same hidden class as arguments to functions.</p><p>Anyway, you can actually <em>compare</em> the hidden classes of two objects:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">function Class( val ) &#123;</span><br><span class="line">  this.prop = val;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">var a = new Class(&#x27;foo&#x27;);</span><br><span class="line">var b = new Class(&#x27;bar&#x27;);</span><br><span class="line"></span><br><span class="line">print( %HaveSameMap( a, b ) );</span><br><span class="line"></span><br><span class="line">b.prop2 = &#x27;baz&#x27;;</span><br><span class="line"></span><br><span class="line">print( %HaveSameMap( a, b ) );</span><br></pre></td></tr></table></figure><p>Run <code>$ d8 --allow-natives-syntax test.js</code></p><p>You should see <code>true</code>, then <code>false</code>. By adding <code>b.prop2 = &#39;baz&#39;</code>, we modified its structure and created a new hidden class.</p><h3 id="Node-js"><a href="#Node-js" class="headerlink" title="Node.js"></a>Node.js</h3><p>A lot of these flags (but not all of them) work with Node, too. <code>--trace-opt</code>, <code>--prof</code>, <code>--allow-natives-syntax</code> are all supported.</p><p>That can be helpful if you want to test something that relies on another library, since you can use Node’s <code>require()</code>.</p><p>A list of supported V8 flags can be accessed with <code>node --v8-options</code>.</p><h3 id="Links"><a href="#Links" class="headerlink" title="Links"></a>Links</h3><p><a href="http://www.html5rocks.com/en/tutorials/speed/v8/">Performance Tips for JavaScript in V8</a> (Good basic intro to Hidden Classes)</p><p><a href="http://www.html5rocks.com/en/tutorials/performance/mystery/">Use forensics and detective work to solve JavaScript performance mysteries</a></p><p><a href="https://www.youtube.com/watch?v=UJPdhx5zTaw">Breaking the JavaScript Speed Limit with V8</a></p><p><a href="http://wingolog.org/archives/2011/07/05/v8-a-tale-of-two-compilers">V8 - A Tale of Two Compilers</a> (Good explanation of Inline Caches)</p><hr><p>Anyway, this is all still pretty new to me, and there’s a lot I haven’t figured out yet. But the stuff I’ve found so far is pretty cool, so I wanted to write something up and share it.</p><p>Oh, and I’m sure there’s stuff in here that I’m wrong about, because I’m honestly a little out of my depth here. Feedback is appreciated.</p><blockquote><p>使用 Demo 请参考 <a href="https://gist.github.com/kevincennis/0cd2138c78a07412ef21#d8-shell-examples">d8-shell-examples</a></p></blockquote><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://github.com/nodejs/node-gyp/issues/569#issuecomment-94917337">xcode-select: error: tool ‘xcodebuild’ requires Xcode, but active developer directory ‘&#x2F;Library&#x2F;Developer&#x2F;CommandLineTools’ is a command line tools instance</a></li><li><a href="https://gist.github.com/kevincennis/0cd2138c78a07412ef21">Installing V8 on a Mac</a></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/ddec6c95591a/</id>
    <link href="https://blog.zisheng.pro/posts/ddec6c95591a/"/>
    <published>2025-03-06T22:54:41.000Z</published>
    <summary>介绍如何在 Mac 上从源码编译安装 V8 引擎（d8 shell），并通过 --trace-opt、--prof、--trace-gc 等命令深入了解 V8 的优化、性能分析与垃圾回收机制。</summary>
    <title>在 Mac 上安装 V8</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="工程化" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/%E5%B7%A5%E7%A8%8B%E5%8C%96/"/>
    <category term="TypeScript" scheme="https://blog.zisheng.pro/tags/TypeScript/"/>
    <category term="Jest" scheme="https://blog.zisheng.pro/tags/Jest/"/>
    <category term="Coverage" scheme="https://blog.zisheng.pro/tags/Coverage/"/>
    <category term="项目初始化" scheme="https://blog.zisheng.pro/tags/%E9%A1%B9%E7%9B%AE%E5%88%9D%E5%A7%8B%E5%8C%96/"/>
    <content>
      <![CDATA[<h2 id="TypeScript"><a href="#TypeScript" class="headerlink" title="TypeScript"></a>TypeScript</h2><blockquote><p>参考文档：<a href="https://www.typescriptlang.org/docs/handbook/typescript-tooling-in-5-minutes.html">https://www.typescriptlang.org/docs/handbook/typescript-tooling-in-5-minutes.html</a></p></blockquote><p>初始化：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 初始化 npm 项目</span></span><br><span class="line">$ npm init -y</span><br><span class="line"><span class="comment"># 全局安装 TypeScript</span></span><br><span class="line">$ npm install -g typescript</span><br><span class="line"><span class="comment"># 项目安装 TypeScript</span></span><br><span class="line">$ pnpm add typescript -D</span><br><span class="line"><span class="comment"># 初始化 TypeScript 配置文件</span></span><br><span class="line">$ tsc --init</span><br></pre></td></tr></table></figure><p>配置 tsconfig.json：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;compilerOptions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;target&quot;</span><span class="punctuation">:</span> <span class="string">&quot;es2016&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;module&quot;</span><span class="punctuation">:</span> <span class="string">&quot;commonjs&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;outDir&quot;</span><span class="punctuation">:</span> <span class="string">&quot;./dist&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;esModuleInterop&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;forceConsistentCasingInFileNames&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;strict&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;skipLibCheck&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><ul><li>target：用于指定 TypeScript 代码编译后生成的 JavaScript 代码的目标版本。</li><li>module：用于指定 TypeScript 模块的目标模式，即编译后的 JavaScript 代码所使用的模块系统。</li><li>outDir：用于指定编译后的 JavaScript 文件输出目录的配置选项。</li><li>esModuleInterop：主要用于处理 ECMAScript 模块（ES 模块）与 CommonJS 模块之间的交互问题<blockquote><p>当esModuleInterop设置为true时，TypeScript 编译器会在编译过程中生成一些额外的代码来处理 ES 模块和 CommonJS 模块之间的差异。具体来说，它会确保当从 ES 模块中导入 CommonJS 模块时，能够正确地获取到 CommonJS 模块的导出内容。</p></blockquote></li><li>forceConsistentCasingInFileNames：用于指定是否强制使用一致的大小写来命名文件。<blockquote><p>当forceConsistentCasingInFileNames设置为true时，TypeScript 编译器会在编译过程中严格检查所有导入和引用的文件名的大小写是否与实际文件系统中的文件名大小写一致。如果发现不一致，编译器将抛出错误，提示开发人员修正文件名的大小写问题。</p></blockquote></li><li>strict：用于指定是否启用严格模式。<blockquote><p>当strict设置为true时，TypeScript 编译器会启用严格模式，对代码进行更严格的类型检查和错误检测。这意味着编译器会对代码进行更严格的类型检查，以帮助开发人员发现潜在的类型错误和问题。</p></blockquote></li><li>skipLibCheck：主要用于控制 TypeScript 编译器对声明文件（.d.ts）的类型检查行为，以下是具体介绍：<blockquote><p>当编译器遇到skipLibCheck设置为true时，它会在编译过程中跳过对node_modules目录下以及types目录（如果有）中声明文件的类型检查。编译器仍然会处理这些声明文件，以便在代码中正确地使用它们提供的类型信息，但不会对其内部的类型定义进行严格的检查和验证。</p></blockquote></li></ul><p>配置文件优化：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;compilerOptions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">     <span class="comment">/* 声明文件与源码映射 */</span></span><br><span class="line">    <span class="attr">&quot;declaration&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span>      <span class="comment">// 生成 .d.ts 类型声明</span></span><br><span class="line">    <span class="attr">&quot;declarationMap&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span>   <span class="comment">// 类型声明与源码映射</span></span><br><span class="line">    <span class="attr">&quot;sourceMap&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span>        <span class="comment">// 生成调试用 sourcemap</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 路径与模块配置 */</span></span><br><span class="line">    <span class="attr">&quot;baseUrl&quot;</span><span class="punctuation">:</span> <span class="string">&quot;.&quot;</span><span class="punctuation">,</span>           <span class="comment">// 模块解析基准路径[6,9](@ref)</span></span><br><span class="line">    <span class="attr">&quot;paths&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span>                <span class="comment">// 路径别名映射[6,9](@ref)</span></span><br><span class="line">      <span class="attr">&quot;@/*&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;src/*&quot;</span><span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;include&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;src/**/*&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;exclude&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;dist&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;**/*.test.ts&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>添加 <code>scripts</code>：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;prebuild&quot;</span><span class="punctuation">:</span> <span class="string">&quot;rimraf dist&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;build&quot;</span><span class="punctuation">:</span> <span class="string">&quot;tsc&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;dev&quot;</span><span class="punctuation">:</span> <span class="string">&quot;tsc --watch&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;devDependencies&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;rimraf&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^3.0.2&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><ul><li>rimraf：用于删除指定目录下的所有文件和子目录。</li></ul><p>创建第一个 TypeScript 文件：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/sum.ts</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">sum</span>(<span class="params">a: <span class="built_in">number</span>, b: <span class="built_in">number</span></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> a + b;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> sum;</span><br></pre></td></tr></table></figure><p>执行 <code>pnpm run build</code> 命令，生成 <code>dist</code> 文件夹：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1740441881437.png" alt="图 0"></p><h2 id="Jest"><a href="#Jest" class="headerlink" title="Jest"></a>Jest</h2><blockquote><p>参考文档：<a href="https://jestjs.io/zh-Hans/docs/getting-started">https://jestjs.io/zh-Hans/docs/getting-started</a></p></blockquote><h3 id="安装-jest"><a href="#安装-jest" class="headerlink" title="安装 jest"></a>安装 jest</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 全局安装 Jest</span></span><br><span class="line">$ npm install -g jest</span><br><span class="line"><span class="comment"># 项目安装 Jest</span></span><br><span class="line">$ pnpm add jest -D</span><br></pre></td></tr></table></figure><h3 id="安装-ts-jest"><a href="#安装-ts-jest" class="headerlink" title="安装 ts-jest"></a>安装 ts-jest</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pnpm add ts-jest -D</span><br></pre></td></tr></table></figure><p>安装 ts-jest 后，Jest 会自动识别 TypeScript 文件，并使用 <code>ts-jest</code> 进行编译和测试。</p><h3 id="使用-ts-jest"><a href="#使用-ts-jest" class="headerlink" title="使用 ts-jest"></a>使用 ts-jest</h3><p>为了让 Jest 使用 ts-jest 来转换 TypeScript，你还需要使用 <code>jest --init</code> 命令创建一个配置文件。之后在 jest.config.ts 文件中添加一行：<code>preset: &quot;ts-jest&quot;</code>。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pnpm add ts-jest -D</span><br></pre></td></tr></table></figure><h3 id="安装-ts-node"><a href="#安装-ts-node" class="headerlink" title="安装 ts-node"></a>安装 ts-node</h3><p>使用 Typescript 配置文件的还需给 Jest 安装 ts-node， 请确保在项目中安装该依赖。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pnpm add ts-node -D</span><br></pre></td></tr></table></figure><h3 id="安装-types-jest"><a href="#安装-types-jest" class="headerlink" title="安装 @types&#x2F;jest"></a>安装 @types&#x2F;jest</h3><p>安装 <code>@types/jest</code> 包。它为 Jest 的全局变量提供类型，无需导入它们。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pnpm add @types/jest -D</span><br></pre></td></tr></table></figure><h3 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h3><p>创建一个测试文件，一般我们会把测试文件放在 <code>__tests__</code> 目录下：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/__tests__/sum.test.ts</span></span><br><span class="line"><span class="keyword">import</span> sum <span class="keyword">from</span> <span class="string">&#x27;./sum&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title function_">test</span>(<span class="string">&#x27;adds 1 + 2 to equal 3&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">expect</span>(<span class="title function_">sum</span>(<span class="number">1</span>, <span class="number">2</span>)).<span class="title function_">toBe</span>(<span class="number">3</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>执行 <code>pnpm run test</code> 命令，测试通过：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1740443065042.png" alt="图 1"></p><p>会在项目根目录下生成 <code>coverage</code> 文件夹，里面包含了测试覆盖率的报告：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1740443117575.png" alt="图 2"></p><h2 id="Codecov"><a href="#Codecov" class="headerlink" title="Codecov"></a>Codecov</h2><h3 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h3><p>Codecov 是一款专注于代码覆盖率分析的在线服务平台，支持多种编程语言和测试框架，能够帮助开发者直观评估测试用例的有效性，并优化代码质量。</p><h3 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h3><p>访问 <code>codecov.io</code> 网站，点击 <code>Sign in with GitHub</code> 按钮，授权登录。选择具体仓库进行配置：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1740444392401.png" alt="图 4"></p><p>在仓库中添加 TOKEN：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1740443734805.png" alt="图 3"></p><p>在项目根目录添加 <code>.github/workflows/test.yaml</code> 文件：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">Run</span> <span class="string">tests</span> <span class="string">and</span> <span class="string">upload</span> <span class="string">coverage</span></span><br><span class="line"></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line">  <span class="string">push</span></span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">test:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">Run</span> <span class="string">tests</span> <span class="string">and</span> <span class="string">collect</span> <span class="string">coverage</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Checkout</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">actions/checkout@v4</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">fetch-depth:</span> <span class="number">0</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Set</span> <span class="string">up</span> <span class="string">Node</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">actions/setup-node@v4</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Install</span> <span class="string">dependencies</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">npm</span> <span class="string">install</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Run</span> <span class="string">tests</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">npx</span> <span class="string">jest</span> <span class="string">--coverage</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Upload</span> <span class="string">results</span> <span class="string">to</span> <span class="string">Codecov</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">codecov/codecov-action@v5</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">token:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.CODECOV_TOKEN</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">slug:</span> <span class="string">youngjuning/fucking-js</span></span><br></pre></td></tr></table></figure><p>执行完后访问 <a href="https://app.codecov.io/gh/youngjuning/jsace">https://app.codecov.io/gh/youngjuning/jsace</a> 网站，查看测试覆盖率：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1740444533825.png" alt="图 5"></p><h2 id="eslint-flat-config"><a href="#eslint-flat-config" class="headerlink" title="eslint flat config"></a>eslint flat config</h2><h3 id="安装-youngjuning-eslint-config"><a href="#安装-youngjuning-eslint-config" class="headerlink" title="安装 @youngjuning&#x2F;eslint-config"></a>安装 @youngjuning&#x2F;eslint-config</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pnpm add @youngjuning/eslint-config eslint prettier typescript jest -D</span><br></pre></td></tr></table></figure><h3 id="添加-eslint-config-ts"><a href="#添加-eslint-config-ts" class="headerlink" title="添加 eslint.config.ts"></a>添加 eslint.config.ts</h3><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> tseslint <span class="keyword">from</span> <span class="string">&#x27;typescript-eslint&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> youngjuning <span class="keyword">from</span> <span class="string">&#x27;@youngjuning/eslint-config&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> tseslint.<span class="title function_">config</span>(youngjuning, &#123;</span><br><span class="line">  <span class="attr">ignores</span>: [<span class="string">&#x27;**/dist/**&#x27;</span>],</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="pnpm-Installation"><a href="#pnpm-Installation" class="headerlink" title="pnpm Installation"></a>pnpm Installation</h3><p>要在 pnpm 中使用 ESLint，我们建议设置一个 <code>.npmrc</code> 文件，并至少包含以下设置：</p><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">auto-install-peers=true</span><br><span class="line">node-linker=hoisted</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/724224a79d85/</id>
    <link href="https://blog.zisheng.pro/posts/724224a79d85/"/>
    <published>2025-02-24T13:45:40.000Z</published>
    <summary>从零到一初始化一个 TypeScript 项目，使用最新的技术</summary>
    <title>TypeScript+Jest+Coverage+Eslint+Prettier 项目初始化</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="方法论" scheme="https://blog.zisheng.pro/categories/%E6%96%B9%E6%B3%95%E8%AE%BA/"/>
    <category term="个人目标" scheme="https://blog.zisheng.pro/tags/%E4%B8%AA%E4%BA%BA%E7%9B%AE%E6%A0%87/"/>
    <category term="OKR" scheme="https://blog.zisheng.pro/tags/OKR/"/>
    <category term="SMART" scheme="https://blog.zisheng.pro/tags/SMART/"/>
    <content>
      <![CDATA[<p>个人学习目标适合使用 <strong>OKR</strong> 和 <strong>SMART</strong> 模型都可以，但它们的适用场景和侧重点有所不同。选择哪种模型取决于你的学习目标类型、时间跨度以及你希望如何跟踪进展。</p><hr><h2 id="1-OKR（目标与关键结果）"><a href="#1-OKR（目标与关键结果）" class="headerlink" title="1. OKR（目标与关键结果）"></a><strong>1. OKR（目标与关键结果）</strong></h2><h3 id="特点："><a href="#特点：" class="headerlink" title="特点："></a><strong>特点：</strong></h3><ul><li><strong>目标（Objective）</strong>：描述一个有激励性的方向或愿景。</li><li><strong>关键结果（Key Results）</strong>：具体的、可量化的指标，用于衡量目标是否达成。</li><li><strong>适用场景</strong>：适合中长期目标，尤其是需要明确方向并量化成果的学习计划。</li></ul><h3 id="优点："><a href="#优点：" class="headerlink" title="优点："></a><strong>优点：</strong></h3><ul><li>强调愿景驱动，能够激发动力。</li><li>通过关键结果提供清晰的衡量标准。</li><li>灵活调整，适合动态变化的目标。</li></ul><h3 id="缺点："><a href="#缺点：" class="headerlink" title="缺点："></a><strong>缺点：</strong></h3><ul><li>对初学者来说可能过于抽象，难以制定具体的关键结果。</li><li>需要定期回顾和调整，否则容易偏离目标。</li></ul><h3 id="示例：个人学习目标"><a href="#示例：个人学习目标" class="headerlink" title="示例：个人学习目标"></a><strong>示例：个人学习目标</strong></h3><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="section"># 学习目标（季度）</span></span><br><span class="line"></span><br><span class="line"><span class="section">## 目标 1: 掌握 Python 编程基础</span></span><br><span class="line"><span class="bullet">-</span> <span class="strong">**关键结果 1**</span>: 完成一本 Python 入门书籍的学习（如《Python编程：从入门到实践》）。</span><br><span class="line"><span class="bullet">-</span> <span class="strong">**关键结果 2**</span>: 在 LeetCode 上完成 30 道简单难度的算法题。</span><br><span class="line"><span class="bullet">-</span> <span class="strong">**关键结果 3**</span>: 开发一个小型项目（如记账工具）并部署到 GitHub。</span><br><span class="line"></span><br><span class="line"><span class="section">## 目标 2: 提升英语口语能力</span></span><br><span class="line"><span class="bullet">-</span> <span class="strong">**关键结果 1**</span>: 每周进行 3 次英语对话练习（每次至少 30 分钟）。</span><br><span class="line"><span class="bullet">-</span> <span class="strong">**关键结果 2**</span>: 参加一次在线英语演讲活动并获得反馈。</span><br><span class="line"><span class="bullet">-</span> <span class="strong">**关键结果 3**</span>: 英语听力测试分数提升至 85%。</span><br></pre></td></tr></table></figure><h3 id="适用人群："><a href="#适用人群：" class="headerlink" title="适用人群："></a><strong>适用人群：</strong></h3><ul><li>希望设定中长期学习目标的人。</li><li>需要量化成果并定期检查进展的人。</li><li>喜欢挑战性目标并愿意不断调整的人。</li></ul><hr><h2 id="2-SMART-模型"><a href="#2-SMART-模型" class="headerlink" title="2. SMART 模型"></a><strong>2. SMART 模型</strong></h2><h3 id="特点：-1"><a href="#特点：-1" class="headerlink" title="特点："></a><strong>特点：</strong></h3><ul><li><strong>Specific（具体）</strong>：目标明确，避免模糊。</li><li><strong>Measurable（可衡量）</strong>：可以量化或评估目标完成情况。</li><li><strong>Achievable（可实现）</strong>：目标现实且符合自身能力。</li><li><strong>Relevant（相关性）</strong>：目标与个人需求或长期规划相关。</li><li><strong>Time-bound（有时限）</strong>：设置明确的时间节点。</li></ul><h3 id="优点：-1"><a href="#优点：-1" class="headerlink" title="优点："></a><strong>优点：</strong></h3><ul><li>结构化强，易于制定和执行。</li><li>适合短期目标或需要快速见效的任务。</li><li>强调可行性，减少挫败感。</li></ul><h3 id="缺点：-1"><a href="#缺点：-1" class="headerlink" title="缺点："></a><strong>缺点：</strong></h3><ul><li>过于关注细节可能导致视野狭窄。</li><li>对于复杂或长期目标，可能缺乏灵活性。</li></ul><h3 id="示例：个人学习目标-1"><a href="#示例：个人学习目标-1" class="headerlink" title="示例：个人学习目标"></a><strong>示例：个人学习目标</strong></h3><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="section"># 学习目标（月度）</span></span><br><span class="line"></span><br><span class="line"><span class="section">## 目标 1: 完成 Python 数据分析基础学习</span></span><br><span class="line"><span class="bullet">-</span> <span class="strong">**Specific**</span>: 学习 Pandas 和 NumPy 的基本操作。</span><br><span class="line"><span class="bullet">-</span> <span class="strong">**Measurable**</span>: 完成 Udemy 上的《Python数据分析入门课程》，并通过所有课后练习。</span><br><span class="line"><span class="bullet">-</span> <span class="strong">**Achievable**</span>: 每天投入 1 小时学习，周末复习。</span><br><span class="line"><span class="bullet">-</span> <span class="strong">**Relevant**</span>: 数据分析技能有助于未来职业发展。</span><br><span class="line"><span class="bullet">-</span> <span class="strong">**Time-bound**</span>: 在 1 个月内完成课程。</span><br><span class="line"></span><br><span class="line"><span class="section">## 目标 2: 提高英语词汇量</span></span><br><span class="line"><span class="bullet">-</span> <span class="strong">**Specific**</span>: 每天记忆 10 个新单词。</span><br><span class="line"><span class="bullet">-</span> <span class="strong">**Measurable**</span>: 使用 Anki 软件记录单词，并确保每周掌握 70 个单词。</span><br><span class="line"><span class="bullet">-</span> <span class="strong">**Achievable**</span>: 每天花 15 分钟背单词，结合例句记忆。</span><br><span class="line"><span class="bullet">-</span> <span class="strong">**Relevant**</span>: 扩大词汇量有助于阅读和写作能力提升。</span><br><span class="line"><span class="bullet">-</span> <span class="strong">**Time-bound**</span>: 持续 3 个月，总计掌握 900 个单词。</span><br></pre></td></tr></table></figure><h3 id="适用人群：-1"><a href="#适用人群：-1" class="headerlink" title="适用人群："></a><strong>适用人群：</strong></h3><ul><li>希望设定短期、具体目标的人。</li><li>需要明确步骤和时间节点的人。</li><li>初学者或对目标管理不熟悉的人。</li></ul><hr><h2 id="对比总结"><a href="#对比总结" class="headerlink" title="对比总结"></a><strong>对比总结</strong></h2><table><thead><tr><th>特性</th><th>OKR</th><th>SMART</th></tr></thead><tbody><tr><td><strong>时间跨度</strong></td><td>中长期（季度、半年、年度）</td><td>短期（周、月）</td></tr><tr><td><strong>目标性质</strong></td><td>愿景驱动，强调方向和成果</td><td>细节导向，强调可执行性和可行性</td></tr><tr><td><strong>灵活性</strong></td><td>动态调整，适应变化</td><td>固定性强，需按计划执行</td></tr><tr><td><strong>适用场景</strong></td><td>需要量化成果的复杂目标</td><td>需要快速见效的具体任务</td></tr><tr><td><strong>学习者类型</strong></td><td>喜欢挑战性目标，善于自我激励</td><td>偏好结构化指导，注重执行力</td></tr></tbody></table><hr><h2 id="如何选择？"><a href="#如何选择？" class="headerlink" title="如何选择？"></a><strong>如何选择？</strong></h2><ol><li><p><strong>如果你是初学者</strong>：</p><ul><li>推荐使用 <strong>SMART 模型</strong>，因为它更具体、更容易上手。</li><li>示例：每天背 10 个单词，每周写一篇短文。</li></ul></li><li><p><strong>如果你已经有明确的学习方向</strong>：</p><ul><li>推荐使用 <strong>OKR 模型</strong>，因为它可以帮助你聚焦愿景并量化成果。</li><li>示例：目标是“成为数据科学家”，关键结果包括完成特定课程、完成项目等。</li></ul></li><li><p><strong>结合使用</strong>：</p><ul><li>可以用 OKR 设定中长期目标，用 SMART 模型细化每个关键结果的具体执行计划。</li><li>示例：<figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="section"># 季度目标（OKR）</span></span><br><span class="line"><span class="section">## 目标 1: 成为初级数据分析师</span></span><br><span class="line"><span class="bullet">-</span> <span class="strong">**关键结果 1**</span>: 完成 Python 数据分析课程（SMART 计划：每天学习 1 小时，1 个月内完成）。</span><br><span class="line"><span class="bullet">-</span> <span class="strong">**关键结果 2**</span>: 完成一个数据分析项目（SMART 计划：每周投入 5 小时，2 个月内完成）。</span><br></pre></td></tr></table></figure></li></ul></li></ol><hr><h2 id="最终建议"><a href="#最终建议" class="headerlink" title="最终建议"></a><strong>最终建议</strong></h2><ul><li>如果你的学习目标较为长期且需要激励自己，选择 <strong>OKR</strong>。</li><li>如果你的学习目标较为短期且需要明确的执行步骤，选择 <strong>SMART</strong>。</li><li>如果不确定，可以从 <strong>SMART 模型</strong>开始，逐步过渡到 <strong>OKR</strong>，或者将两者结合使用。</li></ul><p>如果你能分享你的具体学习目标，我可以为你设计一个更贴合的方案！</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/be3ad9c965b3/</id>
    <link href="https://blog.zisheng.pro/posts/be3ad9c965b3/"/>
    <published>2025-02-20T08:47:14.000Z</published>
    <summary>个人学习目标适合使用 **OKR** 和 **SMART** 模型都可以，但它们的适用场景和侧重点有所不同。选择哪种模型取决于你的学习目标类型、时间跨度以及你希望如何跟踪进展。</summary>
    <title>个人目标管理模型 OKR 和 SMART 的区别</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="数据结构" scheme="https://blog.zisheng.pro/categories/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
    <category term="JavaScript" scheme="https://blog.zisheng.pro/tags/JavaScript/"/>
    <category term="算法" scheme="https://blog.zisheng.pro/tags/%E7%AE%97%E6%B3%95/"/>
    <content>
      <![CDATA[<h1 id="数组"><a href="#数组" class="headerlink" title="数组"></a>数组</h1><ul><li>跟其他编程语言一样，JS 数组也是一组有序的数据，但跟其他语言不同的是，数组中的每个槽位可以存储任意类型的数组。</li><li>JS 数组是动态大小的，会随着数据添加而自动增长。</li></ul><h2 id="创建数组"><a href="#创建数组" class="headerlink" title="创建数组"></a>创建数组</h2><h3 id="使用-Array-构造函数"><a href="#使用-Array-构造函数" class="headerlink" title="使用 Array 构造函数"></a>使用 Array 构造函数</h3><p>创建一个空数组：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建一个空数组</span></span><br><span class="line"><span class="keyword">const</span> arr = <span class="keyword">new</span> <span class="title class_">Array</span>();</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arr); <span class="comment">// []</span></span><br></pre></td></tr></table></figure><p>创建一个指定长度的数组，元素为 <code>undefined</code>：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建一个长度为 5 的数组，元素为 undefined</span></span><br><span class="line"><span class="keyword">const</span> arr = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">5</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arr); <span class="comment">// [undefined, undefined, undefined, undefined, undefined]</span></span><br></pre></td></tr></table></figure><p>创建并初始化数组：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建一个包含 [1, 2, 3] 的数组</span></span><br><span class="line"><span class="keyword">const</span> arr = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arr); <span class="comment">// [1, 2, 3]</span></span><br></pre></td></tr></table></figure><h3 id="使用数组字面量"><a href="#使用数组字面量" class="headerlink" title="使用数组字面量"></a>使用数组字面量</h3><p>创建一个空数组：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = []; <span class="comment">// 创建一个空数组</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arr); <span class="comment">// []</span></span><br></pre></td></tr></table></figure><p>直接定义数组元素：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建一个包含 [1, 2, 3] 的数组</span></span><br><span class="line"><span class="keyword">const</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arr); <span class="comment">// [1, 2, 3]</span></span><br></pre></td></tr></table></figure><h3 id="使用-Array-from-方法"><a href="#使用-Array-from-方法" class="headerlink" title="使用 Array.from 方法"></a>使用 <code>Array.from</code> 方法</h3><p>从类数组对象或可迭代对象创建数组：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建一个包含 [1, 2, 3] 的数组</span></span><br><span class="line"><span class="keyword">let</span> arr = <span class="title class_">Array</span>.<span class="title function_">from</span>(&#123; <span class="attr">length</span>: <span class="number">3</span> &#125;, <span class="function">(<span class="params">_, i</span>) =&gt;</span> i + <span class="number">1</span>);</span><br></pre></td></tr></table></figure><p>从字符串转换为数组：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建一个包含 [&#x27;h&#x27;, &#x27;e&#x27;, &#x27;l&#x27;, &#x27;l&#x27;, &#x27;o&#x27;] 的数组</span></span><br><span class="line"><span class="keyword">let</span> arr = <span class="title class_">Array</span>.<span class="title function_">from</span>(<span class="string">&quot;hello&quot;</span>);</span><br></pre></td></tr></table></figure><h3 id="使用-Array-of-方法"><a href="#使用-Array-of-方法" class="headerlink" title="使用 Array.of 方法"></a>使用 <code>Array.of</code> 方法</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建一个包含 [1, 2, 3] 的数组</span></span><br><span class="line"><span class="keyword">let</span> arr = <span class="title class_">Array</span>.<span class="title function_">of</span>(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>);</span><br></pre></td></tr></table></figure><h3 id="使用-Array-fill-方法"><a href="#使用-Array-fill-方法" class="headerlink" title="使用 Array.fill 方法"></a>使用 <code>Array.fill</code> 方法</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建一个长度为 5 的数组，元素全为 0。时间复杂度为 O(N)</span></span><br><span class="line"><span class="keyword">let</span> arr = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">5</span>).<span class="title function_">fill</span>(<span class="number">0</span>);</span><br></pre></td></tr></table></figure><h2 id="插入元素"><a href="#插入元素" class="headerlink" title="插入元素"></a>插入元素</h2><h3 id="在末尾插入元素"><a href="#在末尾插入元素" class="headerlink" title="在末尾插入元素"></a>在末尾插入元素</h3><blockquote><p>时间复杂度为 O(1)</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">arr.<span class="title function_">push</span>(<span class="number">4</span>); <span class="comment">// 在末尾插入元素 4</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arr) <span class="comment">// [1, 2, 3, 4]</span></span><br></pre></td></tr></table></figure><h3 id="在开头插入元素"><a href="#在开头插入元素" class="headerlink" title="在开头插入元素"></a>在开头插入元素</h3><blockquote><p>时间复杂度为 O(N)</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">arr.<span class="title function_">unshift</span>(<span class="number">0</span>); <span class="comment">// 在开头插入元素 0</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arr); <span class="comment">// [0, 1, 2, 3]</span></span><br></pre></td></tr></table></figure><h3 id="在任意位置插入元素"><a href="#在任意位置插入元素" class="headerlink" title="在任意位置插入元素"></a>在任意位置插入元素</h3><blockquote><p>时间复杂度为 O(N)</p></blockquote><p><code>splice()</code> 方法可以在数组的任意位置插入元素。它接受三个参数：</p><ul><li>起始索引</li><li>要删除的元素的数量（如果不需要删除元素，设置为 <code>0</code>）</li><li>要插入的元素（可以是一个或多个）</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">arr.<span class="title function_">splice</span>(<span class="number">1</span>, <span class="number">0</span>, <span class="number">1.5</span>); <span class="comment">// 在索引 1 的位置插入元素 1.5。</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arr) <span class="comment">// [1, 1.5, 2, 3]</span></span><br></pre></td></tr></table></figure><h2 id="删除元素"><a href="#删除元素" class="headerlink" title="删除元素"></a>删除元素</h2><h3 id="从末尾删除元素"><a href="#从末尾删除元素" class="headerlink" title="从末尾删除元素"></a>从末尾删除元素</h3><blockquote><p>时间复杂度为 O(1)</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">arr.<span class="title function_">pop</span>(); <span class="comment">// 删除末尾元素 3。</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arr) <span class="comment">// [1, 2]</span></span><br></pre></td></tr></table></figure><h3 id="从开头删除元素"><a href="#从开头删除元素" class="headerlink" title="从开头删除元素"></a>从开头删除元素</h3><blockquote><p>时间复杂度为 O(N)</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">arr.<span class="title function_">shift</span>(); <span class="comment">// 删除开头元素 1。</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arr); <span class="comment">// [2, 3]</span></span><br></pre></td></tr></table></figure><h3 id="在任意位置删除元素"><a href="#在任意位置删除元素" class="headerlink" title="在任意位置删除元素"></a>在任意位置删除元素</h3><blockquote><p>时间复杂度为 O(N)</p></blockquote><p><code>splice()</code> 方法可以删除数组的任意位置元素。它接受两个参数：</p><ul><li>起始索引</li><li>要删除的元素的数量（如果不需要删除元素，设置为 <code>0</code>）</li><li>要插入的元素（可以是一个或多个）</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">arr.<span class="title function_">splice</span>(<span class="number">1</span>, <span class="number">1</span>); <span class="comment">// 删除索引 1 的元素 2。</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arr) <span class="comment">// [1, 3]</span></span><br></pre></td></tr></table></figure><h2 id="修改元素"><a href="#修改元素" class="headerlink" title="修改元素"></a>修改元素</h2><h3 id="根据索引修改元素"><a href="#根据索引修改元素" class="headerlink" title="根据索引修改元素"></a>根据索引修改元素</h3><blockquote><p>时间复杂度为 O(1)</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">arr[<span class="number">0</span>] = <span class="number">100</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arr); <span class="comment">// [100, 2, 3]</span></span><br></pre></td></tr></table></figure><h3 id="使用-splice-方法"><a href="#使用-splice-方法" class="headerlink" title="使用 splice 方法"></a>使用 <code>splice</code> 方法</h3><blockquote><p>时间复杂度为 O(N)</p></blockquote><p><code>splice()</code> 方法可以删除数组的任意位置元素。它接受两个参数：</p><ul><li>起始索引</li><li>要删除的元素的数量（如果不需要删除元素，设置为 <code>0</code>）</li><li>要插入的元素（可以是一个或多个）</li></ul><p>当你需要修改数组中连续的多个元素时，<code>splice()</code> 方法非常有用。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">arr.<span class="title function_">splice</span>(<span class="number">1</span>, <span class="number">1</span>, <span class="number">6</span>); <span class="comment">// 从索引 2 开始，删除 1 个元素，并插入新元素 6。</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arr); <span class="comment">// [1, 6, 3]</span></span><br></pre></td></tr></table></figure><h2 id="查询元素"><a href="#查询元素" class="headerlink" title="查询元素"></a>查询元素</h2><h3 id="根据索引查询元素值"><a href="#根据索引查询元素值" class="headerlink" title="根据索引查询元素值"></a>根据索引查询元素值</h3><blockquote><p>时间复杂度为 O(1)</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">const</span> element = arr[<span class="number">1</span>]; <span class="comment">// 查询索引 1 的元素 2。</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(element); <span class="comment">// 2</span></span><br></pre></td></tr></table></figure><h3 id="根据元素值查找索引"><a href="#根据元素值查找索引" class="headerlink" title="根据元素值查找索引"></a>根据元素值查找索引</h3><blockquote><p>时间复杂度为 O(N)</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">const</span> index = arr.<span class="title function_">indexOf</span>(<span class="number">2</span>); <span class="comment">// 查询元素 2 的索引 1。</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(index); <span class="comment">// 1</span></span><br></pre></td></tr></table></figure><h1 id="链表"><a href="#链表" class="headerlink" title="链表"></a>链表</h1><p>链表（Linked List）是一种常见的线性数据结构，它由一系列节点（Node）组成，每个节点包含两部分：数据域和指针域。数据域用于存储实际的数据，指针域则存储指向下一个节点的引用（在单链表中），通过这些指针将各个节点链接起来形成一个链式结构。</p><p>链表存储有序的元素集合，但不同于数组，链表中的元素在内存中不是连续放置的，而是通过指针相互关联。</p><h2 id="链表的特点"><a href="#链表的特点" class="headerlink" title="链表的特点"></a>链表的特点</h2><ul><li>插入和删除的效率高：在链表中插入或删除节点，只需要修改指针，时间复杂度为 O(1)。</li><li>随机访问效率低：由于链表中的节点在内存中不是连续存储的，不能像数组那样通过下标直接访问元素，需要从头节点开始依次遍历，时间复杂度为 O(n)。</li></ul><h2 id="链表的类型"><a href="#链表的类型" class="headerlink" title="链表的类型"></a>链表的类型</h2><ul><li>单链表：每个节点包含一个数据元素和一个指向下一个节点的指针。链表的最后一个节点的指针通常指向 <code>null</code>，表示链表的结束。</li><li>双向链表：每个节点除了包含数据元素和指向下一个节点的指针外，还包含一个指向前一个节点的指针。这种结构允许在链表中双向遍历。</li><li>循环链表：单循环链表的最后一个节点的指针指向头节点，形成一个闭环；双循环链表则是双向链表的首尾相连形成闭环。</li></ul><h2 id="创建链表"><a href="#创建链表" class="headerlink" title="创建链表"></a>创建链表</h2><h3 id="节点类的实现"><a href="#节点类的实现" class="headerlink" title="节点类的实现"></a>节点类的实现</h3><ul><li>链表最后一个节点的下一个元素始终是 <code>undefined</code> 或 <code>null</code>。</li><li>当一个 Node 实例被创建时，它的 <code>next</code> 指针总是 <code>undefined</code>。这没问题，因为我们知道它会是链表的最后一项。</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ListNode</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">element</span>) &#123;</span><br><span class="line">    <span class="comment">// 数据域，存储节点的值</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">element</span> = element;</span><br><span class="line">    <span class="comment">// 指针域，指向下一个节点</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">next</span> = <span class="literal">undefined</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="链表类的实现"><a href="#链表类的实现" class="headerlink" title="链表类的实现"></a>链表类的实现</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">LinkedList</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">// 存储链表中的元素数量</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">count</span> = <span class="number">0</span>;</span><br><span class="line">    <span class="comment">// 头节点，初始化为 undefined</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">head</span> = <span class="literal">undefined</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="向链表尾部添加元素"><a href="#向链表尾部添加元素" class="headerlink" title="向链表尾部添加元素"></a>向链表尾部添加元素</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">LinkedList</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">count</span> = <span class="number">0</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">head</span> = <span class="literal">undefined</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">push</span>(<span class="params">element</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> node = <span class="keyword">new</span> <span class="title class_">ListNode</span>(element);</span><br><span class="line">    <span class="keyword">let</span> current;</span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">head</span> == <span class="literal">null</span>) &#123;</span><br><span class="line">      <span class="comment">// 如果链表为空，将头节点指向新节点</span></span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">head</span> = node;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      current = <span class="variable language_">this</span>.<span class="property">head</span>;</span><br><span class="line">      <span class="comment">// 遍历链表，找到最后一个节点</span></span><br><span class="line">      <span class="keyword">while</span> (current.<span class="property">next</span> != <span class="literal">null</span>) &#123;</span><br><span class="line">        current = current.<span class="property">next</span>;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">// 将最后一个节点的 next 指针指向新节点</span></span><br><span class="line">      current.<span class="property">next</span> = node;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 递增链表的长度</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">count</span>++;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="循环迭代链表直到目标位置"><a href="#循环迭代链表直到目标位置" class="headerlink" title="循环迭代链表直到目标位置"></a>循环迭代链表直到目标位置</h2><p>循环到目标 index 的代码片段在 LinkedList 类的方法中很常见、因此我们将其提取到一个单独的方法中，这样就可以在不同的地方复用它。</p><blockquote><p>注意：循环停止条件是小于目标 index，而不是小于等。这是因为目标的前一个节点的 next 就是目标节点。</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">LinkedList</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">count</span> = <span class="number">0</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">head</span> = <span class="literal">undefined</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">getElementAt</span>(<span class="params">index</span>) &#123;</span><br><span class="line">    <span class="comment">// 检查越界值</span></span><br><span class="line">    <span class="keyword">if</span> (index &gt;= <span class="number">0</span> &amp;&amp; index &lt;= <span class="variable language_">this</span>.<span class="property">count</span>) &#123;</span><br><span class="line">      <span class="keyword">let</span> current = <span class="variable language_">this</span>.<span class="property">head</span>;</span><br><span class="line">      <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; index &amp;&amp; current != <span class="literal">null</span>; i++) &#123;</span><br><span class="line">        current = current.<span class="property">next</span>;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> current;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 显式声明返回值</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">undefined</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="移除指定位置的元素"><a href="#移除指定位置的元素" class="headerlink" title="移除指定位置的元素"></a>移除指定位置的元素</h2><ul><li>移除第一个元素：要做的就是让 <code>head</code> 指向列表的第二个元素。我们将用 <code>current</code> 变量创建一个对链表第一个元素的引用。这样 <code>current</code> 变量就是对链表中第一个元素的引用。如果把 <code>head</code> 赋为 <code>current.next</code>，就会移除第一个元素。</li><li>移除最后一个或中间某个元素：需要迭代链表的节点，直到找到要移除元素的前一个元素，我们用 <code>previous</code> 变量表示对当前元素的前一个元素的引用。在迭代到目标元素之后，<code>current</code> 变量会持有我们想从链表中移除的节点。因此，要从链表中移除当前元素，要做的就是将 <code>previous.next</code> 赋值为 <code>current.next</code>。</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">LinkedList</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">count</span> = <span class="number">0</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">head</span> = <span class="literal">undefined</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">getElementAt</span>(<span class="params">index</span>) &#123;</span><br><span class="line">    <span class="comment">// 检查越界值</span></span><br><span class="line">    <span class="keyword">if</span> (index &gt;= <span class="number">0</span> &amp;&amp; index &lt;= <span class="variable language_">this</span>.<span class="property">count</span>) &#123;</span><br><span class="line">      <span class="keyword">let</span> current = <span class="variable language_">this</span>.<span class="property">head</span>;</span><br><span class="line">      <span class="comment">// 迭代整个链表直到目标 index。结束循环时，node 元素将是 index 位置元素的引用。</span></span><br><span class="line">      <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; index &amp;&amp; current != <span class="literal">null</span>; i++) &#123;</span><br><span class="line">        current = current.<span class="property">next</span>;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> current;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">undefined</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 从链表的特定位置移除一个元素。</span></span><br><span class="line"><span class="comment">   * <span class="doctag">@param</span> &#123;<span class="type">number</span>&#125; <span class="variable">index</span> - 要移除的元素的索引。</span></span><br><span class="line"><span class="comment">   * <span class="doctag">@returns</span> &#123;<span class="type">any</span>&#125; - 被移除的元素的值。</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="title function_">removeAt</span>(<span class="params">index</span>) &#123;</span><br><span class="line">    <span class="comment">// 检查越界值</span></span><br><span class="line">    <span class="keyword">if</span> (index &gt;= <span class="number">0</span> &amp;&amp; index &lt; <span class="variable language_">this</span>.<span class="property">count</span>) &#123;</span><br><span class="line">      <span class="keyword">let</span> current = <span class="variable language_">this</span>.<span class="property">head</span>;</span><br><span class="line">      <span class="comment">// 移除第一项</span></span><br><span class="line">      <span class="keyword">if</span> (index === <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">head</span> = current.<span class="property">next</span>;</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> previous = <span class="variable language_">this</span>.<span class="title function_">getElementAt</span>(index - <span class="number">1</span>);</span><br><span class="line">        current = previous.<span class="property">next</span>;</span><br><span class="line">        <span class="comment">// 将 previous 与 current 的下一项链接起来：跳过 current，从而移除它</span></span><br><span class="line">        previous.<span class="property">next</span> = current.<span class="property">next</span>;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">count</span>--;</span><br><span class="line">      <span class="keyword">return</span> current.<span class="property">element</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">undefined</span>; <span class="comment">// 如果索引无效，则返回 undefined</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>验证：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> list = <span class="keyword">new</span> <span class="title class_">LinkedList</span>();</span><br><span class="line">list.<span class="title function_">push</span>(<span class="number">5</span>);</span><br><span class="line">list.<span class="title function_">push</span>(<span class="number">10</span>);</span><br><span class="line">list.<span class="title function_">push</span>(<span class="number">15</span>);</span><br><span class="line">list.<span class="title function_">push</span>(<span class="number">20</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 验证删除指定元素</span></span><br><span class="line">list.<span class="title function_">removeAt</span>(<span class="number">1</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">info</span>(list);</span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1739283779711.png" alt="移除指定位置的元素"></p><p>如图可以看到，10 被成功移除了。</p><h2 id="在任意位置插入元素-1"><a href="#在任意位置插入元素-1" class="headerlink" title="在任意位置插入元素"></a>在任意位置插入元素</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">LinkedList</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">count</span> = <span class="number">0</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">head</span> = <span class="literal">undefined</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 在任意位置插入元素</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="title function_">insert</span>(<span class="params">element, index</span>) &#123;</span><br><span class="line">    <span class="comment">// 由于我们处理的是位置，就需要检查越界值</span></span><br><span class="line">    <span class="keyword">if</span> (index &gt;= <span class="number">0</span> &amp;&amp; index &lt;= <span class="variable language_">this</span>.<span class="property">count</span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> node = <span class="keyword">new</span> <span class="title class_">ListNode</span>(element);</span><br><span class="line">      <span class="keyword">if</span> (index === <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">// 在第一个位置添加</span></span><br><span class="line">        <span class="keyword">const</span> current = <span class="variable language_">this</span>.<span class="property">head</span>;</span><br><span class="line">        node.<span class="property">next</span> = current;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">head</span> = node;</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> previous = <span class="variable language_">this</span>.<span class="title function_">getElementAt</span>(index - <span class="number">1</span>);</span><br><span class="line">        <span class="keyword">const</span> current = previous.<span class="property">next</span>;</span><br><span class="line">        node.<span class="property">next</span> = current;</span><br><span class="line">        previous.<span class="property">next</span> = node;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">count</span>++; <span class="comment">// 更新链表长度</span></span><br><span class="line">      <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>注意：使用变量引用我们需要控制的节点非常重要，这样就不会丢失节点之间的链接。我们可以只使用一个变量（previous），但那样会很难控制节点之间的链接。<br>因此，最后声明一个额外的 current 变量来帮助我们处理这些引用。</p></blockquote><p>验证：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> list = <span class="keyword">new</span> <span class="title class_">LinkedList</span>();</span><br><span class="line">list.<span class="title function_">push</span>(<span class="string">&quot;张三&quot;</span>);</span><br><span class="line">list.<span class="title function_">push</span>(<span class="string">&quot;王五&quot;</span>);</span><br><span class="line">list.<span class="title function_">insert</span>(<span class="string">&quot;李四&quot;</span>, <span class="number">1</span>);</span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1739287449033.png" alt="在任意位置插入元素"></p><p>如图，我们成功在索引 1 的位置插入了 15。</p><h2 id="返回一个元素的位置"><a href="#返回一个元素的位置" class="headerlink" title="返回一个元素的位置"></a>返回一个元素的位置</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">defaultEquals</span> = (<span class="params">a, b</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> a === b;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">LinkedList</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">equalsFn = defaultEquals</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">count</span> = <span class="number">0</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">head</span> = <span class="literal">undefined</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">equalsFn</span> = equalsFn;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">indexOf</span>(<span class="params">element</span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> current = <span class="variable language_">this</span>.<span class="property">head</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; <span class="variable language_">this</span>.<span class="property">count</span> &amp;&amp; current != <span class="literal">null</span>; i++) &#123;</span><br><span class="line">      <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="title function_">equalsFn</span>(element, current.<span class="property">element</span>)) &#123;</span><br><span class="line">        <span class="keyword">return</span> i;</span><br><span class="line">      &#125;</span><br><span class="line">      current = current.<span class="property">next</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>一如既往，需要一个变量来帮助我们循环访问列表。该变量是 current，它的初始值是 head。</li><li>然后迭代元素，从 head（索引 0） 开始，直到链表长度（count 变量）为止。为了确保不会发生运行时错误，我们可以验证下 current 变量是否为 null 或 undefined。</li></ul><h2 id="从链表中移除元素"><a href="#从链表中移除元素" class="headerlink" title="从链表中移除元素"></a>从链表中移除元素</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">LinkedList</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">count</span> = <span class="number">0</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">head</span> = <span class="literal">undefined</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">remove</span>(<span class="params">element</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> index = <span class="variable language_">this</span>.<span class="title function_">indexOf</span>(element);</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="title function_">removeAt</span>(index);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="isEmpty、size-和-getHead-方法"><a href="#isEmpty、size-和-getHead-方法" class="headerlink" title="isEmpty、size 和 getHead 方法"></a>isEmpty、size 和 getHead 方法</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">LinkedList</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">count</span> = <span class="number">0</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">head</span> = <span class="literal">undefined</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">isEmpty</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">count</span> === <span class="number">0</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">size</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">count</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">getHead</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">head</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>size：返回链表包含的元素个数，与数组的 length 属性类似。</li><li>getHead：返回链表包含的元素个数，与数组的 length 属性类似。</li><li>isEmpty：在链表中不包含元素时，返回 true，如果链表长度大于 0 则返回 false。</li></ul><h2 id="toString-方法"><a href="#toString-方法" class="headerlink" title="toString 方法"></a>toString 方法</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">LinkedList</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">count</span> = <span class="number">0</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">head</span> = <span class="literal">undefined</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">toString</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">head</span> === <span class="literal">null</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">let</span> objString = <span class="string">`<span class="subst">$&#123;<span class="variable language_">this</span>.head.element&#125;</span>`</span>;</span><br><span class="line">    <span class="keyword">let</span> current = <span class="variable language_">this</span>.<span class="property">head</span>.<span class="property">next</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">1</span>; i &lt; <span class="variable language_">this</span>.<span class="title function_">size</span>() &amp;&amp; current !== <span class="literal">null</span>; i++) &#123;</span><br><span class="line">      objString = <span class="string">`<span class="subst">$&#123;objString&#125;</span>,<span class="subst">$&#123;current.element&#125;</span>`</span>;</span><br><span class="line">      current = current.<span class="property">next</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> objString;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>首先，列表为空（<code>head</code> 为 <code>null</code> 或 <code>undefined</code>），我们就返回一个空字符串，我们也可以用 <code>this.isEmpty()</code> 来判断。</li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/71833ca04b6f/</id>
    <link href="https://blog.zisheng.pro/posts/71833ca04b6f/"/>
    <published>2025-01-21T15:57:59.000Z</published>
    <summary>探索 JavaScript 数据结构的奥秘，掌握数组、链表、树等多种结构，为高效编程与算法实现筑牢根基。</summary>
    <title>JavaScript 数据结构</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="英语" scheme="https://blog.zisheng.pro/categories/%E8%8B%B1%E8%AF%AD/"/>
    <category term="all,port,纠音" scheme="https://blog.zisheng.pro/tags/all-port-%E7%BA%A0%E9%9F%B3/"/>
    <content>
      <![CDATA[<h2 id="All-纠音"><a href="#All-纠音" class="headerlink" title="All 纠音"></a>All 纠音</h2><h3 id="音标分析"><a href="#音标分析" class="headerlink" title="音标分析"></a>音标分析</h3><ul><li><code>[ɔː]</code>：这是一个长元音，发音时口型要张大，舌身压低并后缩，舌尖不抵下齿，双唇稍收圆。可以先发 <code>[ɔ]</code> 的音，然后将音拉长，就得到了 <code>[ɔː]</code> 的发音。</li><li><code>[l]</code>：这个音是舌边音，当它位于元音后时，舌尖抵住上齿龈，舌面稍凹，舌后部向软腭抬起，气流从舌两侧通过，发出类似 “欧” 的音，但要注意舌尖始终抵住上齿龈，不要离开。</li></ul><h3 id="发音技巧"><a href="#发音技巧" class="headerlink" title="发音技巧"></a>发音技巧</h3><ul><li>先发 <code>[ɔː]</code> 的音，要保证发音的长度和饱满度，然后自然地滑向 <code>[l]</code> 的音，将二者连贯地发出，不要有明显的停顿。</li><li>注意整体发音的流畅性和连贯性，不要把 <code>[ɔː]</code> 和 <code>[l]</code> 的发音割裂开来，同时要注意发音的力度和清晰度，不要含糊不清。</li></ul><h3 id="常见错误及纠正"><a href="#常见错误及纠正" class="headerlink" title="常见错误及纠正"></a>常见错误及纠正</h3><ul><li>错误一：把 <code>[ɔː]</code> 发成 <code>[ɒ]</code>，这是比较常见的错误，<code>[ɒ]</code> 的口型比 <code>[ɔː]</code> 要小，发音也更短促。纠正方法是张大嘴巴，尽量将舌身压低后缩，发出更饱满的 <code>[ɔː]</code> 音。</li><li>错误二：忽略 <code>[l]</code> 的发音，或者发音不清晰。这可能导致单词的发音不准确，听起来像 <code>[ɔː]</code> 单独的音。要注意舌尖抵住上齿龈，清晰地发出 <code>[l]</code> 的音，使整个单词的发音完整准确。</li><li>错误三：发音时口型变化不自然，导致发音生硬。发音过程中要注意口型的逐渐过渡，从 <code>[ɔː]</code> 的张大嘴巴到 <code>[l]</code> 的舌尖抵住上齿龈，动作要连贯自然，才能发出准确流畅的 “all” 的音。<br>可以多听一些标准的发音音频，模仿练习，不断纠正自己的发音，逐渐掌握 “all” 的正确发音。</li></ul><h2 id="port-词根"><a href="#port-词根" class="headerlink" title="port 词根"></a>port 词根</h2><h3 id="词根-port-的来源和历史演变"><a href="#词根-port-的来源和历史演变" class="headerlink" title="词根 port 的来源和历史演变"></a>词根 port 的来源和历史演变</h3><p>词根 “port” 来源于拉丁语动词 “portare”，其基本含义是 “to carry”，即 “搬运”“携带” 之意。</p><p>在拉丁语中，“portare” 是一个常用的动词，通过词形变化等方式衍生出了众多与 “搬运”“携带” 相关的词汇。后来，随着语言的发展和演变，这些词汇被借入到英语中，并在英语中保留了 “port” 这个词根，且其含义也基本保持了与 “搬运”“携带” 相关的意义，如 “import”“export”“transport” 等词都体现了这一基本含义。</p><p>同时，从 “搬运” 这一动作延伸，与货物的装卸、运输等相关的地点也用 “port” 来表示，进而有了 “港口” 的含义，像 “seaport”“airport” 等词都是由此演变而来。这体现了语言在发展过程中，词汇的意义会根据人们的认知和社会发展的需要逐渐扩展和演变。</p><h3 id="表示-“搬运”"><a href="#表示-“搬运”" class="headerlink" title="表示 “搬运”"></a>表示 “搬运”</h3><ul><li>import：im - 表示 “进入”，import 即把物品搬运进入一个国家或地区，意为 “进口”，如：The country imports a large amount of oil.（这个国家进口大量石油。）</li><li>export：ex - 表示 “出去”，export 则是把物品搬运出去，即 “出口”，如：China exports many kinds of products to the world.（中国向世界出口多种产品。）</li><li>transport：trans - 表示 “转移”“跨越”，transport 就是把人或物从一处搬运到另一处，意为 “运输”“运送”，如：The goods were transported by train.（货物通过火车运输。）</li></ul><h3 id="表示-“携带”"><a href="#表示-“携带”" class="headerlink" title="表示 “携带”"></a>表示 “携带”</h3><ul><li>portable：-able 表示 “可…… 的”，portable 即可以携带的，意为 “便携式的”“轻便的”，如：I bought a - portable computer.（我买了一台便携式电脑。）</li><li>porter：表示搬运东西的人，即 “搬运工”“行李员”，如：The porter carried our luggage to the room.（行李员把我们的行李搬到房间。）</li></ul><h3 id="表示-“港口”"><a href="#表示-“港口”" class="headerlink" title="表示 “港口”"></a>表示 “港口”</h3><ul><li>port：本身可作名词表示 “港口”“口岸”，如：The ship arrived at the port safely.（船安全抵达港口。）</li><li>airport：air 表示 “空气”“天空”，airport 即飞机起降的港口，意为 “机场”，如：I will meet you at the airport.（我会在机场接你。）</li><li>seaport：sea 表示 “海”“海洋”，seaport 就是海边的港口，意为 “海港”“港口城市”，如：This city is a famous seaport.（这座城市是一个著名的海港。）</li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/b8cca4512b3f/</id>
    <link href="https://blog.zisheng.pro/posts/b8cca4512b3f/"/>
    <published>2024-11-18T23:33:25.000Z</published>
    <summary>“all” 的读音是 [ɔːl]，“port” 的读音是 [pɔːrt]</summary>
    <title>
      <![CDATA[All 纠音 & port 词根]]>
    </title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="英语" scheme="https://blog.zisheng.pro/categories/%E8%8B%B1%E8%AF%AD/"/>
    <category term="priv,Hello" scheme="https://blog.zisheng.pro/tags/priv-Hello/"/>
    <content>
      <![CDATA[<h2 id="Hello-纠音"><a href="#Hello-纠音" class="headerlink" title="Hello 纠音"></a>Hello 纠音</h2><p>Hello 的常见发音是 <code>[həˈləʊ]</code>，以下是对这个单词发音的详细纠音讲解：</p><h3 id="音标分析"><a href="#音标分析" class="headerlink" title="音标分析"></a>音标分析</h3><ul><li><code>[h]</code>：这是一个清辅音，发音时气流从肺部呼出，通过声门时不受阻碍，仅在通过声门时形成轻微的摩擦，声带不振动。发音时，嘴巴张开，舌头自然放松，让气流自然地从口中呼出，同时发出 “喝” 的音，但不要过于用力。例如，在单词 hat <code>[hæt]</code>、hot <code>[hɒt]</code> 中，都有这个音素。</li><li><code>[ə]</code>：这是一个中央元音，发音时舌身中部稍抬起，舌尖微抵下齿龈，双唇向两旁平伸，成扁平形。发音时，口腔肌肉放松，发出类似 “呃” 的音，是一个比较弱的音。比如在单词 about <code>[əˈbaʊt]</code>、again <code>[əˈɡen]</code> 中都有此音。</li><li><code>[ˈləʊ]</code>：这是一个双元音，由 <code>[l]</code> 和 <code>[əʊ]</code> 两个音素组成。首先发 <code>[l]</code> 音，舌尖抵住上齿龈，舌前部向硬腭尽量抬起，气流从舌的两侧泄出，声带振动，这个音类似于汉语中的 “了”，但发音位置更靠后。然后迅速向 <code>[əʊ]</code> 滑动，[əʊ] 的发音是由 <code>[ə]</code> 向 <code>[ʊ]</code> 滑动，双唇由扁平到收圆，声带振动，发出类似 “欧” 的音。在单词 low <code>[ləʊ]</code>、go <code>[ɡəʊ]</code> 中都含有 <code>[əʊ]</code> 这个音素。</li></ul><h3 id="发音技巧"><a href="#发音技巧" class="headerlink" title="发音技巧"></a>发音技巧</h3><ul><li>注意重音在第二个音节，即 <code>[ləʊ]</code> 上，发音时要适当加重这个音节的读音，突出重音，使发音更准确自然。</li><li>整个单词发音要连贯流畅，各个音素之间的过渡要自然，不要有明显的停顿或割裂感。特别是在发双元音 <code>[ˈləʊ]</code> 时，从 <code>[l]</code> 到 <code>[əʊ]</code> 的滑动要迅速而自然，避免生硬。</li></ul><h2 id="priv-词根"><a href="#priv-词根" class="headerlink" title="priv 词根"></a>priv 词根</h2><h3 id="单个"><a href="#单个" class="headerlink" title="单个"></a>单个</h3><p>含义解释：强调个体的、独自的，与集体相对。</p><p>例词：</p><ul><li>private <code>[&#39;praɪvət]</code>：adj. 私人的；个人的；私下的；私有的。<ul><li>This is my private car.（这是我的私人汽车。）</li></ul></li><li>privacy <code>[&#39;prɪvəsi]</code>： n. 隐私；秘密；隐居；隐居处。<ul><li>We should respect everyone’s privacy.（我们应该尊重每个人的隐私。）</li></ul></li><li>privilege <code>[ˈprɪvəlɪdʒ]</code>：n. 特权，优待。&#x3D; priv（单个） + i + lege（法律）<ul><li>Gold card holders enjoy more privileges.（金牌持有者享有更多的特权。）</li></ul></li></ul><h3 id="使丧失"><a href="#使丧失" class="headerlink" title="使丧失"></a>使丧失</h3><p>含义解释：有剥夺、使失去某种权利或东西的意思。</p><p>例词：</p><ul><li>deprive <code>[dɪ&#39;praɪv]</code>：vt. 使丧失，剥夺。<ul><li>The accident deprived him of his legs.（这场事故使他失去了双腿。）</li></ul></li><li>privation <code>[praɪ&#39;veɪʃn]</code>：n. 贫困；丧失；被剥夺了某物的状态。<ul><li>He suffered great privation during the war.（他在战争期间遭受了极大的困苦。）</li></ul></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/bad34c984261/</id>
    <link href="https://blog.zisheng.pro/posts/bad34c984261/"/>
    <published>2024-11-14T04:45:44.000Z</published>
    <summary>Hello 并不是哈喽，每日震惊</summary>
    <title>
      <![CDATA[Hello 纠音 & priv 词根]]>
    </title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="issues" scheme="https://blog.zisheng.pro/categories/issues/"/>
    <category term="node" scheme="https://blog.zisheng.pro/categories/issues/node/"/>
    <content>
      <![CDATA[<p>在 Node.js 环境中，尤其是在使用最新的 Node.js 版本时，你可能会遇到 Error: error:0308010C:digital envelope routines::unsupported 错误。这个错误通常与 Node.js 的加密模块和 OpenSSL 版本有关。如果可以改 Node.js 版本，安装老的 Node 版本可以解决</p><p>最简单的解决方法是设置一个环境变量，告诉 Node.js 使用旧的加密算法。在命令行中运行以下命令：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> NODE_OPTIONS=--openssl-legacy-provider</span><br></pre></td></tr></table></figure><p>如果你使用的是Windows系统，则可以运行：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">set</span> NODE_OPTIONS=--openssl-legacy-provider</span><br></pre></td></tr></table></figure><p>然后重新运行你的 Node.js 应用程序。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/632fe69d082f/</id>
    <link href="https://blog.zisheng.pro/posts/632fe69d082f/"/>
    <published>2024-10-24T09:48:38.000Z</published>
    <summary>
      <![CDATA[<p>在 Node.js 环境中，尤其是在使用最新的 Node.js 版本时，你可能会遇到 Error: error:0308010C:digital envelope routines::unsupported 错误。这个错误通常与 Node.js 的加密模块和 OpenSSL]]>
    </summary>
    <title>Error: error:0308010C:digital envelope routines::unsupported</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/categories/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="NodeJs" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/NodeJs/"/>
    <category term="Node.js" scheme="https://blog.zisheng.pro/tags/Node-js/"/>
    <category term="服务器创建" scheme="https://blog.zisheng.pro/tags/%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%88%9B%E5%BB%BA/"/>
    <category term="express" scheme="https://blog.zisheng.pro/tags/express/"/>
    <category term="ws" scheme="https://blog.zisheng.pro/tags/ws/"/>
    <category term="multer" scheme="https://blog.zisheng.pro/tags/multer/"/>
    <category term="后端开发基础" scheme="https://blog.zisheng.pro/tags/%E5%90%8E%E7%AB%AF%E5%BC%80%E5%8F%91%E5%9F%BA%E7%A1%80/"/>
    <category term="服务器工作原理" scheme="https://blog.zisheng.pro/tags/%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86/"/>
    <content>
      <![CDATA[<blockquote><p>原文地址：<a href="https://dev.to/trixsec/how-to-turn-your-device-into-a-simple-server-using-nodejs-1460">How to Turn Your Device Into a Simple Server Using Node.js</a><br>原文作者：<a href="https://dev.to/trixsec">Trix Cyrus</a></p></blockquote><p>让我们一起在你的设备上部署一个简单的 Node.js。</p><h2 id="入门"><a href="#入门" class="headerlink" title="入门"></a>入门</h2><p>首先，让我们使用 Node.js 创建一个基础的服务。步骤如下：</p><p>创建一个 <code>node-server</code> 目录：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> node-server</span><br><span class="line"><span class="built_in">cd</span> node-server</span><br></pre></td></tr></table></figure><p>初始化一个 Node.js 项目：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm init -y</span><br></pre></td></tr></table></figure><p>安装依赖：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install express ws multer mongoose dotenv helmet morgan cluster compression</span><br></pre></td></tr></table></figure><ul><li>express：处理 HTTP 请求</li><li>ws：支持 Websocket</li><li>multer：处理文件上传</li><li>mongoose：MongoDB 链接</li><li>dotenv：读取环境变量</li><li>helmet：保护服务器安全</li><li>morgan：日志</li><li>cluster：多线程</li><li>compression：用于压缩响应数据</li></ul><h2 id="创建一个-env-文件"><a href="#创建一个-env-文件" class="headerlink" title="创建一个 .env 文件"></a>创建一个 .env 文件</h2><p>在项目根目录，创建一个 <code>.env</code> 文件，它的作用是用来存储环境变量：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">PORT=8080</span><br><span class="line">MONGO_URI=mongodb://localhost:27017/serverdb</span><br></pre></td></tr></table></figure><p>创建一个 <code>server.js</code> 文件：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">touch</span> server.js</span><br><span class="line">nano server.js</span><br></pre></td></tr></table></figure><p>粘贴下面的代码：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">require</span>(<span class="string">&#x27;dotenv&#x27;</span>).<span class="title function_">config</span>();</span><br><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">&#x27;express&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> ws = <span class="built_in">require</span>(<span class="string">&#x27;ws&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> http = <span class="built_in">require</span>(<span class="string">&#x27;http&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> multer = <span class="built_in">require</span>(<span class="string">&#x27;multer&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> mongoose = <span class="built_in">require</span>(<span class="string">&#x27;mongoose&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> helmet = <span class="built_in">require</span>(<span class="string">&#x27;helmet&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> morgan = <span class="built_in">require</span>(<span class="string">&#x27;morgan&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> cluster = <span class="built_in">require</span>(<span class="string">&#x27;cluster&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> os = <span class="built_in">require</span>(<span class="string">&#x27;os&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> compression = <span class="built_in">require</span>(<span class="string">&#x27;compression&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">&#x27;path&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">PORT</span> = process.<span class="property">env</span>.<span class="property">PORT</span> || <span class="number">8080</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">MONGO_URI</span> = process.<span class="property">env</span>.<span class="property">MONGO_URI</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">NUM_CPUS</span> = os.<span class="title function_">cpus</span>().<span class="property">length</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (cluster.<span class="property">isMaster</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Master process is running. Spawning <span class="subst">$&#123;NUM_CPUS&#125;</span> workers...`</span>);</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; <span class="variable constant_">NUM_CPUS</span>; i++) &#123;</span><br><span class="line">    cluster.<span class="title function_">fork</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  cluster.<span class="title function_">on</span>(<span class="string">&#x27;exit&#x27;</span>, <span class="function">(<span class="params">worker, code, signal</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Worker <span class="subst">$&#123;worker.process.pid&#125;</span> died. Spawning a new worker.`</span>);</span><br><span class="line">    cluster.<span class="title function_">fork</span>();</span><br><span class="line">  &#125;);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> app = <span class="title function_">express</span>();</span><br><span class="line">  <span class="keyword">const</span> server = http.<span class="title function_">createServer</span>(app);</span><br><span class="line">  <span class="keyword">const</span> wss = <span class="keyword">new</span> ws.<span class="title class_">Server</span>(&#123; server &#125;);</span><br><span class="line"></span><br><span class="line">  mongoose.<span class="title function_">connect</span>(<span class="variable constant_">MONGO_URI</span>, &#123; <span class="attr">useNewUrlParser</span>: <span class="literal">true</span>, <span class="attr">useUnifiedTopology</span>: <span class="literal">true</span> &#125;)</span><br><span class="line">    .<span class="title function_">then</span>(<span class="function">() =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Connected to MongoDB&#x27;</span>))</span><br><span class="line">    .<span class="title function_">catch</span>(<span class="function"><span class="params">err</span> =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;MongoDB connection error:&#x27;</span>, err));</span><br><span class="line"></span><br><span class="line">  app.<span class="title function_">use</span>(express.<span class="title function_">json</span>());</span><br><span class="line">  app.<span class="title function_">use</span>(<span class="title function_">helmet</span>());</span><br><span class="line">  app.<span class="title function_">use</span>(<span class="title function_">morgan</span>(<span class="string">&#x27;common&#x27;</span>));</span><br><span class="line">  app.<span class="title function_">use</span>(<span class="title function_">compression</span>());</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> upload = <span class="title function_">multer</span>(&#123; <span class="attr">dest</span>: <span class="string">&#x27;uploads/&#x27;</span> &#125;);</span><br><span class="line"></span><br><span class="line">  app.<span class="title function_">use</span>(express.<span class="title function_">static</span>(path.<span class="title function_">join</span>(__dirname, <span class="string">&#x27;public&#x27;</span>)));</span><br><span class="line"></span><br><span class="line">  app.<span class="title function_">use</span>(<span class="function">(<span class="params">req, res, next</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Received <span class="subst">$&#123;req.method&#125;</span> request for <span class="subst">$&#123;req.url&#125;</span>`</span>);</span><br><span class="line">    <span class="title function_">next</span>();</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  wss.<span class="title function_">on</span>(<span class="string">&#x27;connection&#x27;</span>, <span class="function">(<span class="params">socket</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;WebSocket connection established.&#x27;</span>);</span><br><span class="line">    socket.<span class="title function_">on</span>(<span class="string">&#x27;message&#x27;</span>, <span class="function">(<span class="params">message</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Received WebSocket message: <span class="subst">$&#123;message&#125;</span>`</span>);</span><br><span class="line">      socket.<span class="title function_">send</span>(<span class="string">`Message received: <span class="subst">$&#123;message&#125;</span>`</span>);</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  app.<span class="title function_">get</span>(<span class="string">&#x27;/&#x27;</span>, <span class="function">(<span class="params">req, res</span>) =&gt;</span> &#123;</span><br><span class="line">    res.<span class="title function_">sendFile</span>(path.<span class="title function_">join</span>(__dirname, <span class="string">&#x27;public&#x27;</span>, <span class="string">&#x27;index.html&#x27;</span>));</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  app.<span class="title function_">get</span>(<span class="string">&#x27;/api/data&#x27;</span>, <span class="keyword">async</span> (req, res) =&gt; &#123;</span><br><span class="line">    res.<span class="title function_">json</span>(&#123; <span class="attr">message</span>: <span class="string">&#x27;Server API Response&#x27;</span>, <span class="attr">data</span>: [] &#125;);</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  app.<span class="title function_">post</span>(<span class="string">&#x27;/upload&#x27;</span>, upload.<span class="title function_">single</span>(<span class="string">&#x27;file&#x27;</span>), <span class="function">(<span class="params">req, res</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;File uploaded:&#x27;</span>, req.<span class="property">file</span>);</span><br><span class="line">    res.<span class="title function_">status</span>(<span class="number">201</span>).<span class="title function_">json</span>(&#123; <span class="attr">message</span>: <span class="string">&#x27;File uploaded successfully&#x27;</span>, <span class="attr">file</span>: req.<span class="property">file</span> &#125;);</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  app.<span class="title function_">get</span>(<span class="string">&#x27;/greet/:name&#x27;</span>, <span class="function">(<span class="params">req, res</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> name = req.<span class="property">params</span>.<span class="property">name</span> || <span class="string">&#x27;stranger&#x27;</span>;</span><br><span class="line">    res.<span class="title function_">send</span>(<span class="string">`&lt;h1&gt;Hello, <span class="subst">$&#123;name&#125;</span>!&lt;/h1&gt;`</span>);</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  app.<span class="title function_">use</span>(<span class="function">(<span class="params">req, res, next</span>) =&gt;</span> &#123;</span><br><span class="line">    res.<span class="title function_">status</span>(<span class="number">404</span>).<span class="title function_">sendFile</span>(path.<span class="title function_">join</span>(__dirname, <span class="string">&#x27;public&#x27;</span>, <span class="string">&#x27;404.html&#x27;</span>));</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  app.<span class="title function_">use</span>(<span class="function">(<span class="params">err, req, res, next</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;Error encountered:&#x27;</span>, err);</span><br><span class="line">    res.<span class="title function_">status</span>(<span class="number">500</span>).<span class="title function_">json</span>(&#123; <span class="attr">error</span>: <span class="string">&#x27;An internal server error occurred&#x27;</span> &#125;);</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  server.<span class="title function_">listen</span>(<span class="variable constant_">PORT</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Worker process <span class="subst">$&#123;process.pid&#125;</span> is running on port <span class="subst">$&#123;PORT&#125;</span>`</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>创建一个 <code>public</code> 文件夹：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> public</span><br><span class="line"><span class="built_in">cd</span> public</span><br></pre></td></tr></table></figure><h2 id="index-html"><a href="#index-html" class="headerlink" title="index.html"></a>index.html</h2><p>用作的首页的基本的 HTML 文件：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;viewport&quot;</span> <span class="attr">content</span>=<span class="string">&quot;width=device-width, initial-scale=1.0&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>Node.js Server<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">h1</span>&gt;</span>Welcome to the node.js Server!<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">p</span>&gt;</span>This server is a simple server.<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="404-html"><a href="#404-html" class="headerlink" title="404.html"></a>404.html</h3><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;viewport&quot;</span> <span class="attr">content</span>=<span class="string">&quot;width=device-width, initial-scale=1.0&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>404 - Page Not Found<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">h1</span>&gt;</span>404 - Page Not Found<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">p</span>&gt;</span>The page you are looking for doesn&#x27;t exist in this universe.<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="启动服务器"><a href="#启动服务器" class="headerlink" title="启动服务器"></a>启动服务器</h2><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">node server.js</span><br></pre></td></tr></table></figure><h2 id="测试服务器功能"><a href="#测试服务器功能" class="headerlink" title="测试服务器功能"></a>测试服务器功能</h2><ul><li>实时通信：您可以设置一个 WebSocket 客户端向服务器发送消息。</li><li>文件上传：向“<a href="http://localhost:8080/upload%E2%80%9D%E5%8F%91%E9%80%81">http://localhost:8080/upload”发送</a> POST 请求并附上一个文件，它将被保存到上传文件夹中。</li><li>RESTful API：访问“<a href="http://localhost:8080/api/data%E2%80%9D%E4%BB%A5%E6%9F%A5%E7%9C%8B">http://localhost:8080/api/data”以查看</a> API 响应。</li><li>错误处理：访问任何不存在的 URL（例如“<a href="http://localhost:8080/nonexistent%E2%80%9D%EF%BC%89%E4%BB%A5%E6%9F%A5%E7%9C%8B%E8%87%AA%E5%AE%9A%E4%B9%89%E7%9A%84">http://localhost:8080/nonexistent”）以查看自定义的</a> 404 页面。</li><li>数据库：将您的服务器连接到一个 MongoDB 实例，并修改&#x2F;api&#x2F;data 路由以查询并返回实际数据。</li></ul><p>最终目录：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">node-server/</span><br><span class="line">├── public/</span><br><span class="line">│   ├── index.html</span><br><span class="line">│   ├── 404.html</span><br><span class="line">├── server.js</span><br></pre></td></tr></table></figure><h2 id="一切就绪"><a href="#一切就绪" class="headerlink" title="一切就绪"></a>一切就绪</h2><p>你已经在你的设备创建并运行一个简单的 Node.js 服务器！这是学习后端开发基础知识以及了解服务器如何工作的好方法。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/421dcbacb7e8/</id>
    <link href="https://blog.zisheng.pro/posts/421dcbacb7e8/"/>
    <published>2024-09-25T10:27:35.000Z</published>
    <summary>本文是一篇关于在设备上使用 Node.js 创建简单服务器的教程。作者首先详细介绍了创建服务器的具体步骤，包括创建目录、初始化项目、安装依赖、创建配置文件和服务器脚本等。接着展示了最终的目录结构，并对服务器脚本进行了详细分析，解释了每个安装的依赖的用途。此外，还介绍了测试服务器的多种方式，如实时通信、文件上传、访问 RESTful API、触发错误处理以及连接数据库等。最后强调了通过创建这个简单服务器来学习后端开发基础和了解服务器工作原理的价值。</summary>
    <title>如何使用 Node.js 将你的设备变成一个简单的服务器</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="Google" scheme="https://blog.zisheng.pro/categories/Google/"/>
    <category term="Adsense" scheme="https://blog.zisheng.pro/categories/Google/Adsense/"/>
    <category term="站点运营" scheme="https://blog.zisheng.pro/categories/%E7%AB%99%E7%82%B9%E8%BF%90%E8%90%A5/"/>
    <category term="广告" scheme="https://blog.zisheng.pro/categories/%E7%AB%99%E7%82%B9%E8%BF%90%E8%90%A5/%E5%B9%BF%E5%91%8A/"/>
    <category term="google adsense" scheme="https://blog.zisheng.pro/tags/google-adsense/"/>
    <category term="google adsense eCPM" scheme="https://blog.zisheng.pro/tags/google-adsense-eCPM/"/>
    <category term="google adsense eCPM 优化" scheme="https://blog.zisheng.pro/tags/google-adsense-eCPM-%E4%BC%98%E5%8C%96/"/>
    <content>
      <![CDATA[<h2 id="保险（Insurance）"><a href="#保险（Insurance）" class="headerlink" title="保险（Insurance）"></a>保险（Insurance）</h2><p>保险行业广告费投入多，主要有以下几方面原因：</p><p>1、行业竞争激烈</p><ul><li>市场主体众多：保险市场上的公司数量不断增加，无论是大型保险公司还是小型保险企业，都希望在市场中占据一席之地。截至2023年，国内的保险公司数量众多，各公司在产品、服务、价格等方面的差异较小，所以需要通过大量的广告宣传来突出自己的品牌和优势，吸引消费者的关注。例如，在车险市场，有多家大型保险公司以及众多中小保险公司竞争，为了争夺客户，都加大了广告投入。</li><li>产品同质化严重：保险产品在条款、保障范围等方面具有较高的相似性。对于消费者来说，很难从产品本身直观地分辨出不同公司产品的差异。因此，保险公司需要通过广告宣传来塑造品牌形象，让消费者对其品牌产生认知和信任，从而在众多竞品中选择自己的产品。</li></ul><p>2、品牌建设需求高：</p><ul><li>建立信任基础：保险产品是一种基于信任的金融服务，消费者在购买保险时，对保险公司的信誉和可靠性非常关注。通过大规模的广告宣传，保险公司可以向消费者传递自己的专业、可靠、稳健的形象，增强消费者对公司的信任。特别是对于一些新成立的保险公司或者知名度较低的公司，更需要通过广告来快速提升品牌知名度和信任度。</li><li>提升品牌影响力：在保险行业，品牌影响力对于公司的发展至关重要。具有较高品牌影响力的公司能够吸引更多的客户、合作伙伴和优秀的人才。广告宣传是提升品牌影响力的重要手段之一，通过在各种媒体上的广泛传播，保险公司的品牌能够被更多的人所了解和认可。</li></ul><p>3、客户获取成本高：</p><ul><li>销售模式的特点：保险产品的销售通常需要通过代理人、经纪人等中介渠道，或者通过电话销售、网络销售等方式进行。这些销售方式都需要投入大量的人力、物力和财力。相比其他行业，保险产品的销售周期较长，客户的决策过程也较为复杂，需要不断地进行沟通和解释。因此，保险公司需要通过广告宣传来提高客户的认知度和兴趣度，降低销售成本。</li><li>客户群体广泛：保险产品的潜在客户群体非常广泛，包括个人客户和企业客户，涵盖了不同年龄、性别、职业、收入水平的人群。为了覆盖到尽可能多的潜在客户，保险公司需要在各种媒体平台上进行广告投放，这也导致了广告费用的增加。</li></ul><p>4、业务拓展的需要：</p><ul><li>新产品推广：保险公司不断推出新的保险产品和服务，以满足市场需求和客户的个性化需求。新产品的推出需要进行大量的宣传推广，让消费者了解产品的特点、优势和适用场景。广告是新产品推广的重要手段之一，能够快速地将产品信息传递给潜在客户，提高产品的知名度和市场占有率。</li><li>开拓新市场：随着经济的发展和人们保险意识的提高，保险市场的潜力不断被挖掘。保险公司需要不断开拓新的市场领域，如农村市场、互联网保险市场等。在开拓新市场的过程中，广告宣传能够帮助保险公司快速地进入市场，提高品牌知名度和市场份额。</li></ul><p>5、监管要求和信息披露：</p><ul><li>合规宣传的需要：保险行业受到严格的监管，保险公司需要按照监管要求进行信息披露和宣传。为了确保广告内容的合规性和准确性，保险公司需要投入一定的资源进行广告策划、制作和审核。同时，监管部门也要求保险公司在广告宣传中不得进行虚假宣传、误导消费者等行为，这也增加了保险公司的广告成本。</li><li>风险提示和教育：保险产品涉及到复杂的条款和风险，消费者在购买保险时需要了解相关的风险和注意事项。保险公司需要通过广告宣传来进行风险提示和保险知识教育，帮助消费者正确理解保险产品，提高消费者的风险意识和保险素养。这也需要保险公司投入一定的广告费用。</li></ul><h3 id="推荐关键词"><a href="#推荐关键词" class="headerlink" title="推荐关键词"></a>推荐关键词</h3><ul><li>Insurance for employees 2024</li><li>Guardian Term Life Insurance</li><li>Recommend a nearby auto insurance company</li><li>Auto insurance comparison</li><li>Best car insurance policies</li><li>Insurance claim process</li><li>Affordable auto insurance</li><li>Comprehensive car insurance</li><li>High coverage auto insurance</li><li>Reliable insurance companies</li><li>Insurance discounts for safe drivers</li><li>Long-term car insurance plans</li><li>Insurance for luxury cars</li></ul><h2 id="法律（Lawyer）"><a href="#法律（Lawyer）" class="headerlink" title="法律（Lawyer）"></a>法律（Lawyer）</h2><ul><li>Medical Malpractice Lawyer</li><li>Legal advice</li><li>Business law</li><li>Criminal law</li><li>Family law</li><li>Contract law</li><li>Intellectual property law</li><li>Employment law</li><li>Real estate law</li><li>Environmental law</li><li>International law</li></ul><h2 id="教育（education）"><a href="#教育（education）" class="headerlink" title="教育（education）"></a>教育（education）</h2><ul><li>mba best colleges</li><li>1 year Master’s degrees</li><li>Recommend a nearby scholarships</li><li>Quality education resources</li><li>Innovative teaching methods</li><li>Best educational institutions</li><li>Online education platforms</li><li>Distance learning opportunities</li><li>Educational technology tools</li><li>Early childhood education</li><li>Higher education scholarships</li><li>Lifelong learning programs</li><li>Special education needs</li></ul><h2 id="律师（Attorney）"><a href="#律师（Attorney）" class="headerlink" title="律师（Attorney）"></a>律师（Attorney）</h2><ul><li>Top lawyers nearby</li><li>Best law firms for economy</li><li>Experienced criminal defense lawyers</li><li>Family law attorneys near me</li><li>Business litigation lawyers</li><li>Intellectual property lawyers</li><li>Personal injury lawyers reviews</li><li>Immigration lawyers services</li><li>Real estate lawyers advice</li><li>Bankruptcy lawyers assistance</li></ul><h2 id="AI-推荐"><a href="#AI-推荐" class="headerlink" title="AI 推荐"></a>AI 推荐</h2><ul><li>Financial planning（财务规划）：Deals with personal finance which attracts advertisers in the banking and investment sectors.</li><li>Health insurance comparison（健康保险比较）：High demand as people are constantly looking for the best health insurance options.</li><li>Luxury travel destinations（豪华旅游目的地）：Appeals to high-end travel companies and luxury brands.</li><li>Online business courses（在线商业课程）：Reason: Targets entrepreneurs and professionals looking to enhance their skills.</li><li>Smart home technology（智能家居技术）：A growing market with many companies advertising their smart home products.</li><li>Organic skincare products（有机护肤品）：Popular in the beauty and wellness industry.</li><li>Pet care services（宠物护理服务）：Pet owners are always looking for good care options for their furry friends.</li><li>Fitness equipment reviews（健身器材评价）：People interested in fitness are likely to click on ads related to fitness equipment.</li><li>Wedding planning ideas（婚礼策划创意）：A lucrative market with many advertisers in the wedding industry.</li><li>Green energy solutions（绿色能源解决方案）：As the demand for sustainable energy grows, advertisers in this field are likely to pay well.</li></ul><h2 id="Youtuber-推荐"><a href="#Youtuber-推荐" class="headerlink" title="Youtuber 推荐"></a>Youtuber 推荐</h2><ul><li>PaloAltoNetworks</li><li>Allergan</li><li>Avemco</li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/fe9898219413/</id>
    <link href="https://blog.zisheng.pro/posts/fe9898219413/"/>
    <published>2024-09-15T02:12:11.000Z</published>
    <summary>Google AdSense Shifts To eCPM Payment Model</summary>
    <title>Google Adsense High eCPM</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="方法论" scheme="https://blog.zisheng.pro/categories/%E6%96%B9%E6%B3%95%E8%AE%BA/"/>
    <category term="英语" scheme="https://blog.zisheng.pro/tags/%E8%8B%B1%E8%AF%AD/"/>
    <content>
      <![CDATA[<h2 id="学习方法"><a href="#学习方法" class="headerlink" title="学习方法"></a>学习方法</h2><h3 id="词汇积累"><a href="#词汇积累" class="headerlink" title="词汇积累"></a>词汇积累</h3><ol><li>制定计划：确定每天要学习的词汇量，比如每天学习 20 个新单词。可以利用单词书、手机应用（如百词斩、扇贝单词等）来辅助学习。</li><li>语境记忆：将单词放在具体的语境中去理解和记忆，比如阅读英文文章、观看英语电影或电视剧。遇到生词时，结合上下文猜测其含义，然后再查阅词典进行确认。</li><li>词根词缀法：学习常见的词根、词缀，这样可以帮助你推断出许多陌生单词的含义。例如，“un-”表示否定，“re-”表示再次等。</li><li>联想记忆：将新学的单词与已知的事物、场景或概念联系起来，创造有趣的联想，以便更好地记住它们。</li></ol><h3 id="听力训练"><a href="#听力训练" class="headerlink" title="听力训练"></a>听力训练</h3><ol><li>多听英语广播：如 BBC、CNN 等，让自己沉浸在英语环境中。可以在早上起床后、做家务时或者乘坐交通工具时收听。</li><li>观看英语电影和电视剧：选择有英文字幕的影片，先观看一遍了解剧情，然后再反复观看，尝试不看字幕理解对话内容。</li><li>听英语歌曲：选择自己喜欢的英语歌曲，边听边看歌词，学习其中的词汇和表达方式。还可以跟着哼唱，提高口语发音。</li><li>做听力练习：可以使用英语学习教材配套的听力材料，或者在网上搜索专门的听力练习题。做完练习后，对照答案分析错误原因，逐步提高听力水平。</li></ol><h3 id="口语练习"><a href="#口语练习" class="headerlink" title="口语练习"></a>口语练习</h3><ol><li>模仿跟读：选择英语原声材料，如英语电影片段、英语演讲等，进行模仿跟读。注意模仿语音、语调、语速和停顿，尽量让自己的发音更标准。</li><li>口语交流：寻找英语学习伙伴或者参加英语角，与他人进行口语交流。不要害怕犯错，勇敢地表达自己的想法。</li><li>录制自己的声音：用手机或录音设备录制自己的英语口语练习，然后回放听一听，找出不足之处并加以改进。</li><li>参加口语课程：报名参加线上或线下的英语口语课程，有专业的老师指导可以更快地提高口语水平。</li></ol><h3 id="阅读提升"><a href="#阅读提升" class="headerlink" title="阅读提升"></a>阅读提升</h3><ol><li>选择适合的阅读材料：根据自己的英语水平选择难度适中的英文书籍、报纸、杂志或文章。可以从简单的儿童读物开始，逐渐过渡到更复杂的文学作品。</li><li>精读与泛读结合：精读时，仔细分析文章的结构、语法和词汇，理解每一个句子的含义。泛读时，快速浏览文章，了解大意即可，主要是提高阅读速度和语感。</li><li>做阅读笔记：在阅读过程中，遇到好的词汇、短语和句子可以记录下来，方便日后复习和运用。</li><li>阅读挑战：给自己设定阅读目标，比如每周读完一本英文书或者每天阅读一定数量的文章，坚持下去可以提高阅读能力。</li></ol><h3 id="写作锻炼"><a href="#写作锻炼" class="headerlink" title="写作锻炼"></a>写作锻炼</h3><ol><li>仿写练习：选择优秀的英语范文，模仿其结构、风格和表达方式进行写作练习。可以从简单的句子仿写开始，逐渐过渡到段落和文章的仿写。</li><li>坚持写作日记：用英语记录自己的日常生活、想法和感受。这不仅可以提高写作能力，还能帮助你更好地掌握词汇和语法。</li><li>找老师或伙伴批改：将自己写的文章交给老师、英语学习伙伴或者使用在线写作批改工具，让他们指出其中的错误和不足之处，以便及时改进。</li><li>参加写作比赛：参加英语写作比赛可以激发自己的学习动力，同时也能从其他参赛者的作品中学习到很多写作技巧和经验。</li></ol><h2 id="英文环境搭建"><a href="#英文环境搭建" class="headerlink" title="英文环境搭建"></a>英文环境搭建</h2><p>为了更好地耳濡目染，将每天使用频率最高的工具设置成英文版本。</p><ul><li>vscode studio 不安装汉化插件</li><li>手机系统设置成英文</li></ul><h2 id="英语学习资源"><a href="#英语学习资源" class="headerlink" title="英语学习资源"></a>英语学习资源</h2><p>以下是一些适合程序员学习英语的学习资源：</p><h3 id="在线学习平台"><a href="#在线学习平台" class="headerlink" title="在线学习平台"></a>在线学习平台</h3><ul><li><strong>Codecademy</strong>：这是一个在线的交互式编程学习平台，提供多种编程语言课程。在学习编程语言的同时，注重教授与编程相关的英语技能，用户可以通过项目和任务在真实编程场景中使用英语，加深记忆。</li><li><strong>Udemy</strong>：有大量由专业人士或机构开设的编程课程，部分课程是全英文教学，课程内容丰富多样，涵盖了从基础到高级的各种编程技能和知识，可以根据自己的需求选择适合的课程来学习英语和编程。</li></ul><h3 id="技术文档和官方网站"><a href="#技术文档和官方网站" class="headerlink" title="技术文档和官方网站"></a>技术文档和官方网站</h3><ul><li><a href="https://react.dev/">react.dev</a></li><li><a href="https://reactflow.dev/">reactflow.dev</a></li><li><a href="https://www.patterns.dev/">patterns.dev</a></li><li><a href="https://nodejs.org/en/learn/getting-started/nodejs-with-typescript">nodejs.org</a></li><li><a href="https://javascript.info/">javascript.info</a></li></ul><h3 id="英语学习应用"><a href="#英语学习应用" class="headerlink" title="英语学习应用"></a>英语学习应用</h3><ul><li>百词斩：一个专注于英语学习的单词记忆应用，可以帮助你记忆单词、复习单词、查找单词、背单词等。</li><li>豆包：字节跳动大模型英语，划词翻译、收藏、AI 助教等功能很适合日常使用。</li></ul><h3 id="英文技术博客和论坛"><a href="#英文技术博客和论坛" class="headerlink" title="英文技术博客和论坛"></a>英文技术博客和论坛</h3><ul><li><strong>Stack Overflow</strong>：全球最知名的技术问答社区，程序员可以在这里找到各种技术问题的解答和讨论。阅读和参与 Stack Overflow 上的问答，能够接触到大量的专业英语表述和技术讨论，提高英语阅读和写作能力。</li><li><strong>Medium</strong>：有许多程序员和技术专家在上面分享自己的编程经验、技术见解和学习心得等文章。阅读 Medium 上的英文技术文章，可以了解行业的最新动态和技术趋势，同时提升英语阅读和理解能力。</li><li><strong>Reddit</strong> 的相关技术子论坛，如 &#x2F;r&#x2F;programming、&#x2F;r&#x2F;learnprogramming 等，程序员们可以在这些论坛上交流技术问题，阅读和参与讨论也能提升英语水平。</li></ul><h3 id="视频教程平台"><a href="#视频教程平台" class="headerlink" title="视频教程平台"></a>视频教程平台</h3><ul><li><strong>YouTube</strong>：有许多程序员上传的英文编程教程、技术分享、项目演示等视频，内容丰富多样，可以根据自己的兴趣和需求选择观看，锻炼英语听力和理解能力，同时学习编程知识。</li><li><strong>B 站</strong>：也有一些搬运或原创的英文编程教学视频，部分视频还会配有中文字幕，方便学习和理解。</li></ul><h2 id="英语学习计划"><a href="#英语学习计划" class="headerlink" title="英语学习计划"></a>英语学习计划</h2><p>英文写作可以使用人工智能解决，但是使用频次高的词汇、阅读、听力和口语就必须熟练掌握了。毕竟，借助人工智能会打断你的阅读，更别提人工智能没法在实时交流是帮上忙了。</p><ul><li>翻译英文文档一篇：锻炼词汇、阅读能力</li><li>精听一篇英文文章：锻炼听力能力</li><li>TalkAI 进行模拟面试练习</li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/e32a5cc21917/</id>
    <link href="https://blog.zisheng.pro/posts/e32a5cc21917/"/>
    <published>2024-09-11T11:34:35.000Z</published>
    <summary>本文旨在探索一个适合程序员的学习英语的方法体系。</summary>
    <title>程序员如何学习英语</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="TypeScript" scheme="https://blog.zisheng.pro/tags/TypeScript/"/>
    <category term="数据结构" scheme="https://blog.zisheng.pro/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
    <content>
      <![CDATA[<p>例如，下面是一个使用递归定义的树形结构的例子：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> <span class="title class_">TreeNode</span>&lt;T&gt; = &#123;</span><br><span class="line">  <span class="attr">value</span>: T;</span><br><span class="line">  <span class="attr">children</span>: <span class="title class_">TreeNode</span>&lt;T&gt;[];</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="attr">tree</span>: <span class="title class_">TreeNode</span>&lt;<span class="built_in">string</span>&gt; = &#123;</span><br><span class="line">  <span class="attr">value</span>: <span class="string">&quot;root&quot;</span>,</span><br><span class="line">  <span class="attr">children</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">value</span>: <span class="string">&quot;child1&quot;</span>,</span><br><span class="line">      <span class="attr">children</span>: [</span><br><span class="line">        &#123;</span><br><span class="line">          <span class="attr">value</span>: <span class="string">&quot;grandchild1&quot;</span>,</span><br><span class="line">          <span class="attr">children</span>: [],</span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">          <span class="attr">value</span>: <span class="string">&quot;grandchild2&quot;</span>,</span><br><span class="line">          <span class="attr">children</span>: [],</span><br><span class="line">        &#125;,</span><br><span class="line">      ],</span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">value</span>: <span class="string">&quot;child2&quot;</span>,</span><br><span class="line">      <span class="attr">children</span>: [],</span><br><span class="line">    &#125;,</span><br><span class="line">  ],</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>在这个例子中，我们定义了一个TreeNode类型，它包含一个值和一个子节点数组。子节点数组也是一个 TreeNode 类型，这就形成了一个递归结构。我们可以使用这个类型来描述一个树形结构，其中每个节点可以有任意数量的子节点，也可以没有子节点。</p><p>这种递归结构特性可以用于解决很多问题，例如遍历树形结构、计算树的深度、判断树是否平衡等问题。同时，递归结构也可能会导致性能问题，因此在实际使用中需要注意递归深度的限制和性能优化。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/56b3e1b7f6b1/</id>
    <link href="https://blog.zisheng.pro/posts/56b3e1b7f6b1/"/>
    <published>2024-07-10T03:21:12.000Z</published>
    <summary>TypeScript的递归结构特性是指在TypeScript中，可以使用递归定义来描述具有递归结构的数据类型。这种特性可以用于定义树形结构、嵌套数据、递归算法等问题。</summary>
    <title>TypeScript 描述树结构</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="人工智能" scheme="https://blog.zisheng.pro/categories/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/"/>
    <category term="LLM" scheme="https://blog.zisheng.pro/tags/LLM/"/>
    <category term="大语言模型" scheme="https://blog.zisheng.pro/tags/%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B/"/>
    <category term="AI" scheme="https://blog.zisheng.pro/tags/AI/"/>
    <category term="人工智能" scheme="https://blog.zisheng.pro/tags/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/"/>
    <category term="ML" scheme="https://blog.zisheng.pro/tags/ML/"/>
    <category term="机器学习" scheme="https://blog.zisheng.pro/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    <content>
      <![CDATA[<h2 id="人工智能（AI）"><a href="#人工智能（AI）" class="headerlink" title="人工智能（AI）"></a>人工智能（AI）</h2><blockquote><p>AI，全称 Artificial Intelligence，人工智能。</p></blockquote><p>人工智能是一种模拟人类智能的技术和系统。人工智能的目标是使机器能够像人类一样思考、学习、推理和解决问题。它涉及到多个领域，包括机器学习、深度学习、自然语言处理、计算机视觉等。</p><p>人工智能技术可以分为弱人工智能和强人工智能两种类型。弱人工智能是针对特定任务的智能系统，如语音识别、图像识别等。而强人工智能则是具有与人类相似的智能水平，能够在各种任务上表现出类似甚至超越人类的智能。</p><p>人工智能技术在各个领域都有广泛的应用，如医疗保健、金融、交通、教育等。在医疗领域，人工智能可用于辅助医生进行诊断和制定治疗方案；在金融领域，人工智能可用于风险管理和交易预测；在交通领域，人工智能可用于交通流量优化和自动驾驶技术；在教育领域，人工智能可用于个性化教学和学习辅助等。</p><p>随着深度学习等技术的发展，人工智能在各个领域取得了巨大进展，并为社会带来了许多便利和创新。人工智能技术的不断发展和应用将继续推动科技进步和社会发展。</p><h2 id="机器学习（ML）"><a href="#机器学习（ML）" class="headerlink" title="机器学习（ML）"></a>机器学习（ML）</h2><blockquote><p>ML，全称 Machine Learning，机器学习。</p></blockquote><p>机器学习是一门开发算法和统计模型的科学，计算机系统使用这些算法和模型，在没有明确指令的情况下，依靠既有模式和推理来执行任务。计算机系统使用机器学习算法来处理大量历史数据。并识别数据模式。还可让计算机系统根据给出的输入数据集更准确地预测结果。例如，数据科学家可以通过存储数百万的扫描图片和相应诊断来训练医学应用程序，使该程序能够根据 X 光图片诊断癌症。</p><h3 id="机器学习的工作原理是什么？"><a href="#机器学习的工作原理是什么？" class="headerlink" title="机器学习的工作原理是什么？"></a>机器学习的工作原理是什么？</h3><p>机器学习的核心思想是任意输入和输出数据组合之间的现有数学关系。机器学习模型事先不知道这种关系，但如果给出足够的数据集，它可以猜测出来。这意味着每个机器学习算法都是围绕一个可修改的数学函数构建的。基本原理可以这样理解：</p><ol><li>我们为算法提供了以下输入&#x2F;输出<code>(i,o)</code>组合来“训练”算法：<code>(2,10)</code>、<code>(5,19)</code>、<code>(9,31)</code></li><li>算法计算出输入和输出之间的关系为：<code>o=3*i+4</code></li><li>我们为算法提供输入 7，并要求算法预测输出。算法会自动确定输出位 25。</li></ol><p>虽然这只是机器学习的基本理解，但机器学习关注的是一个原则，即所有复杂的数据点都可以通过计算机系统建立数学关联，只要它们有足够的数据和计算能力来处理这些数据。因此，输出的准确度与给定的输入大小直接相关。</p><h3 id="机器学习算法有哪些类型？"><a href="#机器学习算法有哪些类型？" class="headerlink" title="机器学习算法有哪些类型？"></a>机器学习算法有哪些类型？</h3><p>机器学习包括传统机器学习和深度学习，有以下几种类型：</p><ul><li>监督学习（Supervised Learning）：每个样本都有对应的目标值，通过搭建模型实现从输入特征向量到目标值的映射，例如解决回归和分类问题。</li><li>无监督学习（Unsupervised Learning）：所有样本没有目标值，期望从数据本身发下一些潜在规律，例如解决聚类问题。</li><li>强化学习（Reinforcement Learning）：相对较为复杂，系统和外界环境不断交互，在外界反馈的基础上决定自身行为，已达到目标最优化。例如阿尔法为其和无人驾驶。</li></ul><h3 id="机器学习和人工智能的关系"><a href="#机器学习和人工智能的关系" class="headerlink" title="机器学习和人工智能的关系"></a>机器学习和人工智能的关系</h3><p>机器学习是人工智能的一个重要分支，属于实现人工智能的技术手段之一。人工智能是一门研究如何使计算机能够模拟人类智能行为的学科，而机器学习则是实现这一目标的关键方法之一。</p><p>具体来说，机器学习是让计算机系统从数据中学习并改进性能的技术，而人工智能则包括了更广泛的概念，涵盖了各种技术和方法，如专家系统、自然语言处理、计算机视觉等。机器学习作为实现人工智能的重要手段之一，通过让计算机系统从数据中学习规律和模式，使其具备类似人类智能的能力。</p><p>在实际应用中，机器学习技术被广泛应用于各种人工智能任务中，如语音识别、图像识别、自然语言处理等。通过不断优化算法和模型，机器学习使得计算机系统能够逐渐展现出类似人类智能的行为和能力，推动了人工智能领域的发展和进步。</p><h2 id="深度学习（DL）"><a href="#深度学习（DL）" class="headerlink" title="深度学习（DL）"></a>深度学习（DL）</h2><blockquote><p>DL，全称 Deep Learning，深度学习。</p></blockquote><p><strong>深度学习是机器学习的一个分支，它是一种通过模仿人类大脑的神经网络结构来实现学习和推断的技术</strong>。深度学习利用多层神经网络来学习数据的特征表示，从而可以在大规模数据集上进行复杂的模式识别和预测任务。机器学习则是一种更广泛的概念，涵盖了各种通过算法让计算机系统从数据中学习和改进性能的技术。深度学习是机器学习的一个重要分支，它在图像识别、语音识别、自然语言处理等领域取得了显著的成就。</p><h3 id="神经网络（Neural-Network）"><a href="#神经网络（Neural-Network）" class="headerlink" title="神经网络（Neural Network）"></a>神经网络（Neural Network）</h3><blockquote><p>NN，全称 Neural Network，神经网络。</p></blockquote><p>神经网络是一种受到生物神经系统启发而设计的人工智能模型，用于模拟人类大脑的学习和推断过程。神经网络由多个神经元（节点）组成，这些神经元通过连接（权重）相互关联，形成多层网络结构。</p><p>在神经网络中，通常包括输入层、隐藏层和输出层。输入层接收原始数据输入，隐藏层通过学习数据中的特征表示来提取数据的高级特征，输出层根据学习到的特征进行预测或分类。</p><p>神经网络的训练过程通常通过反向传播算法来实现。在训练过程中，神经网络通过不断调整连接权重，使得模型能够逐渐优化并学习到数据中的模式和规律。这样，神经网络就能够在给定输入数据的情况下做出准确的预测或分类。</p><h4 id="神经网络有哪些类型？"><a href="#神经网络有哪些类型？" class="headerlink" title="神经网络有哪些类型？"></a>神经网络有哪些类型？</h4><ul><li>前馈神经网络（Feedforward Neural Network，FNN）：前馈神经网络是最简单的神经网络形式，信息在网络中单向传递，不会形成循环。它包括输入层、隐藏层和输出层，常用于分类和回归任务。</li><li>卷积神经网络（Convolutional Neural Network，CNN）：卷积神经网络主要用于处理图像数据，具有共享权重和局部连接的特点，能够有效提取图像特征。CNN在计算机视觉领域取得了巨大成功。</li><li>循环神经模型（Recurrent Neural Network，RNN）：循环神经网络具有循环结构，能够处理序列数据，如自然语言处理任务。RNN具有记忆功能，能够保留先前的信息，但存在梯度消失和梯度爆炸问题。</li><li>长短期记忆网络（Long Short-Term Memory，LSTM）：LSTM 是 RNN 的一种变体，通过门控机制解决了梯度消失和梯度爆炸问题，适用于处理长序列数据。</li><li>生成对抗网络（Generative Adversarial Network，GAN）：生成对抗网络由生成器和判别器组成，通过对抗训练实现生成逼真数据的目标。GAN 在图像生成、风格迁移等任务中表现出色。</li><li>自编码器（Autoencoder）：自编码器是一种无监督学习模型，通过将输入数据压缩表示并重建输出数据来学习数据的有效表示。自编码器常用于降维和特征提取。</li></ul><h4 id="神经网络的机器学习的关系"><a href="#神经网络的机器学习的关系" class="headerlink" title="神经网络的机器学习的关系"></a>神经网络的机器学习的关系</h4><p>神经网络是机器学习的一种技术手段，属于机器学习的范畴。机器学习是一种人工智能的分支领域，旨在让计算机系统能够从数据中学习和改进性能，而神经网络则是实现这一目标的重要工具之一。</p><p>在机器学习中，除了神经网络外，还包括传统的机器学习算法，如决策树、支持向量机、K近邻等。这些算法通常用于处理结构化数据和特定任务，如分类、回归、聚类等。神经网络则更适用于处理复杂的非线性关系和大规模数据集，具有强大的表征学习能力。</p><h4 id="神经网络和深度学习的关系"><a href="#神经网络和深度学习的关系" class="headerlink" title="神经网络和深度学习的关系"></a>神经网络和深度学习的关系</h4><p>神经网络是深度学习的基础，是实现深度学习的重要工具之一。</p><p>具体来说，神经网络是受到生物神经系统启发而设计的人工智能模型，由多个神经元（节点）组成，通过连接（权重）相互关联，形成多层网络结构。深度学习通过构建多层神经网络（即深层网络）来实现对数据的高级抽象和表征，从而提高对数据的理解和预测能力。</p><p>在深度学习中，神经网络扮演着核心角色，通过多层神经元之间的连接和权重调整来实现对数据的学习和建模。深度学习技术基于神经网络结构，能够处理复杂的数据模式和关系，取得了在计算机视觉、自然语言处理、语音识别等领域的显著进展。</p><p>因此，可以说深度学习是建立在神经网络基础上的一种机器学习技术，通过构建多层神经网络结构实现对数据的高级抽象和表征，从而提高对数据的理解和预测能力。神经网络作为深度学习的基础组件，在人工智能领域发挥着重要作用。</p><h3 id="Transformer"><a href="#Transformer" class="headerlink" title="Transformer"></a>Transformer</h3><p>Transformer 是一种基于注意力机制的深度学习模型架构，最初由 Google 的研究人员提出，被广泛应用于自然语言处理领域，尤其是机器翻译任务。Transformer 模型的提出对传统的循环神经网络（RNN）和长短时记忆网络（LSTM）等模型进行了革命性的改进。</p><p>Transformer 模型的核心是自注意力机制（Self-Attention Mechanism），通过自注意力机制，模型能够同时考虑输入序列中不同位置的词语之间的关联性，从而捕捉长距离依赖关系。此外，Transformer 模型还包括位置编码（Positional Encoding）和多头注意力机制（Multi-Head Attention），使其能够更好地处理序列数据。</p><p>相比于传统的循环神经网络，Transformer 模型具有以下优势：</p><ol><li>并行计算：Transformer 能够并行计算，加快训练速度；</li><li>长距离依赖：通过自注意力机制，Transformer 能够捕捉长距离依赖关系；</li><li>更好的表征学习：Transformer 能够学习到更加抽象和有效的特征表示。</li></ol><p>Transformer 模型已经被广泛应用于自然语言处理任务，如机器翻译、文本生成、文本分类等，取得了显著的成就。其成功应用推动了深度学习领域的发展，并为处理序列数据提供了一种高效且强大的模型架构。</p><h4 id="Transformer-和神经网络的关系"><a href="#Transformer-和神经网络的关系" class="headerlink" title="Transformer 和神经网络的关系"></a>Transformer 和神经网络的关系</h4><p>Transformer 是一种基于注意力机制的深度学习模型，而神经网络是深度学习的基础组件之一。具体来说，Transformer 可以被视为一种神经网络架构，它使用注意力机制来处理序列数据，如自然语言文本。</p><p>在传统的神经网络中，信息的传递是通过循环结构实现的，存在梯度消失和梯度爆炸问题。相比之下，Transformer 利用注意力机制实现了更为高效的信息传递和建模，能够并行处理序列数据，从而提高了训练速度和模型性能。</p><p>因此，Transformer 可以被看作是一种神经网络架构的演进和创新，它在机器翻译、文本生成、语音识别等领域取得了显著成就。通过引入自注意力机制和位置编码，Transformer 模型能够更好地捕捉序列数据中的长距离依赖关系和特征表示，成为深度学习领域的重要里程碑之一。</p><h2 id="大语言模型（LLM）"><a href="#大语言模型（LLM）" class="headerlink" title="大语言模型（LLM）"></a>大语言模型（LLM）</h2><blockquote><p>LLM，全称 Large Language Model，大语言模型。</p></blockquote><p>大型语言模型（LLM）是基于大量数据进行预训练的超大型深度学习模型，旨在理解和生成人类语言。它们在海量的文本数据上进行训练，通常具有至少几十亿参数，可以执行多样性的下游任务。。底层转换器是一组神经网络，这些神经网络由具有自注意力功能的编码器和解码器组成。编码器和解码器从一系列文本中提取含义，并理解其中的单词和短语之间的关系。</p><p>Transformer 能够进行无监督的训练，但更精确的解释使转换器可以执行自主学习。通过此过程，Transformer 可学会理解基本的语法、语言和知识。</p><p>与早期按顺序处理输入的循环神经网络（RNN）不同，Transformer 并行处理整个序列。这可让数据科学家使用 GPU 训练基于 Transformer 的 LLM，从而大幅度缩短训练时间。</p><p>借助 Transformer 神经网络架构，你可使用非常大规模的模型，其中通常具有数千亿个参数。这种大规模模型可以摄取通常来自互联网的大量数据，但也可以从包含 500 多亿个网页的 Common Crawl 和拥有约 5700 万个页面的 Wikipedia 等来源摄取数据。</p><h3 id="大型语言模型有哪些应用？"><a href="#大型语言模型有哪些应用？" class="headerlink" title="大型语言模型有哪些应用？"></a>大型语言模型有哪些应用？</h3><ul><li>文案写作：除了 GPT-3 和 ChatGPT 之外，Claude、Llama 2、Cohere Command 和 Jurassic 也可编写原件。AI21 Wordspice 建议修改原始语句以改善风格和语音。</li><li>知识库回答：该技术通常称为知识密集型自然语言处理（KI-NLP），是指可以根据数字存档中的信息帮助回答特定问题的 LLM。AI21 Studio playground 能够回答常识性问题就是此类示例。</li><li>文本分类：使用集群，LLM 可以对含义或情绪相似的文本进行分类。用途包括衡量客户情绪、确定文本之间的关系和文档搜索。</li><li>代码生成：LLM 擅长根据自然语言提示生成代码。示例包括 Amazon CodeWhisperer 和 GitHub Copilot 中使用的 Open AI Codex，它们可以用 Python、JavaScript、Ruby 和其他几种编程语言编码。其他编码应用包括创建 SQL 查询、编写 Shell 命令和进行网站设计。</li><li>文本生成：与代码生成类似，文本生成可以完成不完整的语句，编写产品文档，或者像 Alexa Create 一样创作简短的儿童故事。</li></ul><h3 id="GPT"><a href="#GPT" class="headerlink" title="GPT"></a>GPT</h3><blockquote><p>GPT 全称 Generative Pre-trained Transformer，生成预训练转换器。</p></blockquote><p>GPT 是由 OpenAI 开发的一系列基于 Transformer 架构的大型语言模型。GPT 模型采用了自回归的方式进行训练，能够生成文本、回答问题、完成翻译等自然语言处理任务。</p><h3 id="开源-LLM"><a href="#开源-LLM" class="headerlink" title="开源 LLM"></a>开源 LLM</h3><ul><li>通义千问：通义千问-7B（Qwen-7B） 是阿里云研发的通义千问大模型系列的70亿参数规模的模型。Qwen-7B是基于Transformer的大语言模型, 在超大规模的预训练数据上进行训练得到。预训练数据类型多样，覆盖广泛，包括大量网络文本、专业书籍、代码等。同时，在Qwen-7B的基础上，使用对齐机制打造了基于大语言模型的AI助手Qwen-7B-Chat。</li><li>智谱·AI：ChatGLM-6B 是一个开源的、支持中英双语问答的对话语言模型，基于 General Language Model (GLM) 架构，具有 62 亿参数</li></ul><h2 id="AIGC-Generative-AI"><a href="#AIGC-Generative-AI" class="headerlink" title="AIGC&#x2F;Generative AI"></a>AIGC&#x2F;Generative AI</h2><blockquote><p>AIGC，全称 Artificial Intelligence Generative Content，人工智能生成内容。<br>Generative AI，生成式 AI。</p></blockquote><p>AIGC 又称生成式 AI (Generative AI)，是继专业生产内容（PGC， Professional-generated Content）、用户生产内容（UGC， User-generated Content）之后的新型内容创作方式，可以在对话、故事、图像、视频和音乐制作等方面，打造新的数字内容生成与交互形式。</p><p>与所有人工智能技术一样，AIGC 的能力由机器学习模型提供，这些模型是基于大量数据进行预先训练的大模型，通常被称为基础模型（Foundation Models）。如今以基础模型为驱动的 AIGC 应用迭代速度呈现指数级发展，从由 Stable Diffusion 文生图模型驱动的 AI 作画应用，再到以大语言模型（LLM）驱动的智能聊天机器人，深度学习模型不断完善、开源预训练基础模型的推动以及大模型探索商业化的可能，都在成为这场人工智能颠覆性革命的主要驱动力。</p><h2 id="论文摘要"><a href="#论文摘要" class="headerlink" title="论文摘要"></a>论文摘要</h2><h3 id="《Neural-Machine-Translation-by-Jointly-Learning-to-Align-and-Translate》"><a href="#《Neural-Machine-Translation-by-Jointly-Learning-to-Align-and-Translate》" class="headerlink" title="《Neural Machine Translation by Jointly Learning to Align and Translate》"></a>《Neural Machine Translation by Jointly Learning to Align and Translate》</h3><p>《基于联合学习对齐和翻译的神经机器翻译》是一篇经典的机器翻译论文，由 Google Brain 团队的研究人员 Bahdanau（ [bɑːdɑˈnoʊ]）等人于 2014 年提出。该论文提出了一种新颖的神经机器翻译模型，引入了注意力机制，极大地改进了传统的基于统计机器翻译的方法。</p><p>该论文提出了一种称为 Seq2Seq 的神经网络架构，用于进行机器翻译任务。该架构包括编码器和解码器两部分，通过将源语言句子编码为固定长度的向量表示，然后将此向量解码为目标语言句子。与传统的固定长度短语表示不同，该模型能够根据输入序列中的不同部分动态地调整注意力，以便更好地对齐源语言和目标语言之间的单词。</p><p>通过引入注意力机制，该模型能够在生成目标语言句子的每个单词时动态地关注源语言句子的不同部分，从而更好地处理长距离依赖关系和词序问题。实验结果表明，这种基于注意力机制的神经机器翻译模型在翻译质量和效率上都取得了显著的提升，成为了后续神经机器翻译模型发展的重要里程碑。</p><p>这篇论文为后续的神经机器翻译研究奠定了基础，并启发了许多后续模型的发展，如 Transformer 等。</p><h3 id="《Attention-is-All-You-Need》"><a href="#《Attention-is-All-You-Need》" class="headerlink" title="《Attention is All You Need》"></a>《Attention is All You Need》</h3><p>《Attention is All You Need》是一篇由 Google Brain 团队提出的论文，介绍了 Transformer 模型，这是一种基于注意力机制的深度学习模型。</p><p>Transformer 模型摒弃了传统的循环神经网络（RNN）和长短期记忆网络（LSTM），完全基于自注意力机制来处理序列数据。该模型通过引入多头注意力机制和位置编码，实现了并行计算、更好的序列建模能力以及更快的训练速度。Transformer 模型在机器翻译等自然语言处理任务中取得了优异的性能，证明了注意力机制在序列建模中的有效性。</p><p>该论文的提出对深度学习领域产生了深远影响，激发了后续许多基于 Transformer 架构的模型的发展，如 BERT、GPT 等。Transformer 模型的成功证明了注意力机制在处理序列数据时的重要性，为自然语言处理领域带来了革命性的变革。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://aws.amazon.com/cn/what-is/artificial-intelligence/">什么是人工智能？</a></li><li><a href="https://aws.amazon.com/cn/what-is/machine-learning/">什么是机器学习？</a></li><li><a href="https://aws.amazon.com/cn/what-is/deep-learning/">什么是深度学习？</a></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/456d17dc39c7/</id>
    <link href="https://blog.zisheng.pro/posts/456d17dc39c7/"/>
    <published>2024-04-05T15:12:43.000Z</published>
    <summary>本文介绍了人工智能领域的各种概念，旨在建立起人工智能知识架构，帮助大家更好地理解人工智能</summary>
    <title>人工智能概念学习</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="算法" scheme="https://blog.zisheng.pro/categories/%E7%AE%97%E6%B3%95/"/>
    <category term="计划" scheme="https://blog.zisheng.pro/tags/%E8%AE%A1%E5%88%92/"/>
    <content>
      <![CDATA[<h2 id="第一阶段"><a href="#第一阶段" class="headerlink" title="第一阶段"></a>第一阶段</h2><h3 id="基础知识学习"><a href="#基础知识学习" class="headerlink" title="基础知识学习"></a>基础知识学习</h3><ul><li>学习基本数据结构（数组、链表、栈、队列、树等）和基本算法（排序、查找、递归等）。</li><li>刷题巩固基础，解决大量简单和中等难度的算法问题。</li></ul><h3 id="算法思想学习"><a href="#算法思想学习" class="headerlink" title="算法思想学习"></a>算法思想学习</h3><ul><li>深入学习常用算法思想，如贪心算法、动态规划、回溯算法等。</li><li>练习相关算法题目，加深理解和掌握应用。</li></ul><h3 id="LeetCode刷题阶段"><a href="#LeetCode刷题阶段" class="headerlink" title="LeetCode刷题阶段"></a>LeetCode刷题阶段</h3><ul><li>刷LeetCode题库，重点解决中等和困难难度的算法问题。</li><li>注重多种解题思路的探索，提升解决问题的能力和速度。</li></ul><h2 id="第二阶段"><a href="#第二阶段" class="headerlink" title="第二阶段"></a>第二阶段</h2><h3 id="算法进阶与实战"><a href="#算法进阶与实战" class="headerlink" title="算法进阶与实战"></a>算法进阶与实战</h3><ul><li>学习高级数据结构（图、堆、并查集等）和高级算法（网络流、字符串匹配等）。</li><li>解决复杂算法问题，注重优化算法效率和空间复杂度。</li></ul><h3 id="项目实践与总结"><a href="#项目实践与总结" class="headerlink" title="项目实践与总结"></a>项目实践与总结</h3><ul><li>将所学算法应用到实际项目中，解决实际问题。</li><li>总结学习经验，复习重要知识点，填补知识漏洞。</li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/06239a00ff4d/</id>
    <link href="https://blog.zisheng.pro/posts/06239a00ff4d/"/>
    <published>2024-03-13T14:56:33.000Z</published>
    <summary>算法学习计划</summary>
    <title>算法学习计划</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="全栈开发" scheme="https://blog.zisheng.pro/categories/%E5%85%A8%E6%A0%88%E5%BC%80%E5%8F%91/"/>
    <category term="JavaScript" scheme="https://blog.zisheng.pro/tags/JavaScript/"/>
    <category term="Puppeteer" scheme="https://blog.zisheng.pro/tags/Puppeteer/"/>
    <content>
      <![CDATA[<h2 id="拦截所有图片资源"><a href="#拦截所有图片资源" class="headerlink" title="拦截所有图片资源"></a>拦截所有图片资源</h2><p>在 Puppeteer 中，如果你想禁止加载图片，可以通过拦截请求并阻止图片资源类型的请求来实现。这可以通过使用 <code>page.setRequestInterception(true)</code> 来启用请求拦截，然后在 <code>request</code> 事件监听器中检查请求类型，如果是图片，则调用 <code>request.abort()</code> 方法。</p><p>以下是一个示例代码，展示了如何在 Puppeteer 中禁止加载图片：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> puppeteer = <span class="built_in">require</span>(<span class="string">&#x27;puppeteer&#x27;</span>);</span><br><span class="line"></span><br><span class="line">(<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> browser = <span class="keyword">await</span> puppeteer.<span class="title function_">launch</span>();</span><br><span class="line">  <span class="keyword">const</span> page = <span class="keyword">await</span> browser.<span class="title function_">newPage</span>();</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 启用请求拦截</span></span><br><span class="line">  <span class="keyword">await</span> page.<span class="title function_">setRequestInterception</span>(<span class="literal">true</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 添加请求拦截器</span></span><br><span class="line">  page.<span class="title function_">on</span>(<span class="string">&#x27;request&#x27;</span>, <span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 如果请求的资源类型是图片，则阻止该请求</span></span><br><span class="line">    <span class="keyword">if</span> (request.<span class="title function_">resourceType</span>() === <span class="string">&#x27;image&#x27;</span>) &#123;</span><br><span class="line">      request.<span class="title function_">abort</span>();</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      request.<span class="title function_">continue</span>();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 导航到页面</span></span><br><span class="line">  <span class="keyword">await</span> page.<span class="title function_">goto</span>(<span class="string">&#x27;https://example.com&#x27;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 其他操作...</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">await</span> browser.<span class="title function_">close</span>();</span><br><span class="line">&#125;)();</span><br></pre></td></tr></table></figure><p>在这段代码中，我们首先通过 <code>setRequestInterception(true)</code> 启用了请求拦截。然后，我们监听 <code>request</code> 事件，并检查每个请求的资源类型。如果资源类型是 <code>image</code>，我们就调用 <code>request.abort()</code> 来阻止这个请求。对于其他类型的请求，我们调用 <code>request.continue()</code> 来允许请求继续。</p><p>这种方法可以有效减少页面加载时间和带宽使用，特别是在你不需要处理图片数据时。</p><h2 id="拦截指定域名的图片资源"><a href="#拦截指定域名的图片资源" class="headerlink" title="拦截指定域名的图片资源"></a>拦截指定域名的图片资源</h2><p>在 Puppeteer 中，如果你想禁止加载来自特定域名的图片，你可以在启用请求拦截后，检查请求的 URL，并根据域名决定是否要阻止该请求。以下是一个示例代码，展示了如何根据域名禁止加载图片：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> puppeteer = <span class="built_in">require</span>(<span class="string">&#x27;puppeteer&#x27;</span>);</span><br><span class="line"></span><br><span class="line">(<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> browser = <span class="keyword">await</span> puppeteer.<span class="title function_">launch</span>();</span><br><span class="line">  <span class="keyword">const</span> page = <span class="keyword">await</span> browser.<span class="title function_">newPage</span>();</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 启用请求拦截</span></span><br><span class="line">  <span class="keyword">await</span> page.<span class="title function_">setRequestInterception</span>(<span class="literal">true</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 添加请求拦截器</span></span><br><span class="line">  page.<span class="title function_">on</span>(<span class="string">&#x27;request&#x27;</span>, <span class="function"><span class="params">request</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 获取请求的 URL</span></span><br><span class="line">    <span class="keyword">const</span> url = request.<span class="title function_">url</span>();</span><br><span class="line">    <span class="comment">// 解析 URL 以获取域名</span></span><br><span class="line">    <span class="keyword">const</span> hostname = <span class="keyword">new</span> <span class="title function_">URL</span>(url).<span class="property">hostname</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 检查请求是否为图片以及域名是否为特定域名</span></span><br><span class="line">    <span class="keyword">if</span> (request.<span class="title function_">resourceType</span>() === <span class="string">&#x27;image&#x27;</span> &amp;&amp; hostname === <span class="string">&#x27;example.com&#x27;</span>) &#123;</span><br><span class="line">      <span class="comment">// 阻止来自特定域名的图片请求</span></span><br><span class="line">      request.<span class="title function_">abort</span>();</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="comment">// 允许其他请求</span></span><br><span class="line">      request.<span class="title function_">continue</span>();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 导航到页面</span></span><br><span class="line">  <span class="keyword">await</span> page.<span class="title function_">goto</span>(<span class="string">&#x27;https://example.com&#x27;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 其他操作...</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">await</span> browser.<span class="title function_">close</span>();</span><br><span class="line">&#125;)();</span><br></pre></td></tr></table></figure><p>在这个例子中，我们首先通过 <code>setRequestInterception(true)</code> 启用了请求拦截。然后，我们监听 <code>request</code> 事件，对于每个请求，我们获取它的 URL 并解析出域名。如果请求的资源类型是 <code>image</code> 并且域名与我们指定的域名匹配，我们就调用 <code>request.abort()</code> 来阻止这个请求。对于其他类型的请求或来自其他域名的请求，我们调用 <code>request.continue()</code> 来允许请求继续。</p><p>请确保将 <code>&#39;example.com&#39;</code> 替换为你想要阻止图片加载的特定域名。这种方法可以帮助你减少不必要的网络流量，特别是当你只想屏蔽来自某些域名的图片时。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/020a842650e4/</id>
    <link href="https://blog.zisheng.pro/posts/020a842650e4/"/>
    <published>2024-03-07T09:29:02.000Z</published>
    <summary>在 Puppeteer 中，如果你想禁止加载图片，可以通过拦截请求并阻止图片资源类型的请求来实现。这可以通过使用 page.setRequestInterception(true) 来启用请求拦截，然后在 request 事件监听器中检查请求类型，如果是图片，则调用 request.abort() 方法。</summary>
    <title>Puppeteer 学习笔记（示例）: 禁止加载图片资源</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="JavaScript" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/JavaScript/"/>
    <category term="Promise" scheme="https://blog.zisheng.pro/tags/Promise/"/>
    <category term="setTimeout" scheme="https://blog.zisheng.pro/tags/setTimeout/"/>
    <content>
      <![CDATA[<p>在 JavaScript 中实现一个延迟函数（通常称为 <code>delay</code> 或 <code>sleep</code> 函数）非常简单，你可以使用 Promise 和 <code>setTimeout</code> 函数来实现。以下是一个 <code>delay</code> 函数的示例：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">delay</span>(<span class="params">time</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function"><span class="params">resolve</span> =&gt;</span> <span class="built_in">setTimeout</span>(resolve, time));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用方法</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">run</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Delay start&#x27;</span>);</span><br><span class="line">  <span class="keyword">await</span> <span class="title function_">delay</span>(<span class="number">2000</span>); <span class="comment">// 延迟 2000 毫秒（2秒）</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Delay end&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">run</span>();</span><br></pre></td></tr></table></figure><p>在这个例子中，<code>delay</code> 函数接收一个时间参数（以毫秒为单位），然后返回一个 Promise。这个 Promise 将在指定的时间后通过 <code>setTimeout</code> 函数解决。在 <code>run</code> 函数中，我们使用 <code>await</code> 关键字等待 <code>delay</code> 函数，这样代码的执行就会在这里暂停指定的时间。</p><p>你可以根据需要调用 <code>delay</code> 函数来在任何地方添加延迟。这在模拟网络请求、动画等场景中非常有用。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/d5391f511e4c/</id>
    <link href="https://blog.zisheng.pro/posts/d5391f511e4c/"/>
    <published>2024-03-07T09:27:07.000Z</published>
    <summary>在 JavaScript 中实现一个延迟函数（通常称为 delay 或 sleep 函数）非常简单，你可以使用 Promise 和 setTimeout 函数来实现。</summary>
    <title>在 JavaScript 中实现一个延迟函数</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="全栈开发" scheme="https://blog.zisheng.pro/categories/%E5%85%A8%E6%A0%88%E5%BC%80%E5%8F%91/"/>
    <category term="Puppeteer" scheme="https://blog.zisheng.pro/tags/Puppeteer/"/>
    <category term="爬虫" scheme="https://blog.zisheng.pro/tags/%E7%88%AC%E8%99%AB/"/>
    <category term="Node" scheme="https://blog.zisheng.pro/tags/Node/"/>
    <content>
      <![CDATA[<p>在 Puppeteer 中，”tab”通常指的是浏览器窗口中的页面（Page）。如果你想同时打开多个页面（即多个tab），然后依次在它们之间切换，你可以按照以下步骤进行：</p><ol><li>创建多个页面对象。</li><li>同时打开所有页面。</li><li>依次切换到每个页面并执行操作。</li></ol><p>以下是一个示例代码，展示了如何同时打开多个页面，并在它们都加载完成后依次切换和操作：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> puppeteer = <span class="built_in">require</span>(<span class="string">&#x27;puppeteer&#x27;</span>);</span><br><span class="line"></span><br><span class="line">(<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="comment">// 启动浏览器</span></span><br><span class="line">  <span class="keyword">const</span> browser = <span class="keyword">await</span> puppeteer.<span class="title function_">launch</span>(&#123; <span class="attr">headless</span>: <span class="literal">false</span> &#125;); <span class="comment">// 设置为非无头模式以便看到浏览器标签切换</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 定义要访问的 URL 列表</span></span><br><span class="line">  <span class="keyword">const</span> urls = [<span class="string">&#x27;https://example.com&#x27;</span>, <span class="string">&#x27;https://example.org&#x27;</span>, <span class="string">&#x27;https://example.net&#x27;</span>];</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 同时创建并打开所有页面</span></span><br><span class="line">  <span class="keyword">const</span> pages = <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">all</span>(urls.<span class="title function_">map</span>(<span class="keyword">async</span> (url) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> page = <span class="keyword">await</span> browser.<span class="title function_">newPage</span>();</span><br><span class="line">    <span class="keyword">await</span> page.<span class="title function_">goto</span>(url);</span><br><span class="line">    <span class="keyword">return</span> page;</span><br><span class="line">  &#125;));</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 依次切换到每个页面并执行操作</span></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> page <span class="keyword">of</span> pages) &#123;</span><br><span class="line">    <span class="comment">// 将焦点切换到当前页面</span></span><br><span class="line">    <span class="keyword">await</span> page.<span class="title function_">bringToFront</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 在这里可以执行页面操作，例如截图、提取页面内容等</span></span><br><span class="line">    <span class="comment">// 示例：打印当前页面的 URL</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="keyword">await</span> page.<span class="title function_">url</span>());</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 你可以在这里加入随机停留时间</span></span><br><span class="line">    <span class="keyword">const</span> randomDelay = <span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title class_">Math</span>.<span class="title function_">random</span>() * (<span class="number">180000</span> - <span class="number">60000</span> + <span class="number">1</span>)) + <span class="number">60000</span>;</span><br><span class="line">    <span class="keyword">await</span> page.<span class="title function_">waitForTimeout</span>(randomDelay);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 执行完操作后，可以选择关闭当前页面或保持打开状态</span></span><br><span class="line">    <span class="comment">// await page.close();</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 如果你没有关闭每个页面，可以在这里关闭浏览器</span></span><br><span class="line">  <span class="comment">// await browser.close();</span></span><br><span class="line">&#125;)();</span><br></pre></td></tr></table></figure><p>在这个脚本中：</p><ul><li><code>browser.newPage()</code> 用于创建新页面。</li><li><code>page.goto(url)</code> 用于导航到指定的 URL。</li><li><code>page.bringToFront()</code> 用于将当前页面切换到前台，这样你就可以模拟用户在不同的标签页之间切换。</li></ul><p>请注意，如果你不关闭每个页面，那么在脚本的最后你应该关闭浏览器，以释放资源。如果你关闭了每个页面，那么关闭浏览器的步骤可以省略，因为当最后一个页面关闭时，Puppeteer 通常会自动关闭浏览器。</p><p>此外，由于这个脚本是在非无头模式下运行的（<code>headless: false</code>），你可以看到浏览器窗口和标签页的切换。如果你不需要看到浏览器界面，可以将其设置为无头模式（<code>headless: true</code>），这通常会稍微提高性能。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/2cb484464818/</id>
    <link href="https://blog.zisheng.pro/posts/2cb484464818/"/>
    <published>2024-03-07T09:25:16.000Z</published>
    <summary>在 Puppeteer 中，&quot;tab&quot;通常指的是浏览器窗口中的页面（Page）。如果你想同时打开多个页面（即多个tab），然后依次在它们之间切换，你可以按照以下步骤进行。</summary>
    <title>Puppeteer 学习笔记（示例）: 打开多个页面并依次切换tab</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="全栈开发" scheme="https://blog.zisheng.pro/categories/%E5%85%A8%E6%A0%88%E5%BC%80%E5%8F%91/"/>
    <category term="Puppeteer" scheme="https://blog.zisheng.pro/tags/Puppeteer/"/>
    <category term="学习笔记" scheme="https://blog.zisheng.pro/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <content>
      <![CDATA[<p>在 Puppeteer 中，要打开一个页面并滑动到底部，你可以使用页面提供的滚动功能。这通常通过执行页面上下文中的 JavaScript 代码来实现。以下是一个示例，展示了如何使用 Puppeteer 打开一个页面并滚动到底部：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> puppeteer = <span class="built_in">require</span>(<span class="string">&#x27;puppeteer&#x27;</span>);</span><br><span class="line"></span><br><span class="line">(<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="comment">// 启动浏览器</span></span><br><span class="line">  <span class="keyword">const</span> browser = <span class="keyword">await</span> puppeteer.<span class="title function_">launch</span>();</span><br><span class="line">  <span class="comment">// 创建一个新页面</span></span><br><span class="line">  <span class="keyword">const</span> page = <span class="keyword">await</span> browser.<span class="title function_">newPage</span>();</span><br><span class="line">  <span class="comment">// 导航到指定的 URL</span></span><br><span class="line">  <span class="keyword">await</span> page.<span class="title function_">goto</span>(<span class="string">&#x27;https://example.com&#x27;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 滑动到页面底部的函数</span></span><br><span class="line">  <span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">scrollToBottom</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">await</span> page.evaluate(<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">      <span class="keyword">await</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">var</span> totalHeight = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">var</span> distance = <span class="number">100</span>; <span class="comment">// 每次向下滚动的距离</span></span><br><span class="line">        <span class="keyword">var</span> timer = <span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">          <span class="keyword">var</span> scrollHeight = <span class="variable language_">document</span>.<span class="property">body</span>.<span class="property">scrollHeight</span>;</span><br><span class="line">          <span class="variable language_">window</span>.<span class="title function_">scrollBy</span>(<span class="number">0</span>, distance);</span><br><span class="line">          totalHeight += distance;</span><br><span class="line"></span><br><span class="line">          <span class="keyword">if</span> (totalHeight &gt;= scrollHeight) &#123;</span><br><span class="line">            <span class="built_in">clearInterval</span>(timer);</span><br><span class="line">            <span class="title function_">resolve</span>();</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;, <span class="number">100</span>);</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 调用滑动到页面底部的函数</span></span><br><span class="line">  <span class="keyword">await</span> <span class="title function_">scrollToBottom</span>();</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 在这里可以执行其他页面操作，例如截图、提取页面内容等</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 关闭页面</span></span><br><span class="line">  <span class="keyword">await</span> page.<span class="title function_">close</span>();</span><br><span class="line">  <span class="comment">// 关闭浏览器</span></span><br><span class="line">  <span class="keyword">await</span> browser.<span class="title function_">close</span>();</span><br><span class="line">&#125;)();</span><br></pre></td></tr></table></figure><p>在这个脚本中，<code>scrollToBottom</code> 函数通过设置一个定时器来模拟用户滚动行为，每隔一段时间滚动一小段距离，直到达到页面底部。这种方法可以模拟真实用户的滚动行为，对于需要加载懒加载内容（如图片或者无限滚动的社交媒体帖子）的页面尤其有用。</p><p>请注意，滚动到底部的速度和每次滚动的距离可以根据页面的实际情况进行调整。如果页面有很多懒加载内容，可能需要减慢滚动速度或增加每次滚动的距离，以确保内容能够正确加载。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/d86e73bee997/</id>
    <link href="https://blog.zisheng.pro/posts/d86e73bee997/"/>
    <published>2024-03-07T02:47:39.000Z</published>
    <summary>在 Puppeteer 中，要打开一个页面并滑动到底部，你可以使用页面提供的滚动功能。这通常通过执行页面上下文中的 JavaScript 代码来实现。本文是介绍了一个示例，展示了如何使用 Puppeteer 打开一个页面并滚动到底部</summary>
    <title>Puppeteer 学习笔记（示例）: 打开页面并滑动到底部</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="全栈开发" scheme="https://blog.zisheng.pro/categories/%E5%85%A8%E6%A0%88%E5%BC%80%E5%8F%91/"/>
    <category term="Puppeteer" scheme="https://blog.zisheng.pro/tags/Puppeteer/"/>
    <category term="学习笔记" scheme="https://blog.zisheng.pro/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <content>
      <![CDATA[<p>要使用 Puppeteer 并行打开多个页面，你可以使用 <code>Promise.all()</code> 来同时处理多个页面。这样，你可以在同一个浏览器实例中并行打开多个标签页。下面是一个示例代码，展示了如何实现这一点：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> puppeteer = <span class="built_in">require</span>(<span class="string">&#x27;puppeteer&#x27;</span>);</span><br><span class="line"></span><br><span class="line">(<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="comment">// 启动浏览器</span></span><br><span class="line">  <span class="keyword">const</span> browser = <span class="keyword">await</span> puppeteer.<span class="title function_">launch</span>();</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 定义要访问的 URL 列表</span></span><br><span class="line">  <span class="keyword">const</span> urls = [<span class="string">&#x27;https://example.com&#x27;</span>, <span class="string">&#x27;https://example.org&#x27;</span>, <span class="string">&#x27;https://example.net&#x27;</span>];</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 使用 map() 方法将每个 URL 映射到打开页面的 Promise</span></span><br><span class="line">  <span class="keyword">const</span> pagePromises = urls.<span class="title function_">map</span>(<span class="keyword">async</span> (url) =&gt; &#123;</span><br><span class="line">    <span class="comment">// 创建一个新页面</span></span><br><span class="line">    <span class="keyword">const</span> page = <span class="keyword">await</span> browser.<span class="title function_">newPage</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 导航到指定的 URL</span></span><br><span class="line">    <span class="keyword">await</span> page.<span class="title function_">goto</span>(url);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 在这里可以执行其他页面操作，例如截图、提取页面内容等</span></span><br><span class="line">    <span class="comment">// 例如：await page.screenshot(&#123; path: `screenshot-$&#123;url&#125;.png` &#125;);</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 关闭页面</span></span><br><span class="line">    <span class="keyword">await</span> page.<span class="title function_">close</span>();</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 使用 Promise.all() 等待所有页面的 Promise 完成</span></span><br><span class="line">  <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">all</span>(pagePromises);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 所有页面处理完毕后关闭浏览器</span></span><br><span class="line">  <span class="keyword">await</span> browser.<span class="title function_">close</span>();</span><br><span class="line">&#125;)();</span><br></pre></td></tr></table></figure><p>在这个脚本中，<code>urls.map()</code> 会为每个 URL 创建一个新的 Promise，这些 Promise 会开始执行页面的打开和操作过程。<code>Promise.all()</code> 会等待所有这些 Promise 完成，这意味着所有页面的操作都是并行执行的。</p><p>请注意，虽然这种方法可以提高效率，但是如果同时打开的页面太多，可能会消耗大量的系统资源，导致性能下降。因此，如果要处理大量页面，可能需要实现一种队列或者限制并发数量的机制。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/a581be17d009/</id>
    <link href="https://blog.zisheng.pro/posts/a581be17d009/"/>
    <published>2024-03-07T02:44:16.000Z</published>
    <summary>要使用 Puppeteer 并行打开多个页面，你可以使用 `Promise.all()` 来同时处理多个页面。这样，你可以在同一个浏览器实例中并行打开多个标签页。</summary>
    <title>Puppeteer 学习笔记（示例）: 并行打开多个页面</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="全栈开发" scheme="https://blog.zisheng.pro/categories/%E5%85%A8%E6%A0%88%E5%BC%80%E5%8F%91/"/>
    <category term="Puppeteer" scheme="https://blog.zisheng.pro/tags/Puppeteer/"/>
    <category term="学习笔记" scheme="https://blog.zisheng.pro/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <content>
      <![CDATA[<p>在 Puppeteer 中，你可以使用 <code>browser.newPage()</code> 方法来打开多个页面（也称为标签页或 tabs）。以下是一个基本的示例，展示了如何使用 Puppeteer 打开多个页面并逐个访问不同的 URL：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> puppeteer = <span class="built_in">require</span>(<span class="string">&#x27;puppeteer&#x27;</span>);</span><br><span class="line"></span><br><span class="line">(<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="comment">// 启动浏览器</span></span><br><span class="line">  <span class="keyword">const</span> browser = <span class="keyword">await</span> puppeteer.<span class="title function_">launch</span>();</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 定义要访问的 URL 列表</span></span><br><span class="line">  <span class="keyword">const</span> urls = [<span class="string">&#x27;https://example.com&#x27;</span>, <span class="string">&#x27;https://example.org&#x27;</span>, <span class="string">&#x27;https://example.net&#x27;</span>];</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 遍历 URL 列表，为每个 URL 打开一个新页面</span></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> url <span class="keyword">of</span> urls) &#123;</span><br><span class="line">    <span class="comment">// 创建一个新页面</span></span><br><span class="line">    <span class="keyword">const</span> page = <span class="keyword">await</span> browser.<span class="title function_">newPage</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 导航到指定的 URL</span></span><br><span class="line">    <span class="keyword">await</span> page.<span class="title function_">goto</span>(url);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 在这里可以执行其他页面操作，例如截图、提取页面内容等</span></span><br><span class="line">    <span class="comment">// 例如：await page.screenshot(&#123; path: `screenshot-$&#123;url&#125;.png` &#125;);</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 关闭页面</span></span><br><span class="line">    <span class="keyword">await</span> page.<span class="title function_">close</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 所有页面访问完毕后关闭浏览器</span></span><br><span class="line">  <span class="keyword">await</span> browser.<span class="title function_">close</span>();</span><br><span class="line">&#125;)();</span><br></pre></td></tr></table></figure><p>这个脚本首先启动了一个新的浏览器实例，然后遍历一个包含 URL 的数组，为每个 URL 创建一个新页面，并导航到相应的地址。在每个页面上，你可以执行所需的操作，比如截图、页面内容分析等。完成操作后，脚本会关闭当前页面，并在处理完所有页面后关闭浏览器实例。</p><p>请注意，这个脚本是按顺序打开和处理每个页面的。如果你想同时打开多个页面，你可以创建所有页面的 Promise，然后使用 <code>Promise.all()</code> 来等待所有页面操作完成。但是，这样做可能会对计算机的性能造成较大压力，尤其是当打开的页面数量较多时。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/7c063e00d3c1/</id>
    <link href="https://blog.zisheng.pro/posts/7c063e00d3c1/"/>
    <published>2024-03-07T02:35:27.000Z</published>
    <summary>在 Puppeteer 中，你可以使用 `browser.newPage()` 方法来打开多个页面（也称为标签页或 tabs）</summary>
    <title>Puppeteer 学习笔记（示例）: 逐个打开页面并滑动到底部</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="全栈开发" scheme="https://blog.zisheng.pro/categories/%E5%85%A8%E6%A0%88%E5%BC%80%E5%8F%91/"/>
    <category term="前端" scheme="https://blog.zisheng.pro/tags/%E5%89%8D%E7%AB%AF/"/>
    <category term="puppeteer" scheme="https://blog.zisheng.pro/tags/puppeteer/"/>
    <category term="nodejs" scheme="https://blog.zisheng.pro/tags/nodejs/"/>
    <content>
      <![CDATA[<h2 id="Puppeteer-是什么？"><a href="#Puppeteer-是什么？" class="headerlink" title="Puppeteer 是什么？"></a>Puppeteer 是什么？</h2><p>Puppeteer 是一个 Node 库，它提供了一个高级 API 来通过 DevTools 协议控制 Chromium 或 Chrome。Puppeteer 默认以 headless 模式运行，但是可以通过修改配置文件运行“有头”模式。</p><h2 id="Puppeteer-能做什么"><a href="#Puppeteer-能做什么" class="headerlink" title="Puppeteer 能做什么?"></a>Puppeteer 能做什么?</h2><p>你可以在浏览器中手动执行的绝大多数操作都可以使用 Puppeteer 来完成！ 下面是一些示例：</p><ul><li>生成页面 PDF。</li><li>抓取 SPA（单页应用）并生成预渲染内容（即“SSR”（服务器端渲染））。</li><li>自动提交表单，进行 UI 测试，键盘输入等。</li><li>创建一个时时更新的自动化测试环境。 使用最新的 JavaScript 和浏览器功能直接在最新版本的Chrome中执行测试。</li><li>捕获网站的 timeline trace，用来帮助分析性能问题。</li><li>测试浏览器扩展。</li></ul><h2 id="如何安装-puppeteer？"><a href="#如何安装-puppeteer？" class="headerlink" title="如何安装 puppeteer？"></a>如何安装 puppeteer？</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install puppeteer</span><br></pre></td></tr></table></figure><h2 id="使用-puppeteer"><a href="#使用-puppeteer" class="headerlink" title="使用 puppeteer"></a>使用 puppeteer</h2><p>Puppeteer 使用起来和其他测试框架类似。你需要创建一个 Browser 实例，打开页面，然后使用 Puppeteer 的 API。</p><p>Example - 跳转到 <a href="https://example.com/">https://example.com</a> 并保存截图至 example.png:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// example.js</span></span><br><span class="line"><span class="keyword">const</span> puppeteer = <span class="built_in">require</span>(<span class="string">&#x27;puppeteer&#x27;</span>);</span><br><span class="line"></span><br><span class="line">(<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> browser = <span class="keyword">await</span> puppeteer.<span class="title function_">launch</span>();</span><br><span class="line">  <span class="keyword">const</span> page = <span class="keyword">await</span> browser.<span class="title function_">newPage</span>();</span><br><span class="line">  <span class="keyword">await</span> page.<span class="title function_">goto</span>(<span class="string">&#x27;https://www.otemeta.com&#x27;</span>);</span><br><span class="line">  <span class="keyword">await</span> page.<span class="title function_">screenshot</span>(&#123;<span class="attr">path</span>: <span class="string">&#x27;otemeta.png&#x27;</span>&#125;);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">await</span> browser.<span class="title function_">close</span>();</span><br><span class="line">&#125;)();</span><br></pre></td></tr></table></figure><p>在命令行中执行：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">node example.<span class="property">js</span></span><br></pre></td></tr></table></figure><h2 id="默认设置"><a href="#默认设置" class="headerlink" title="默认设置"></a>默认设置</h2><h3 id="使用有头模式"><a href="#使用有头模式" class="headerlink" title="使用有头模式"></a>使用有头模式</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> puppeteer = <span class="built_in">require</span>(<span class="string">&#x27;puppeteer&#x27;</span>);</span><br><span class="line"></span><br><span class="line">(<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> browser = <span class="keyword">await</span> puppeteer.<span class="title function_">launch</span>(&#123; <span class="attr">headless</span>: <span class="literal">false</span> &#125;);</span><br><span class="line">  <span class="keyword">const</span> page = <span class="keyword">await</span> browser.<span class="title function_">newPage</span>();</span><br><span class="line">  <span class="keyword">await</span> page.<span class="title function_">goto</span>(<span class="string">&#x27;https://www.otemeta.com&#x27;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">await</span> browser.<span class="title function_">close</span>();</span><br><span class="line">&#125;)();</span><br></pre></td></tr></table></figure><h3 id="默认视口"><a href="#默认视口" class="headerlink" title="默认视口"></a>默认视口</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> puppeteer = <span class="built_in">require</span>(<span class="string">&#x27;puppeteer&#x27;</span>);</span><br><span class="line"></span><br><span class="line">(<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> browser = <span class="keyword">await</span> puppeteer.<span class="title function_">launch</span>(&#123;</span><br><span class="line">    <span class="attr">headless</span>: <span class="literal">false</span>,</span><br><span class="line">    <span class="attr">defaultViewport</span>: &#123;</span><br><span class="line">      <span class="attr">width</span>: <span class="number">1280</span>,</span><br><span class="line">      <span class="attr">height</span>: <span class="number">720</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="keyword">const</span> page = <span class="keyword">await</span> browser.<span class="title function_">newPage</span>();</span><br><span class="line">  <span class="keyword">await</span> page.<span class="title function_">goto</span>(<span class="string">&#x27;https://www.otemeta.com&#x27;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">await</span> browser.<span class="title function_">close</span>();</span><br><span class="line">&#125;)();</span><br></pre></td></tr></table></figure><h3 id="在导航期间忽略-HTTPS-错误"><a href="#在导航期间忽略-HTTPS-错误" class="headerlink" title="在导航期间忽略 HTTPS 错误."></a>在导航期间忽略 HTTPS 错误.</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> puppeteer = <span class="built_in">require</span>(<span class="string">&#x27;puppeteer&#x27;</span>);</span><br><span class="line"></span><br><span class="line">(<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> browser = <span class="keyword">await</span> puppeteer.<span class="title function_">launch</span>(&#123;</span><br><span class="line">    <span class="attr">headless</span>: <span class="literal">false</span>,</span><br><span class="line">    <span class="attr">ignoreHTTPSErrors</span>: <span class="literal">false</span>,</span><br><span class="line">    <span class="attr">defaultViewport</span>: &#123;</span><br><span class="line">      <span class="attr">width</span>: <span class="number">1280</span>,</span><br><span class="line">      <span class="attr">height</span>: <span class="number">720</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="keyword">const</span> page = <span class="keyword">await</span> browser.<span class="title function_">newPage</span>();</span><br><span class="line">  <span class="keyword">await</span> page.<span class="title function_">goto</span>(<span class="string">&#x27;https://www.otemeta.com&#x27;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">await</span> browser.<span class="title function_">close</span>();</span><br><span class="line">&#125;)();</span><br></pre></td></tr></table></figure><h2 id="Browser-实例"><a href="#Browser-实例" class="headerlink" title="Browser 实例"></a>Browser 实例</h2><h3 id="常用方法"><a href="#常用方法" class="headerlink" title="常用方法"></a>常用方法</h3><ul><li><code>browser.newPage()</code>：返回一个新的 Page 对象。Page 在一个默认的浏览器上下文中被创建。</li><li><code>browser.close()</code>：关闭 Chromium 及其所有页面(如果页面被打开的话)。</li><li><code>browser.pages()</code>：返回一个包含所有打开的页面的数组。</li></ul><h2 id="Page-实例"><a href="#Page-实例" class="headerlink" title="Page 实例"></a>Page 实例</h2><h3 id="page-goto-url-options"><a href="#page-goto-url-options" class="headerlink" title="page.goto(url[, options])"></a>page.goto(url[, options])</h3><p>导航到相应地址. 地址应该带有 http 协议, 比如 <code>https://</code>。options：</p><ul><li>timeout：跳转等待时间，单位是毫秒, 默认是30秒, 传 0 表示无限等待。可以通过 <code>page.setDefaultNavigationTimeout(timeout)</code> 方法修改默认值</li><li>waitUntil：满足什么条件认为页面跳转完成，默认是 load 事件触发时。指定事件数组，那么所有事件触发后才认为是跳转完成。事件包括：<ul><li>load - 页面的load事件触发时</li><li>domcontentloaded - 页面的 DOMContentLoaded 事件触发时</li><li>networkidle0 - 不再有网络连接时触发（至少500毫秒后）</li><li>networkidle2 - 只有2个网络连接时触发（至少500毫秒后）</li></ul></li></ul><h3 id="常用方法-1"><a href="#常用方法-1" class="headerlink" title="常用方法"></a>常用方法</h3><ul><li><code>page.$(selector)</code>：此方法在页面内执行 <code>document.querySelector</code>。如果没有元素匹配指定选择器，返回值是 <code>null</code>。</li><li><code>page.$$(selector)</code>：此方法在页面内执行 <code>document.querySelectorAll</code>。如果没有元素匹配指定选择器，返回值是 <code>[]</code>。</li><li><code>page.close([options])</code>：关闭页面</li><li><code>page.evaluate(pageFunction[, ...args])</code>： 在页面实例上下文中执行方法</li><li><code>page.setCookie(...cookies)</code>：设置 cookie</li><li><code>page.setViewport(viewport)</code>：如果是一个浏览器多个页面的情况，每个页面都可以有单独的 viewport</li><li><code>page.waitFor()</code>：设置页面停留时间</li><li><code>page.url()</code>：返回当前页面的 URL</li></ul><h2 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h2><h3 id="创建一个-PDF。"><a href="#创建一个-PDF。" class="headerlink" title="创建一个 PDF。"></a>创建一个 PDF。</h3><p>文件为 <code>hn.js</code></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> puppeteer = <span class="built_in">require</span>(<span class="string">&#x27;puppeteer&#x27;</span>);</span><br><span class="line"></span><br><span class="line">(<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> browser = <span class="keyword">await</span> puppeteer.<span class="title function_">launch</span>();</span><br><span class="line">  <span class="keyword">const</span> page = <span class="keyword">await</span> browser.<span class="title function_">newPage</span>();</span><br><span class="line">  <span class="keyword">await</span> page.<span class="title function_">goto</span>(<span class="string">&#x27;https://www.otemeta.com&#x27;</span>, &#123; <span class="attr">waitUntil</span>: <span class="string">&#x27;networkidle2&#x27;</span> &#125;);</span><br><span class="line">  <span class="keyword">await</span> page.<span class="title function_">pdf</span>(&#123;<span class="attr">path</span>: <span class="string">&#x27;hn.pdf&#x27;</span>, <span class="attr">format</span>: <span class="string">&#x27;A4&#x27;</span>&#125;);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">await</span> browser.<span class="title function_">close</span>();</span><br><span class="line">&#125;)();</span><br></pre></td></tr></table></figure><h3 id="在页面中执行脚本"><a href="#在页面中执行脚本" class="headerlink" title="在页面中执行脚本"></a>在页面中执行脚本</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> puppeteer = <span class="built_in">require</span>(<span class="string">&#x27;puppeteer&#x27;</span>);</span><br><span class="line"></span><br><span class="line">(<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> browser = <span class="keyword">await</span> puppeteer.<span class="title function_">launch</span>();</span><br><span class="line">  <span class="keyword">const</span> page = <span class="keyword">await</span> browser.<span class="title function_">newPage</span>();</span><br><span class="line">  <span class="keyword">await</span> page.<span class="title function_">goto</span>(<span class="string">&#x27;https://www.otemeta.com&#x27;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Get the &quot;viewport&quot; of the page, as reported by the page.</span></span><br><span class="line">  <span class="keyword">const</span> dimensions = <span class="keyword">await</span> page.evaluate(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      <span class="attr">width</span>: <span class="variable language_">document</span>.<span class="property">documentElement</span>.<span class="property">clientWidth</span>,</span><br><span class="line">      <span class="attr">height</span>: <span class="variable language_">document</span>.<span class="property">documentElement</span>.<span class="property">clientHeight</span>,</span><br><span class="line">      <span class="attr">deviceScaleFactor</span>: <span class="variable language_">window</span>.<span class="property">devicePixelRatio</span></span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Dimensions:&#x27;</span>, dimensions);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">await</span> browser.<span class="title function_">close</span>();</span><br><span class="line">&#125;)();</span><br></pre></td></tr></table></figure><h2 id="puppeteer-FAQ"><a href="#puppeteer-FAQ" class="headerlink" title="puppeteer FAQ"></a>puppeteer FAQ</h2><h3 id="puppeteer-和-puppeteer-core-的区别"><a href="#puppeteer-和-puppeteer-core-的区别" class="headerlink" title="puppeteer 和 puppeteer-core 的区别"></a>puppeteer 和 puppeteer-core 的区别</h3><p>当你安装 <code>puppeteer</code> 时，它会下载最新版本的 Chromium（<del>170MB Mac，</del>282MB Linux，~280MB Win），以保证可以使用 API。 如果想要跳过下载，请阅读环境变量。</p><p>自 1.7.0 版本以来，我们都会发布一个 <code>puppeteer-core</code> 包，这个包默认不会下载 Chromium。</p><p><code>puppeteer-core</code> 是一个的轻量级的 Puppeteer 版本，用于启动现有浏览器安装或连接到远程安装。</p><h3 id="puppeteer-launch-和-puppeteer-connect-的区别"><a href="#puppeteer-launch-和-puppeteer-connect-的区别" class="headerlink" title="puppeteer.launch 和 puppeteer.connect 的区别"></a>puppeteer.launch 和 puppeteer.connect 的区别</h3><p>puppeteer 提供了两种方式来启动浏览器：<code>puppeteer.launch</code> 和 <code>puppeteer.connect</code>。</p><ol><li><p><code>puppeteer.launch</code>: 这个方法会启动一个全新的浏览器实例，每次调用 <code>puppeteer.launch</code> 都会启动一个新的浏览器进程。这种方式适合于需要独立的、干净的浏览器环境的场景，比如进行网页截图、自动化测试等。</p></li><li><p><code>puppeteer.connect</code>: 这个方法允许连接到一个已经存在的浏览器实例。这种方式适合于需要在已有浏览器实例上进行操作的场景，比如远程调试、复用已有的浏览器环境等。</p></li></ol><p>因此，<code>puppeteer.launch</code> 用于启动新的浏览器实例，而 <code>puppeteer.connect</code> 用于连接到已有的浏览器实例。根据具体的需求和场景，选择合适的方法来启动或连接浏览器。</p><h3 id="networkidle2-和-networkidle0-的区别"><a href="#networkidle2-和-networkidle0-的区别" class="headerlink" title="networkidle2 和 networkidle0 的区别"></a>networkidle2 和 networkidle0 的区别</h3><p>在 Puppeteer 中，<code>networkidle0</code> 和 <code>networkidle2</code> 是两种网络空闲检测的事件，它们用于在 <code>page.goto()</code> 或 <code>page.waitForNavigation()</code> 等函数中作为选项，以便确定何时认为页面导航和加载已经完成。</p><p>这两个事件的区别在于它们对网络空闲状态的定义：</p><ul><li><code>networkidle0</code>：在至少 500 毫秒内没有任何网络连接时触发。这意味着页面在指定时间内没有任何网络请求，这通常表示页面加载已经完成，并且没有任何长轮询请求或其他持续的网络活动。</li><li><code>networkidle2</code>：在至少 500 毫秒内网络连接数不超过 2 个时触发。这意味着页面可能仍然在进行一些轻量级的网络活动，但基本上是空闲的。这对于有一些小的、持续的网络请求（如 WebSockets 或长轮询请求）的页面来说是有用的。</li></ul><p>选择哪一个取决于你的用例。如果你需要等待所有网络活动完全停止，使用 <code>networkidle0</code> 可能更合适。如果页面预期会有一些持续的网络活动，但你想在大部分资源加载完毕时继续操作，那么 <code>networkidle2</code> 可能是更好的选择。</p><p>请注意，从 Puppeteer v3.0.0 开始，<code>networkidle0</code> 和 <code>networkidle2</code> 事件已被弃用，取而代之的是 <code>networkidle</code>。如果你使用的是更新的 Puppeteer 版本，请查阅最新的文档以获取正确的使用方法。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/3ed9d91bc806/</id>
    <link href="https://blog.zisheng.pro/posts/3ed9d91bc806/"/>
    <published>2024-03-07T02:19:57.000Z</published>
    <summary>Puppeteer 是一个 Node 库，它提供了一个高级 API 来通过 DevTools 协议控制 Chromium 或 Chrome。Puppeteer 默认以 headless 模式运行，但是可以通过修改配置文件运行“有头”模式。</summary>
    <title>Puppeteer 学习笔记（一）: 入门</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="JavaScript" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/JavaScript/"/>
    <category term="javascript" scheme="https://blog.zisheng.pro/tags/javascript/"/>
    <category term="IIFE" scheme="https://blog.zisheng.pro/tags/IIFE/"/>
    <content>
      <![CDATA[<p>在 JavaScript 中，IIFE（Immediately Invoked Function Expression）是一种重要且强大的编程模式。它能够帮助我们创建私有作用域，避免全局命名冲突，并提供了一种模块化的编程方式。本文将深入探讨 IIFE 的概念、用途和最佳实践。</p><h2 id="IIFE-的定义和语法"><a href="#IIFE-的定义和语法" class="headerlink" title="IIFE 的定义和语法"></a>IIFE 的定义和语法</h2><p>IIFE 是一个立即调用的函数表达式，它由两部分组成：函数表达式和立即调用运算符。其基本语法如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">(<span class="keyword">function</span>(<span class="params"></span>)&#123;</span><br><span class="line">  <span class="comment">// 代码块</span></span><br><span class="line">&#125;)();</span><br></pre></td></tr></table></figure><p>或</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">(<span class="keyword">function</span>(<span class="params"></span>)&#123;</span><br><span class="line">  <span class="comment">// 代码块</span></span><br><span class="line">&#125;());</span><br></pre></td></tr></table></figure><p>或</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">!<span class="keyword">function</span>(<span class="params"></span>)&#123;</span><br><span class="line">  <span class="comment">// 代码块</span></span><br><span class="line">&#125;();</span><br></pre></td></tr></table></figure><p>这样定义和调用的函数会立即执行，而不需要通过函数名来调用。</p><h2 id="IIFE-的作用和用途"><a href="#IIFE-的作用和用途" class="headerlink" title="IIFE 的作用和用途"></a>IIFE 的作用和用途</h2><p>IIFE 的一个主要作用是创建一个私有作用域，避免全局变量的污染。通过将代码封装在 IIFE 内部，所有的变量和函数都将存在于 IIFE 的作用域内，不会与全局作用域发生冲突。这种模式特别适用于插件和库的开发，可以将插件的代码封装在 IIFE 中，只暴露需要的接口给外部使用。</p><h2 id="IIFE-的参数传递和返回值"><a href="#IIFE-的参数传递和返回值" class="headerlink" title="IIFE 的参数传递和返回值"></a>IIFE 的参数传递和返回值</h2><p>IIFE 可以接受参数，并返回一个值。通过传递参数，我们可以向 IIFE 内部传递外部的变量和值，进一步增强了其灵活性。同时，通过返回一个值，我们可以将内部的数据和功能暴露给外部使用。</p><h2 id="IIFE-的最佳实践"><a href="#IIFE-的最佳实践" class="headerlink" title="IIFE 的最佳实践"></a>IIFE 的最佳实践</h2><p>在使用 IIFE 时，有几个最佳实践值得注意：</p><ul><li>使用分号结尾：在 IIFE 的结尾加上分号，以避免与其他代码的连锁执行发生问题。</li><li>使用严格模式：在 IIFE 内部开启严格模式，以确保代码的安全性和可靠性。</li><li>适度使用 IIFE：不要滥用 IIFE，只在需要创建私有作用域或模块化代码时使用它。</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>IIFE 是 JavaScript 中一种强大且常用的编程模式，它可以帮助我们避免全局命名冲突，创建私有作用域，并提供模块化的编程方式。通过合理地运用 IIFE，我们可以写出更加安全、可维护和可扩展的代码。</p><p>希望本文能够帮助你更好地了解和应用 IIFE，在 JavaScript 的开发中发挥更大的作用。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/8e6c60fd4747/</id>
    <link href="https://blog.zisheng.pro/posts/8e6c60fd4747/"/>
    <published>2024-02-08T06:33:23.000Z</published>
    <summary>当提到 IIFE（Immediately Invoked Function Expression）时，它是一种 JavaScript 中的函数表达式。这种表达式定义了一个函数，并立即调用它。以下是一篇关于 IIFE 的文章。</summary>
    <title>深入理解 JavaScript 中的 IIFE</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="Mac" scheme="https://blog.zisheng.pro/categories/Mac/"/>
    <category term="picgo" scheme="https://blog.zisheng.pro/tags/picgo/"/>
    <content>
      <![CDATA[<h2 id="安装方式"><a href="#安装方式" class="headerlink" title="安装方式"></a>安装方式</h2><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">brew install picgo --cask</span><br></pre></td></tr></table></figure><h2 id="问题1-安装后打开-picgo-报错：xxx-已损坏，无法打开。您应该将它移到废纸篓"><a href="#问题1-安装后打开-picgo-报错：xxx-已损坏，无法打开。您应该将它移到废纸篓" class="headerlink" title="问题1: 安装后打开 picgo 报错：xxx 已损坏，无法打开。您应该将它移到废纸篓"></a>问题1: 安装后打开 picgo 报错：xxx 已损坏，无法打开。您应该将它移到废纸篓</h2><p>终端输入：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo xattr -r -d com.apple.quarantine 你的APP路径</span><br></pre></td></tr></table></figure><p>APP 路径的获取方法：</p><p>打开 “访达”（Finder）进入 “应用程序” 目录，找到该软件图标，将图标拖到终端窗口，最终的命令类似</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo xattr -r -d com.apple.quarantine <span class="string">&#x27;/Applications/PicGo.app&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="问题2-无法从剪贴板上传"><a href="#问题2-无法从剪贴板上传" class="headerlink" title="问题2: 无法从剪贴板上传"></a>问题2: 无法从剪贴板上传</h2><p>现象：配置好 cos 之后，可以正常从本地推动到主窗口上传，但在剪贴板上的“等待上传” 则点击无效。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/202310302151692.png"></p><p>排查方式：打开 picgo 的日志文件， 在<strong>主窗口 -&gt; PicGo设置 -&gt; 设置日志文件（点击设置） -&gt; 日志文件（点击打开）</strong>，如果其中的报错是如下格式，可以检查一下对应目录 <code>picgo-clipboard-images</code> 是否存在，不存在手动创建一个：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Error: ENOENT: no such file or directory, open <span class="string">&#x27;/Users/zizhu/Library/Application Support/picgo/picgo-clipboard-images/202306272131465.png.3840258425&#x27;</span></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/ddf5c735f6e0/</id>
    <link href="https://blog.zisheng.pro/posts/ddf5c735f6e0/"/>
    <published>2023-12-31T16:00:00.000Z</published>
    <summary>本文介绍了 Mac(M2) 正确安装 picgo 姿势</summary>
    <title>Mac(M2) 正确安装 picgo 姿势</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="项目管理" scheme="https://blog.zisheng.pro/categories/%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86/"/>
    <category term="kick-off" scheme="https://blog.zisheng.pro/tags/kick-off/"/>
    <content>
      <![CDATA[<h2 id="KO-会议是什么？"><a href="#KO-会议是什么？" class="headerlink" title="KO 会议是什么？"></a>KO 会议是什么？</h2><p>Kick-off Meeting 有的翻译为项目启动会议，也有的翻译成开工或者开踢会议，这不重要，明确这个英文名字即可，不用管如何翻译。会议是 PM 激励其团队的最佳机会。在这次会议上，项目管理人员可以建立共同目标，并开始了解每个人。</p><h2 id="KO-会议过程"><a href="#KO-会议过程" class="headerlink" title="KO 会议过程"></a>KO 会议过程</h2><p><strong>1、项目背景</strong></p><p>我们项目在哪里？说过去，做项目之前的“悲惨境地”，明确问题根源在哪里，明白为什么要做这个项目。</p><p><strong>2、项目意义、目的与目标</strong></p><p>我们项目去哪里？说将来，项目完成之后的美好前景，解决那些问题就算成功，达到什么样的效果就算完成，让听众知道终点在哪里。</p><p><strong>3、需求和功能点概述</strong></p><p>我们怎么做？说现状，说问题，具体用什么方法促使“过去”到“将来”的转变。讲解方法和任务概况，让听众为终极目标跃跃欲试。</p><p><strong>4、项目组织架构</strong></p><p>明确项目组成员工作范围和在项目中的职位，目的是让项目组成员互相认识，明确什么事情应该找谁。</p><p>KO时，最好是所有项目干系人都要到场，至少关键任务都要到场，比如：项目启动者或发起人，需求方，PMO人员。他们可以高屋建甄的给项目成员描绘项目的重要性和伟大前景，非常鼓舞士气。项目早起，务必多让项目启动者或发起人，需求方，PMO人员多多参与，反复确认方向正确，因为这时候，做各种调整的成本比较低。</p><p>注意不要遗漏工作不多的项目干系人，包括服务部门、配合部门的接口人等。这样会给后续项目实施、上线、运维带来很大的便利。</p><p>介绍成员的时候，最好能够让大家介绍一下自己，和大家站起来示意，自己做个简单自我介绍。</p><p><strong>5、项目计划（WBS）</strong></p><p>让所有人了解两个关键点：</p><p>第一、项目的时间点和里程碑；</p><p>第二、项目各个时段需要的资源，即每个人要在各个阶段做什么事情；</p><p>项目管理的计划、进度、资源、质量、风险，永恒不变的话题，只能自己体会。</p><p><strong>6、沟通计划</strong></p><p>沟通在项目管理中的重要地位不言而喻。太多的事情都是因为沟通不顺利，导致的产生一系列的问题。</p><p>做好沟通计划，强迫项目组成员定期沟通，降低问题发生的几率。</p><p>注意：定期与项目启动者或发起人，需求方，PMO人员回报和沟通，是促使项目顺利进行的必要因素。让他们了解项目进度，知道项目遇到的困难，更有助于申请资源。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/b5b1c16bbb84/</id>
    <link href="https://blog.zisheng.pro/posts/b5b1c16bbb84/"/>
    <published>2023-11-10T15:00:00.000Z</published>
    <summary>Kick-off Meeting 有的翻译为项目启动会议，也有的翻译成开工或者开踢会议，这不重要，明确这个英文名字即可，不用管如何翻译。会议是 PM 激励其团队的最佳机会。在这次会议上，项目管理人员可以建立共同目标，并开始了解每个人。</summary>
    <title>KO 会议是什么？</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="NodeJs" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/NodeJs/"/>
    <category term="incompatible" scheme="https://blog.zisheng.pro/tags/incompatible/"/>
    <category term="node" scheme="https://blog.zisheng.pro/tags/node/"/>
    <category term="engine" scheme="https://blog.zisheng.pro/tags/engine/"/>
    <content>
      <![CDATA[<p>大家都知道 npm 包可以通过在 <code>package.json</code> 指定 engines 来限制使用的运行时环境，比如我最近开发公司项目时遇到某个项目指定了 node 版本过低：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;engines&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;node&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&gt;7 &lt;=9&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>由于历史原因我们无法移除这个包，所以需要忽略由其引起报错：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn install --ignore-engines</span><br></pre></td></tr></table></figure><p>或者：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn config <span class="built_in">set</span> ignore-engines <span class="literal">true</span></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/bf86f69dbf94/</id>
    <link href="https://blog.zisheng.pro/posts/bf86f69dbf94/"/>
    <published>2023-08-16T13:53:00.000Z</published>
    <summary>本文介绍了如何忽略不兼容的 node engine 错误</summary>
    <title>忽略 incompatible engine node error</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="antd" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/antd/"/>
    <category term="中后台" scheme="https://blog.zisheng.pro/tags/%E4%B8%AD%E5%90%8E%E5%8F%B0/"/>
    <category term="Antd Pro" scheme="https://blog.zisheng.pro/tags/Antd-Pro/"/>
    <category term="组件" scheme="https://blog.zisheng.pro/tags/%E7%BB%84%E4%BB%B6/"/>
    <category term="企业级" scheme="https://blog.zisheng.pro/tags/%E4%BC%81%E4%B8%9A%E7%BA%A7/"/>
    <category term="textinput" scheme="https://blog.zisheng.pro/tags/textinput/"/>
    <content>
      <![CDATA[<h2 id="TextArea"><a href="#TextArea" class="headerlink" title="TextArea"></a>TextArea</h2><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Input</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;antd&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> &#123; <span class="title class_">TextArea</span> &#125; = <span class="title class_">Input</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">App</span>: <span class="title class_">React</span>.<span class="property">FC</span> = <span class="function">() =&gt;</span> (</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">TextArea</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">rows</span>=<span class="string">&#123;4&#125;</span> </span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">placeholder</span>=<span class="string">&quot;maxLength is 6&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">maxLength</span>=<span class="string">&#123;6&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">style</span>=<span class="string">&#123;&#123;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">resize:</span> &#x27;<span class="attr">none</span>&#x27;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      &#125;&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    /&gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure><h2 id="ProFormTextArea"><a href="#ProFormTextArea" class="headerlink" title="ProFormTextArea"></a>ProFormTextArea</h2><p>ProFormTextArea 组件本质上是 Form.Item 和 Input.TextArea 的结合，我们可以把他们当成一个 FormItem 来使用，并且支持各种 props。每个表单项都支持 fieldProps 属性来支持设置输入组件的 props。 我们支持了 placeholder 的透传，你可以直接在组件上设置 placeholder。</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">ProForm</span>, <span class="title class_">ProFormTextArea</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@ant-design/pro-components&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">App</span>: <span class="title class_">React</span>.<span class="property">FC</span> = <span class="function">() =&gt;</span> (</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">ProForm</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">onValuesChange</span>=<span class="string">&#123;(changeValues)</span> =&gt;</span> console.log(changeValues)&#125;</span></span><br><span class="line"><span class="language-xml">    &gt;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">ProFormTextArea</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">name</span>=<span class="string">&quot;text&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">label</span>=<span class="string">&quot;名称&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">placeholder</span>=<span class="string">&quot;请输入名称&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">fieldProps</span>=<span class="string">&#123;&#123;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">style:</span> &#123;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">resize:</span> &#x27;<span class="attr">none</span>&#x27;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          &#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        &#125;&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">ProForm</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/141618e1f8a1/</id>
    <link href="https://blog.zisheng.pro/posts/141618e1f8a1/"/>
    <published>2023-08-05T07:00:00.000Z</published>
    <summary>Antd TextArea 用于多行输入。</summary>
    <title>Antd form TextArea 隐藏右下角</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="antd" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/antd/"/>
    <category term="中后台" scheme="https://blog.zisheng.pro/tags/%E4%B8%AD%E5%90%8E%E5%8F%B0/"/>
    <category term="Antd Pro" scheme="https://blog.zisheng.pro/tags/Antd-Pro/"/>
    <category term="组件" scheme="https://blog.zisheng.pro/tags/%E7%BB%84%E4%BB%B6/"/>
    <category term="企业级" scheme="https://blog.zisheng.pro/tags/%E4%BC%81%E4%B8%9A%E7%BA%A7/"/>
    <content>
      <![CDATA[<p>ModalForm 是 ProForm 的一个变体，本质上仍然是个表单。所以无法通过 footer 来自定义页脚,如果要定义页脚需要使用 submitter.render 来进行自定义。这两个表单的表现与 ProForm 相同，可以从 ProForm 直接修改而来。本文由浅入深一步一步记录 ModalForm 使用过程。</p><h2 id="最小使用"><a href="#最小使用" class="headerlink" title="最小使用"></a>最小使用</h2><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Button</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;antd&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">ModalForm</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@ant-design/pro-components&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">PlusOutlined</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@ant-design/icons&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">ModalForm</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">title</span>=<span class="string">&quot;新建项目&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">trigger</span>=<span class="string">&#123;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          &lt;<span class="attr">Button</span> <span class="attr">type</span>=<span class="string">&quot;primary&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">PlusOutlined</span> /&gt;</span>新建项目</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;/<span class="name">Button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        &#125;</span></span><br><span class="line"><span class="language-xml">      &gt;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">ModalForm</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><code>title</code>: 弹框的标题</li><li><code>trigger</code>: 用于触发 Modal 打开的 dom，一般是 button</li></ul><p><img src="https://s2.loli.net/2023/08/04/9vINFYMud2xZtJe.png"></p><h2 id="表单项"><a href="#表单项" class="headerlink" title="表单项"></a>表单项</h2><p>一个表单除了 Form 之外还是需要一系列的表单项，ProForm 自带了数量可观的表单项, 这些组件本质上是 Form.Item 和 组件的结合，我们可以把他们当成一个 FormItem 来使用，并且支持各种 props。每个表单项都支持 fieldProps 属性来支持设置输入组件的props。 我们支持了 placeholder 的透传，你可以直接在组件上设置 placeholder。</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Button</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;antd&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">ModalForm</span>, <span class="title class_">ProForm</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@ant-design/pro-components&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">PlusOutlined</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@ant-design/icons&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">ModalForm</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">title</span>=<span class="string">&quot;新建项目&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">trigger</span>=<span class="string">&#123;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          &lt;<span class="attr">Button</span> <span class="attr">type</span>=<span class="string">&quot;primary&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">PlusOutlined</span> /&gt;</span>新建项目</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;/<span class="name">Button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        &#125;</span></span><br><span class="line"><span class="language-xml">        onFinish=&#123;async (values) =&gt; &#123;</span></span><br><span class="line"><span class="language-xml">          console.log(values.name);</span></span><br><span class="line"><span class="language-xml">          message.success(&#x27;提交成功&#x27;);</span></span><br><span class="line"><span class="language-xml">          return true;</span></span><br><span class="line"><span class="language-xml">        &#125;&#125;</span></span><br><span class="line"><span class="language-xml">      &gt;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">ProFormText</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">width</span>=<span class="string">&quot;xl&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">name</span>=<span class="string">&quot;name&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">label</span>=<span class="string">&quot;项目名称&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">tooltip</span>=<span class="string">&quot;最长为 24 位&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">placeholder</span>=<span class="string">&quot;请输入名称&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">fieldProps</span>=<span class="string">&#123;&#123;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">maxLength:</span> <span class="attr">50</span>,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">showCount:</span> <span class="attr">true</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          &#125;&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">ModalForm</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><code>onFinish</code>: 提交数据时触发，如果返回一个 true。会关掉抽屉,如果配置了 <code>destroyOnClose</code> 还会重置表单。</li><li><code>ProFormText</code>: ProFormText 是 FormItem + Input 的产物，用于输入各类文本</li></ul><blockquote><p>注意：onFinish 是提交表单且数据验证成功后回调事件，同 antd 4 Form 组件 API</p></blockquote><p><img src="https://s2.loli.net/2023/08/04/t4LwjbKHGNSP8Rn.png"></p><h2 id="表单校验"><a href="#表单校验" class="headerlink" title="表单校验"></a>表单校验</h2><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Button</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;antd&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">ModalForm</span>, <span class="title class_">ProForm</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@ant-design/pro-components&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">PlusOutlined</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@ant-design/icons&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">ModalForm</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">title</span>=<span class="string">&quot;新建项目&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">trigger</span>=<span class="string">&#123;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          &lt;<span class="attr">Button</span> <span class="attr">type</span>=<span class="string">&quot;primary&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">PlusOutlined</span> /&gt;</span>新建项目</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;/<span class="name">Button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        &#125;</span></span><br><span class="line"><span class="language-xml">        onFinish=&#123;async (values) =&gt; &#123;</span></span><br><span class="line"><span class="language-xml">          console.log(values.name);</span></span><br><span class="line"><span class="language-xml">          message.success(&#x27;提交成功&#x27;);</span></span><br><span class="line"><span class="language-xml">          return true;</span></span><br><span class="line"><span class="language-xml">        &#125;&#125;</span></span><br><span class="line"><span class="language-xml">      &gt;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">ProFormText</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">width</span>=<span class="string">&quot;xl&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">name</span>=<span class="string">&quot;name&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">label</span>=<span class="string">&quot;项目名称&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">tooltip</span>=<span class="string">&quot;最长为 24 位&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">placeholder</span>=<span class="string">&quot;请输入名称&quot;</span>,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">rules</span>=<span class="string">&#123;[</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            &#123;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">              <span class="attr">required:</span> <span class="attr">true</span>,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">              <span class="attr">message:</span> &quot;<span class="attr">请输入项目名称</span>&quot;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            &#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          ]&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">ModalForm</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们只需要关心业务规则，表单校验通过之后 onFinish 才会被调用。</p><p><img src="https://s2.loli.net/2023/08/04/ex4aNfWPXJ28oCp.png"></p><h2 id="默认值"><a href="#默认值" class="headerlink" title="默认值"></a>默认值</h2><p>优先级：request(ProForm) &gt; initialValues(ProForm) &gt; request(ProFormItem) &gt; initialValue(ProFormItem)</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Button</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;antd&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">ModalForm</span>, <span class="title class_">ProForm</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@ant-design/pro-components&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">PlusOutlined</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@ant-design/icons&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">ModalForm</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">title</span>=<span class="string">&quot;新建项目&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">trigger</span>=<span class="string">&#123;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          &lt;<span class="attr">Button</span> <span class="attr">type</span>=<span class="string">&quot;primary&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">PlusOutlined</span> /&gt;</span>新建项目</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;/<span class="name">Button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        &#125;</span></span><br><span class="line"><span class="language-xml">        request=&#123;async () =&gt; (&#123;</span></span><br><span class="line"><span class="language-xml">          name: &quot;紫升的项目1&quot;</span></span><br><span class="line"><span class="language-xml">        &#125;&#125;&#125;</span></span><br><span class="line"><span class="language-xml">        initialValues=&#123;&#123;</span></span><br><span class="line"><span class="language-xml">          name: &quot;紫升的项目2&quot;</span></span><br><span class="line"><span class="language-xml">        &#125;&#125;</span></span><br><span class="line"><span class="language-xml">        onFinish=&#123;async (values) =&gt; &#123;</span></span><br><span class="line"><span class="language-xml">          console.log(values.name);</span></span><br><span class="line"><span class="language-xml">          message.success(&#x27;提交成功&#x27;);</span></span><br><span class="line"><span class="language-xml">          return true;</span></span><br><span class="line"><span class="language-xml">        &#125;&#125;</span></span><br><span class="line"><span class="language-xml">      &gt;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">ProFormText</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">width</span>=<span class="string">&quot;xl&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">name</span>=<span class="string">&quot;name&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">label</span>=<span class="string">&quot;项目名称&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">tooltip</span>=<span class="string">&quot;最长为 24 位&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">placeholder</span>=<span class="string">&quot;请输入名称&quot;</span>,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">request</span>=<span class="string">&#123;async</span> () =&gt;</span> &quot;紫升的项目3&quot;&#125;</span></span><br><span class="line"><span class="language-xml">          initialValue=&#123;&quot;紫升的项目4&quot;&#125;</span></span><br><span class="line"><span class="language-xml">          rules=&#123;[</span></span><br><span class="line"><span class="language-xml">            &#123;</span></span><br><span class="line"><span class="language-xml">              required: true,</span></span><br><span class="line"><span class="language-xml">              message: &quot;请输入项目名称&quot;</span></span><br><span class="line"><span class="language-xml">            &#125;</span></span><br><span class="line"><span class="language-xml">          ]&#125;</span></span><br><span class="line"><span class="language-xml">        /&gt;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">ModalForm</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="modalProps"><a href="#modalProps" class="headerlink" title="modalProps"></a>modalProps</h2><p>Modal 的 props，使用方式与 antd 相同。注意：不支持 ‘visible’，请使用全局的 visible，比如关闭时重置表单的 <code>destroyOnClose</code> 就在这里配置。</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Button</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;antd&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">ModalForm</span>, <span class="title class_">ProForm</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@ant-design/pro-components&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">PlusOutlined</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@ant-design/icons&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">ModalForm</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">title</span>=<span class="string">&quot;新建项目&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">trigger</span>=<span class="string">&#123;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          &lt;<span class="attr">Button</span> <span class="attr">type</span>=<span class="string">&quot;primary&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">PlusOutlined</span> /&gt;</span>新建项目</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;/<span class="name">Button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        &#125;</span></span><br><span class="line"><span class="language-xml">        modalProps=&#123;&#123;</span></span><br><span class="line"><span class="language-xml">          destroyOnClose: true</span></span><br><span class="line"><span class="language-xml">        &#125;&#125;</span></span><br><span class="line"><span class="language-xml">      &gt;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">ProFormText</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">width</span>=<span class="string">&quot;xl&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">name</span>=<span class="string">&quot;name&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">label</span>=<span class="string">&quot;项目名称&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">tooltip</span>=<span class="string">&quot;最长为 24 位&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">placeholder</span>=<span class="string">&quot;请输入名称&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">ModalForm</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><code>destroyOnClose</code>: 默认 ModalFrom 关闭后，表单不会重置，需要单独配置。</li></ul><h2 id="其他属性"><a href="#其他属性" class="headerlink" title="其他属性"></a>其他属性</h2><ul><li><code>autoFocusFirstInput</code>: 自动聚焦第一个输入框</li><li><code>submitTimeout</code>: 提交数据时，禁用取消按钮的超时时间（毫秒）。</li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/18fc5bd5e9a2/</id>
    <link href="https://blog.zisheng.pro/posts/18fc5bd5e9a2/"/>
    <published>2023-08-04T07:00:00.000Z</published>
    <summary>ModalForm 是 ProForm 的一个变体，本质上仍然是个表单。所以无法通过 footer 来自定义页脚,如果要定义页脚需要使用 submitter.render 来进行自定义。这两个表单的表现与 ProForm 相同，可以从 ProForm 直接修改而来。</summary>
    <title>Antd Pro ModalFrom 使用记录</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="全栈开发" scheme="https://blog.zisheng.pro/categories/%E5%85%A8%E6%A0%88%E5%BC%80%E5%8F%91/"/>
    <category term="数据库" scheme="https://blog.zisheng.pro/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    <category term="分页" scheme="https://blog.zisheng.pro/tags/%E5%88%86%E9%A1%B5/"/>
    <category term="sequelize" scheme="https://blog.zisheng.pro/tags/sequelize/"/>
    <content>
      <![CDATA[<h2 id="findAndCountAll-方法"><a href="#findAndCountAll-方法" class="headerlink" title="findAndCountAll 方法"></a>findAndCountAll 方法</h2><p><code>findAndCountAll</code> 会在查出列表记录的同时, 还会返回符合你查询列表数据的条数。</p><h2 id="分页"><a href="#分页" class="headerlink" title="分页"></a>分页</h2><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="title function_">queryProjectList</span>(<span class="params">params: &#123; currentPage: <span class="built_in">number</span>, pageSize: <span class="built_in">number</span> &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; currentPage, pageSize &#125; = params;</span><br><span class="line">  <span class="keyword">const</span> result = <span class="keyword">await</span> <span class="title class_">UserModel</span>.<span class="title function_">findAndCountAll</span>(&#123;</span><br><span class="line">    <span class="attr">limit</span>: params.<span class="property">pageSize</span>, <span class="comment">// 确保是 number 类型</span></span><br><span class="line">    <span class="attr">offset</span>: (currentPage - <span class="number">1</span>) * pageSize, <span class="comment">// 计算偏移量算法</span></span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="attr">data</span>: result.<span class="property">rows</span>,</span><br><span class="line">    <span class="attr">total</span>: result.<span class="property">count</span></span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="条件查询"><a href="#条件查询" class="headerlink" title="条件查询"></a>条件查询</h2><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="title function_">queryProjectList</span>(<span class="params">params: &#123; pid: <span class="built_in">string</span>, <span class="keyword">type</span>: ProjectType &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; pid, <span class="keyword">type</span> &#125; = params;</span><br><span class="line">  <span class="keyword">const</span> <span class="attr">condition</span>: <span class="built_in">any</span> = &#123;</span><br><span class="line">    <span class="attr">deletedAt</span>: <span class="literal">null</span>, <span class="comment">// deletedAt 为 null 表示未删除，需要配合 paranoid 模型配置</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 模糊查询</span></span><br><span class="line">  <span class="keyword">if</span> (pid) &#123;</span><br><span class="line">    condition.<span class="property">pid</span> = &#123; [<span class="title class_">Op</span>.<span class="property">like</span>]: <span class="string">`%<span class="subst">$&#123;pid&#125;</span>%`</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 精确查询</span></span><br><span class="line">  <span class="keyword">if</span>(<span class="keyword">type</span>) &#123;</span><br><span class="line">    condition.<span class="property">type</span> = &#123; [<span class="title class_">Op</span>.<span class="property">eq</span>]: <span class="keyword">type</span> &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="distinct"><a href="#distinct" class="headerlink" title="distinct"></a>distinct</h2><p>如果使用了 <code>include</code> 参数，需要额外传入 <code>distinct: true</code> 配置来去除 <code>include</code> 的数量。</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> result = <span class="keyword">await</span> <span class="title class_">Model</span>.<span class="title function_">findAndCountAll</span>(&#123;</span><br><span class="line">  <span class="attr">limit</span>: params.<span class="property">pageSize</span>, <span class="comment">// 确保是 number 类型</span></span><br><span class="line">  <span class="attr">offset</span>: (currentPage - <span class="number">1</span>) * pageSize, <span class="comment">// 计算偏移量算法</span></span><br><span class="line">  <span class="attr">include</span>: [</span><br><span class="line">    &#123; <span class="attr">model</span>: <span class="title class_">HobbyModel</span>&#125;,</span><br><span class="line">    &#123; <span class="attr">model</span>: <span class="title class_">FriendModel</span>&#125;,</span><br><span class="line">  ],</span><br><span class="line">  <span class="attr">distinct</span>: <span class="literal">true</span>,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><ul><li><a href="https://github.com/sequelize/sequelize/issues/6418">result.count for findAndCountAll is incorrect with include</a></li></ul><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://blog.csdn.net/qq_23521659/article/details/124871838">sequelize模糊查询及分页</a></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/e6c3e5bafb7d/</id>
    <link href="https://blog.zisheng.pro/posts/e6c3e5bafb7d/"/>
    <published>2023-08-02T04:30:00.000Z</published>
    <summary>最近在开发一个全栈应用，起手就是一套增删改查，写到列表查询时，分页接口如何实现费了一番功夫，遂记录一下，以便后续使用。</summary>
    <title>实现 sequelize 分页和条件查询</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="单元测试" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95/"/>
    <category term="nodejs" scheme="https://blog.zisheng.pro/tags/nodejs/"/>
    <category term="typescript" scheme="https://blog.zisheng.pro/tags/typescript/"/>
    <category term="jest" scheme="https://blog.zisheng.pro/tags/jest/"/>
    <content>
      <![CDATA[<h2 id="安装-jest"><a href="#安装-jest" class="headerlink" title="安装 jest"></a>安装 jest</h2><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add jest -D</span><br></pre></td></tr></table></figure><p>然后，将下列配置内容添加到您的 <code>package.json</code>：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;test&quot;</span><span class="punctuation">:</span> <span class="string">&quot;jest&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="安装-ts-jest"><a href="#安装-ts-jest" class="headerlink" title="安装 ts-jest"></a>安装 ts-jest</h2><p>ts-jest 是一个支持 sourcemap 的 TypeScript 预处理器，让你使用 TypeScript 编写 Jest 测试项目</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add --dev ts-jest</span><br></pre></td></tr></table></figure><h2 id="创建配置文件"><a href="#创建配置文件" class="headerlink" title="创建配置文件"></a>创建配置文件</h2><p>执行 <code>jest --init</code> 回答几个简单的问题之后会生成一个 <code>jest.config.ts</code> 文件，然后在这个文件中添加 <code>preset: &#39;ts-jest&#39;</code> 配置。</p><h2 id="安装-ts-node"><a href="#安装-ts-node" class="headerlink" title="安装 ts-node"></a>安装 ts-node</h2><p>如果配置文件是 <code>jest.config.ts</code>，那就需要安装 ts-node 支持它：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add ts-node -D</span><br></pre></td></tr></table></figure><h2 id="安装-types-jest"><a href="#安装-types-jest" class="headerlink" title="安装 @types&#x2F;jest"></a>安装 @types&#x2F;jest</h2><p>如果使用 typescript 写测试用例，那就需要安装 @types&#x2F;jest：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add @types/jest -D</span><br></pre></td></tr></table></figure><h2 id="踩坑"><a href="#踩坑" class="headerlink" title="踩坑"></a>踩坑</h2><h3 id="Error-Jest-‘ts-node’-is-required-for-the-TypeScript-configuration-files-Make-sure-it-is-installed"><a href="#Error-Jest-‘ts-node’-is-required-for-the-TypeScript-configuration-files-Make-sure-it-is-installed" class="headerlink" title="Error: Jest: ‘ts-node’ is required for the TypeScript configuration files. Make sure it is installed"></a>Error: Jest: ‘ts-node’ is required for the TypeScript configuration files. Make sure it is installed</h3><p>在全局执行 <code>jest</code>，会报错，意思是解析 typescript 配置文件需要安装 ts-node，</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 全局</span></span><br><span class="line">$ npm install ts-node -g</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/a95721d19976/</id>
    <link href="https://blog.zisheng.pro/posts/a95721d19976/"/>
    <published>2023-07-28T12:00:00.000Z</published>
    <summary>本文介绍了如何在 Typescript 项目中初始化 Jest 并正确配置</summary>
    <title>Typescript 项目使用 Jest 入门</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="issues" scheme="https://blog.zisheng.pro/categories/issues/"/>
    <category term="github" scheme="https://blog.zisheng.pro/categories/issues/github/"/>
    <category term="github" scheme="https://blog.zisheng.pro/tags/github/"/>
    <category term="github 加载" scheme="https://blog.zisheng.pro/tags/github-%E5%8A%A0%E8%BD%BD/"/>
    <category term="github release" scheme="https://blog.zisheng.pro/tags/github-release/"/>
    <category term="github cdn" scheme="https://blog.zisheng.pro/tags/github-cdn/"/>
    <content>
      <![CDATA[<p>经常使用 Github 的开发在执行 <code>git push</code> 时经常会遇到 <code>Failed to connect to github.com port 443</code> 问题，今天紫升就来介绍下如何完美解决这个问题。</p><ul><li><a href="https://sites.ipaddress.com/github.com">https://sites.ipaddress.com/github.com</a></li><li><a href="https://sites.ipaddress.com/assets-cdn.github.com">https://sites.ipaddress.com/assets-cdn.github.com</a></li><li><a href="https://sites.ipaddress.com/github.global.ssl.fastly.net">https://sites.ipaddress.com/github.global.ssl.fastly.net</a></li><li><a href="https://sites.ipaddress.com/github.dev">https://sites.ipaddress.com/github.dev</a></li><li><a href="https://sites.ipaddress.com/githubusercontent.com">https://sites.ipaddress.com/githubusercontent.com</a></li></ul><p>然后找到 A Records 依次记下 ip。</p><p>最后打开电脑的hosts文件，把下列的东东写在最后，然后保存即可：</p><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">140.82.113.4    github.com</span><br><span class="line">185.199.108.153 assets-cdn.github.com</span><br><span class="line">151.101.1.194  github.global.ssl.fastly.net</span><br><span class="line">52.224.38.193 github.dev</span><br><span class="line">185.199.108.133 githubusercontent.com</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/995252c5296a/</id>
    <link href="https://blog.zisheng.pro/posts/995252c5296a/"/>
    <published>2023-07-17T19:20:35.047Z</published>
    <summary>经常使用 Github 的开发在执行 git push 时经常会遇到 Failed to connect to github.com port 443 问题，今天紫升就来介绍下如何完美解决这个问...</summary>
    <title>「已解决」Failed to connect to github.com port 443</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="工具" scheme="https://blog.zisheng.pro/categories/%E5%B7%A5%E5%85%B7/"/>
    <category term="hosts" scheme="https://blog.zisheng.pro/tags/hosts/"/>
    <category term="macos" scheme="https://blog.zisheng.pro/tags/macos/"/>
    <category term="chrome" scheme="https://blog.zisheng.pro/tags/chrome/"/>
    <content>
      <![CDATA[<h2 id="SwitchHosts"><a href="#SwitchHosts" class="headerlink" title="SwitchHosts"></a>SwitchHosts</h2><p>SwitchHosts是一款免费的跨平台hosts管理工具，可以帮助用户轻松快捷地管理多组hosts，方便地切换各种网络环境。它支持自定义规则、正则表达式以及备份和恢复等功能，是一款非常实用的网络工具。</p><p><img src="https://raw.githubusercontent.com/oldj/SwitchHosts/master/screenshots/sh_light.png" alt="SwitchHosts"></p><p>你可以下载源代码并自行构建，或从以下链接下载已构建的版本：</p><ul><li><a href="https://github.com/oldj/SwitchHosts/releases">SwitchHosts 下载页面（GitHub Release）</a></li></ul><p>您还可以使用软件包管理器 Chocolatey 安装已构建的版本：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">choco install switchhosts</span><br></pre></td></tr></table></figure><h2 id="iHosts"><a href="#iHosts" class="headerlink" title="iHosts"></a>iHosts</h2><p>iHosts 是一款 Mac OS X 上的 <code>/etc/hosts</code> 编辑器，可以通过使用组来管理 hosts，实现部分更新而不是替换整个 hosts 文件。用户可以在编辑 hosts 时使用“实时查看 hosts”的功能，也可以添加或删除 hosts 或 hosts 组。此应用程序以沙盒模式运行，并在编辑 hosts 时更新 <code>/etc/hosts</code> 文件。</p><p><img src="https://github.com/toolinbox/iHosts/raw/master/images/EditHosts.png" alt="iHosts"></p><p><a href="https://itunes.apple.com/app/id1102004240?ls=1&mt=12">&gt; &gt; &gt; 点击免费从 Mac App Store 下载 iHosts &lt;&lt;&lt;</a></p><h2 id="LightProxy"><a href="#LightProxy" class="headerlink" title="LightProxy"></a>LightProxy</h2><p>LightProxy 是阿里巴巴吧开源的一个跨平台的Web调试代理工具，具有证书安装和系统代理设置的完全自动化功能。它基于 Whistle，提供了强大的 <code>hosts/proxy/capture</code> 功能，并支持自定义 Node.js 代理规则。</p><p><img src="https://camo.githubusercontent.com/46a576f77303209ba2eb4df022a2520717064f22d7e297ff88ee7b07635b46f0/68747470733a2f2f696d672e616c6963646e2e636f6d2f7466732f5442313537624a462e5431674b306a535a46725858634e435858612d313339332d3932312e706e67" alt="LightProxy"></p><ul><li><a href="https://gw.alipayobjects.com/os/LightProxy/LightProxy.dmg">macOS Version Download</a></li><li><a href="https://gw.alipayobjects.com/os/LightProxy/LightProxy-Setup.exe">Windows Version Download</a></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/7ac16e45f626/</id>
    <link href="https://blog.zisheng.pro/posts/7ac16e45f626/"/>
    <published>2023-07-17T18:47:19.466Z</published>
    <summary>SwitchHosts SwitchHosts是一款免费的跨平台hosts管理工具，可以帮助用户轻松快捷地管理多组hosts，方便地切换各种网络环境。它支持自定义规则、正则表达式以及备份和恢复等...</summary>
    <title>2023 hosts 切换工具推荐</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="站点运营" scheme="https://blog.zisheng.pro/categories/%E7%AB%99%E7%82%B9%E8%BF%90%E8%90%A5/"/>
    <category term="hexo" scheme="https://blog.zisheng.pro/tags/hexo/"/>
    <category term="vuepress" scheme="https://blog.zisheng.pro/tags/vuepress/"/>
    <category term="qexo" scheme="https://blog.zisheng.pro/tags/qexo/"/>
    <content>
      <![CDATA[<h2 id="部署"><a href="#部署" class="headerlink" title="部署"></a>部署</h2><p>你可以通过 Vercel 提供的免费数据库。但请注意这是个 Beta 功能</p><a class="btn-beautify " href="https://vercel.com/new/clone?repository-url=https://github.com/am-abudu/Qexo" title="一键部署"><span>一键部署</span></a><p>首次部署会报错, 请无视并进行接下来的步骤</p><h3 id="申请-Vercel-数据库"><a href="#申请-Vercel-数据库" class="headerlink" title="申请 Vercel 数据库"></a>申请 Vercel 数据库</h3><p>进入 <a href="https://vercel.com/dashboard/stores">Vercel Storage</a> 界面 然后点击右上角的 Create Database 并选择 Postgres 创建免费 PostgreSQL 数据库, 在 Connect 页面获取数据库连接信息。请注意在地区选择的位置选择与你上一步项目对应的地区（通常为 Washington, D.C., USA (East) - iad1）。</p><h3 id="绑定项目"><a href="#绑定项目" class="headerlink" title="绑定项目"></a>绑定项目</h3><p>在左侧边栏选择 Projects 点击 Connect Project 连接到你第一步创建的项目</p><h3 id="部署-1"><a href="#部署-1" class="headerlink" title="部署"></a>部署</h3><p>回到你的项目页面，在 Deployments 点击 Redeploy 开始部署, 若没有 Error 信息即可打开域名进入初始化引导</p><h2 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h2><p>部署成功后，打开域名进入初始化引导：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1689507026731.png" alt="初始化 qexo"></p><h3 id="博客配置"><a href="#博客配置" class="headerlink" title="博客配置"></a>博客配置</h3><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1689507164192.png" alt="博客配置"></p><ul><li>服务商：github&#x2F;gitlab&#x2F;本地</li><li>使用配置：Hexo&#x2F;Hugo&#x2F;Valaxy&#x2F;Vuepress&#x2F;Vitepress</li><li>Github 密钥：于 <a href="https://github.com/settings/tokens">Github 设置</a> 生成的 Token (建议使用 Classical) 需要 Repo &amp; Workflow 下的权限 不建议给出所有权限</li><li>Github 仓库</li><li>项目分支</li><li>博客路径</li></ul><h3 id="Vercel-配置"><a href="#Vercel-配置" class="headerlink" title="Vercel 配置"></a>Vercel 配置</h3><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1689507547316.png" alt="Vercel 配置"></p><ul><li>Vercel 密钥：您的 Vercel 账户密钥 在 <a href="https://vercel.com/account/tokens">此处</a> 生成</li><li>项目ID：您 Qexo 部署所在项目的 ID 位于 Project Settings -&gt; General -&gt; Project ID</li></ul><h2 id="图床设置"><a href="#图床设置" class="headerlink" title="图床设置"></a>图床设置</h2><p>点击后台右上角设置按钮，进入设置页</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1689508911802.png" alt="设置页面"></p><p>滑动到图床设置部分：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1689508861897.png" alt="图床设置"></p><ul><li>图床类型: 远程 API</li><li>API 地址: <a href="https://sm.ms/api/v2/upload">https://sm.ms/api/v2/upload</a></li><li>POST 参数名: <code>smfile</code></li><li>JSON 路径: <code>data.url</code></li><li>自定义请求头: <code>&#123;&quot;Authorization&quot;: &quot;sm.ms 密钥&quot;&#125;</code></li></ul><blockquote><p>sm.ms 密钥在 <a href="https://smms.app/home/apitoken">https://smms.app/home/apitoken</a> 生成</p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/c5e7f4e67be8/</id>
    <link href="https://blog.zisheng.pro/posts/c5e7f4e67be8/"/>
    <published>2023-07-16T11:06:44.000Z</published>
    <summary>Qexo 是一个美观、强大的在线静态博客管理器</summary>
    <title>静态博客管理器 qexo 搭建指南</title>
    <updated>2026-07-12T09:21:36.911Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="工具" scheme="https://blog.zisheng.pro/categories/%E5%B7%A5%E5%85%B7/"/>
    <category term="mac" scheme="https://blog.zisheng.pro/tags/mac/"/>
    <category term="github" scheme="https://blog.zisheng.pro/tags/github/"/>
    <content>
      <![CDATA[<p>github 提交的账户信息包括两部分 ：<code>git commit</code> 时日志中记录的用户名和邮箱信息、<code>git push</code>提交到远程时的账号密码验证信息。必须两者同时切换才能实现完美的切换账号的目的</p><h2 id="删除钥匙串"><a href="#删除钥匙串" class="headerlink" title="删除钥匙串"></a>删除钥匙串</h2><p>打开”钥匙串访问“。在右上方搜索 git，找到所有 github 相关的账号信息。按删除键，删除所有名称为 github.com 的条目。放心，这个不会影响 Chrome 浏览器保存的用户名和密码。</p><h2 id="执行命令"><a href="#执行命令" class="headerlink" title="执行命令"></a>执行命令</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cp</span> ~/.gitconfig ~/.gitconfig_a</span><br><span class="line"><span class="built_in">cp</span> ~/.gitconfig ~/.gitconfig_b</span><br></pre></td></tr></table></figure><p>在 <code>gitconfig\_a</code> 和 <code>gitconfig\_b</code> 两个文件中分别填写提交日志的用户信息。</p><h2 id="切换账号"><a href="#切换账号" class="headerlink" title="切换账号"></a>切换账号</h2><p>需要用 <code>user\_a</code> 的时候：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cp</span> ~/.gitconfig_a ~/.gitconfig</span><br></pre></td></tr></table></figure><p>然后执行 <code>git push</code> 输入 <code>a</code> 的登录信息即可以提交。</p><h2 id="zsh-设置alias"><a href="#zsh-设置alias" class="headerlink" title="zsh 设置alias"></a>zsh 设置alias</h2><p>在 <code>~/.zshrc</code> 添加别名</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">alias</span> cg=<span class="string">&quot;cat ~/.gitconfig&quot;</span></span><br><span class="line"><span class="built_in">alias</span> cga=<span class="string">&quot;cp ~/.gitconfig_a ~/.gitconfig&quot;</span></span><br><span class="line"><span class="built_in">alias</span> cgb=<span class="string">&quot;cp ~/.gitconfig_b ~/.gitconfig&quot;</span></span><br></pre></td></tr></table></figure><p>然后保存后执行 <code>source ~/.zshrc</code> 使之生效。</p><p>以后每次想切换时即可先用 <code>cg</code> 命令查询当前账户，根据情况执行 <code>cga</code> 或 <code>cgb</code> 切换账户。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/5a54a3be5fa0/</id>
    <link href="https://blog.zisheng.pro/posts/5a54a3be5fa0/"/>
    <published>2023-07-06T21:39:05.142Z</published>
    <summary>本文详细讲述了如何在 Mac 环境下 切换 github 账号</summary>
    <title>Mac 切换 github 账号</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React/"/>
    <category term="React" scheme="https://blog.zisheng.pro/tags/React/"/>
    <content>
      <![CDATA[<p>React 是一种流行的 JavaScript 库，用于构建用户界面。自 React 在 2013 年首次发布以来，它已经成为了前端开发中不可或缺的一部分。在本文中，我们将按年回顾 React 的发展历史。</p><h2 id="童年"><a href="#童年" class="headerlink" title="童年"></a>童年</h2><p>React 最初是由 Facebook 的软件工程师 Jordan Walke 开发的。他在2010年为Facebook的广告平台开发了一种新的模型，这个模型可以根据用户的操作来动态地更新广告。这个模型后来演变成了React框架，并于2013年首次发布。</p><h2 id="幼儿园"><a href="#幼儿园" class="headerlink" title="幼儿园"></a>幼儿园</h2><h3 id="2013-年"><a href="#2013-年" class="headerlink" title="2013 年"></a>2013 年</h3><p>React 首次发布。它由 Facebook 的工程师们开发，旨在提高应用程序的性能和可维护性。React 的主要特点是虚拟 DOM，这是一种在内存中表示 DOM 的方式。通过使用虚拟 DOM，React 可以减少 DOM 操作的次数，从而提高应用程序的性能。</p><h3 id="2014-年"><a href="#2014-年" class="headerlink" title="2014 年"></a>2014 年</h3><p>React 发布了 0.12 版本。这个版本引入了 Mixins，这是一种在组件之间共享代码的方式。Mixins 很快成为了 React 中一个受欢迎的特性，但是在 2016 年被废弃了。</p><h3 id="2015-年"><a href="#2015-年" class="headerlink" title="2015 年"></a>2015 年</h3><p>React 发布了 0.14 版本。这个版本引入了 stateless 函数组件，这是一种更简单的组件形式，它没有状态或生命周期方法。stateless 函数组件很快成为了 React 中一个流行的特性，因为它们更易于测试和理解。</p><h2 id="小学"><a href="#小学" class="headerlink" title="小学"></a>小学</h2><h3 id="2016-年"><a href="#2016-年" class="headerlink" title="2016 年"></a>2016 年</h3><p>React 发布了 15.0 版本。这个版本引入了 PropTypes，这是一种用于验证组件属性的机制。PropTypes 很快成为了 React 中一个受欢迎的特性，因为它可以帮助开发人员捕获错误并提高代码质量。</p><h3 id="2017-年"><a href="#2017-年" class="headerlink" title="2017 年"></a>2017 年</h3><p>React 发布了 16.0 版本。这个版本引入了 Fiber，这是一个重新设计的渲染引擎，旨在提高 React 的性能和可维护性。Fiber 的主要特点是可以中断和恢复渲染过程，从而使 React 更加灵活和响应。</p><h3 id="2018-年"><a href="#2018-年" class="headerlink" title="2018 年"></a>2018 年</h3><p>React 发布了 16.3 版本。这个版本引入了 Context API，这是一种用于在组件之间共享数据的机制。Context API 可以帮助开发人员避免使用 prop drilling，并使代码更加简洁和可维护。</p><h3 id="2019-年"><a href="#2019-年" class="headerlink" title="2019 年"></a>2019 年</h3><p>React 发布了 16.8 版本。这个版本引入了 Hooks，这是一种用于在函数组件中使用状态和其他 React 功能的机制。Hooks 可以帮助开发人员编写更简洁、更易于理解和测试的代码。</p><h3 id="2020-年"><a href="#2020-年" class="headerlink" title="2020 年"></a>2020 年</h3><p>React 发布了 17.0 版本。这个版本没有引入太多新功能，而是主要关注向后兼容性和稳定性。React 17.0 的主要目标是使迁移更加容易，并为未来的版本打下基础。</p><p>总的来说，React 在过去的几年中经历了很多变化和进步。从虚拟 DOM 到 Hooks，每个版本都引入了新的功能和改进，并使 React 变得更加强大、灵活和易于使用。作为前端开发人员，我们可以期待着未来更多令人兴奋的变化和进步。</p><h3 id="2021-年"><a href="#2021-年" class="headerlink" title="2021 年"></a>2021 年</h3><ul><li>React 18: 它引入了许多新功能，例如自动批量更新、新的上下文API和React响应模式等。</li><li>React Server Components: React Server Components是一个实验性的功能，它允许React组件在服务器上渲染，从而提高性能和可靠性。</li></ul><h2 id="中学"><a href="#中学" class="headerlink" title="中学"></a>中学</h2><h3 id="2022-年"><a href="#2022-年" class="headerlink" title="2022 年"></a>2022 年</h3><p>2022 年的 React 更新包括发布了 React 18 正式版，其中包含了新的客户端渲染 API 和更新的服务端渲染 API，完全支持了服务端的 Suspense 和流式 SSR。</p><h3 id="2023-年"><a href="#2023-年" class="headerlink" title="2023 年"></a>2023 年</h3><ul><li>React 进阶版文档的发布，其中介绍了组件状态的保留和重置机制，以及不同位置渲染组件时状态的处理方式。</li><li>React 团队推出了一种新型组件——Server Components，它们可以提前运行，并且不包含在你的 JavaScript bundle 中。这些组件可以在构建过程中运行，让你从文件系统中读取或获取静态内容，也可以在服务器上运行，让你无需构建 API 即可访问数据层。</li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/36fe8cde4212/</id>
    <link href="https://blog.zisheng.pro/posts/36fe8cde4212/"/>
    <published>2023-06-02T08:48:23.000Z</published>
    <summary>2013年5月29日，React 迎来它的十周年纪念日。在这个值得纪念的日子里，我们回顾一下 React 的历史和发展。</summary>
    <title>React 发布 10 周年</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="工具" scheme="https://blog.zisheng.pro/categories/%E5%B7%A5%E5%85%B7/"/>
    <category term="运维" scheme="https://blog.zisheng.pro/categories/%E8%BF%90%E7%BB%B4/"/>
    <category term="云桌面" scheme="https://blog.zisheng.pro/tags/%E4%BA%91%E6%A1%8C%E9%9D%A2/"/>
    <category term="阿里云" scheme="https://blog.zisheng.pro/tags/%E9%98%BF%E9%87%8C%E4%BA%91/"/>
    <category term="腾讯云" scheme="https://blog.zisheng.pro/tags/%E8%85%BE%E8%AE%AF%E4%BA%91/"/>
    <category term="UCloud" scheme="https://blog.zisheng.pro/tags/UCloud/"/>
    <category term="亚马逊云科技" scheme="https://blog.zisheng.pro/tags/%E4%BA%9A%E9%A9%AC%E9%80%8A%E4%BA%91%E7%A7%91%E6%8A%80/"/>
    <category term="微软学习平台" scheme="https://blog.zisheng.pro/tags/%E5%BE%AE%E8%BD%AF%E5%AD%A6%E4%B9%A0%E5%B9%B3%E5%8F%B0/"/>
    <category term="小酷云电脑" scheme="https://blog.zisheng.pro/tags/%E5%B0%8F%E9%85%B7%E4%BA%91%E7%94%B5%E8%84%91/"/>
    <category term="无影云桌面" scheme="https://blog.zisheng.pro/tags/%E6%97%A0%E5%BD%B1%E4%BA%91%E6%A1%8C%E9%9D%A2/"/>
    <category term="云桌面服务" scheme="https://blog.zisheng.pro/tags/%E4%BA%91%E6%A1%8C%E9%9D%A2%E6%9C%8D%E5%8A%A1/"/>
    <category term="云桌面试用" scheme="https://blog.zisheng.pro/tags/%E4%BA%91%E6%A1%8C%E9%9D%A2%E8%AF%95%E7%94%A8/"/>
    <content>
      <![CDATA[<h2 id="什么是云桌面？"><a href="#什么是云桌面？" class="headerlink" title="什么是云桌面？"></a>什么是云桌面？</h2><p>云桌面是一种基于云计算技术的虚拟桌面解决方案，用户可以通过网络访问云端的桌面环境，实现随时随地、安全便捷地使用桌面应用程序和数据。</p><h2 id="云桌面有哪些优势？"><a href="#云桌面有哪些优势？" class="headerlink" title="云桌面有哪些优势？"></a>云桌面有哪些优势？</h2><p>云桌面作为一种新兴的云计算服务，具有以下几个优势：</p><ol><li>灵活性：云桌面可以随时随地通过网络访问，用户不必局限于特定的设备或地点，可以在不同设备上使用相同的桌面环境。</li><li>安全性：云桌面可以在云端进行管理和维护，用户无需担心数据泄露或设备安全问题。</li><li>成本效益：云桌面可以节省企业大量的硬件和软件投入，同时也可以降低维护和升级的成本。</li><li>高可用性：云桌面可以通过负载均衡和容错技术保证高可用性，避免单点故障导致的服务中断。</li></ol><p>总之，云桌面是一种高效、安全、灵活、易用的云计算服务，逐渐被越来越多的企业和个人所采用。</p><h2 id="云桌面需要什么样的硬件配置？"><a href="#云桌面需要什么样的硬件配置？" class="headerlink" title="云桌面需要什么样的硬件配置？"></a>云桌面需要什么样的硬件配置？</h2><p>云桌面和传统本地桌面的区别主要在于数据存储和处理位置不同。传统本地桌面的数据存储和处理都在本地计算机上进行，而云桌面的数据存储和处理则在云端服务器上进行。这意味着使用云桌面可以实现更高的灵活性和可扩展性，同时也可以更好地保护数据安全。另外，云桌面可以随时随地通过网络访问，而传统本地桌面则需要用户在本地计算机前操作。</p><p>一般来说，云桌面的硬件配置要求并不高，只需要具备以下基本配置即可：</p><ul><li>CPU：双核或以上</li><li>内存：4GB或以上</li><li>存储：50GB或以上</li></ul><p>此外，还需要具备稳定的网络环境以保证云桌面的流畅运行。当然，如果需要运行更加复杂的应用程序或者进行更加高强度的计算任务，那么相应的硬件配置也需要相应提高。</p><h2 id="云桌面如何保证数据安全性？"><a href="#云桌面如何保证数据安全性？" class="headerlink" title="云桌面如何保证数据安全性？"></a>云桌面如何保证数据安全性？</h2><p>云桌面可以通过以下方式保证数据安全性：</p><ol><li>数据加密：云桌面可以对数据进行加密，确保数据在传输和存储时不会被窃取或篡改。</li><li>访问控制：云桌面可以设置访问权限，只有授权的用户才能访问数据，从而确保数据不会被非授权人员访问。</li><li>安全备份：云桌面可以定期备份数据，以便在数据丢失或损坏时恢复数据。</li><li>安全审计：云桌面可以记录用户的操作日志，以便在发生安全事件时进行调查和追踪。</li><li>安全防护：云桌面可以部署安全防护措施，如防火墙、反病毒软件等，以保护数据不受攻击和病毒感染。</li></ol><h2 id="可以免费试用的云桌面服务"><a href="#可以免费试用的云桌面服务" class="headerlink" title="可以免费试用的云桌面服务"></a>可以免费试用的云桌面服务</h2><ul><li><a href="https://adfoc.us/81142995604187">阿里云无影云桌面</a>：可以快速构建安全、高性能、低成本的企业桌面办公体系。可以免费试用 3 个月。</li><li><a href="https://adfoc.us/81142995605674">Amazon WorkSpaces</a> ：WorkSpaces云电脑虚拟桌面，每月提供长达40小时的组合使用时长，可按月或按小时付费。</li><li><a href="https://adfoc.us/81142995606178">Microsoft Azure 虚拟桌面</a>：常用服务免费提供 12 个月、超过 55 项服务始终免费、USD200 的赠金，请在领取后 30 天内使用</li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/14e798bd7cdc/</id>
    <link href="https://blog.zisheng.pro/posts/14e798bd7cdc/"/>
    <published>2023-05-11T10:57:16.000Z</published>
    <summary>云桌面是一种基于云计算技术的虚拟桌面解决方案，用户可以通过网络访问云端的桌面环境，实现随时随地、安全便捷地使用桌面应用程序和数据。</summary>
    <title>云桌面是什么？2023 有哪些可以免费试用的云桌面服务？</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="站点运营" scheme="https://blog.zisheng.pro/categories/%E7%AB%99%E7%82%B9%E8%BF%90%E8%90%A5/"/>
    <category term="广告" scheme="https://blog.zisheng.pro/categories/%E7%AB%99%E7%82%B9%E8%BF%90%E8%90%A5/%E5%B9%BF%E5%91%8A/"/>
    <category term="广告" scheme="https://blog.zisheng.pro/tags/%E5%B9%BF%E5%91%8A/"/>
    <category term="RPM" scheme="https://blog.zisheng.pro/tags/RPM/"/>
    <category term="站点运营" scheme="https://blog.zisheng.pro/tags/%E7%AB%99%E7%82%B9%E8%BF%90%E8%90%A5/"/>
    <category term="网站运营" scheme="https://blog.zisheng.pro/tags/%E7%BD%91%E7%AB%99%E8%BF%90%E8%90%A5/"/>
    <category term="CPM" scheme="https://blog.zisheng.pro/tags/CPM/"/>
    <content>
      <![CDATA[<p>RPM（Revenue per mille）广告是指每千次广告展示可以获得的收入。它是根据广告的展示次数、点击率和广告主出价等因素计算出来的。</p><p>在数字化时代，互联网广告已经成为了企业营销的重要手段。而对于广告主来说，他们最关心的就是广告的效果，即广告投放所带来的收益。而每千次展示收入（RPM）就是一个衡量广告效果的指标。</p><p>RPM是指广告主在每千次展示中所获得的收入。它是通过将广告收入除以展示次数，再乘以 1000 得出的。这个指标可以帮助广告主更好地了解他们的广告效果，并作出相应的调整。</p><p>RPM是一个非常重要的指标，因为它可以帮助广告主了解他们的广告投放是否能够带来足够的收益。如果RPM较低，可能意味着广告主需要重新考虑他们的广告策略，或者优化他们的广告内容，以提高点击率和转化率。</p><p>另外，RPM还可以帮助广告主评估不同媒体之间的效果差异。通过比较不同媒体的RPM，广告主可以更好地了解哪些媒体对他们的广告投放最有效，并将更多的预算投入到这些媒体上。</p><p>但是，需要注意的是，RPM并不是衡量广告效果的唯一指标。其他指标，如点击率、转化率、ROI等也同样重要。因此，在评估广告效果时，需要综合考虑多个指标，并结合实际情况进行分析。</p><p>总之，每千次展示收入（RPM）是一个非常重要的指标，可以帮助广告主更好地了解他们的广告效果，并作出相应的调整。但是需要注意的是，它并不是衡量广告效果的唯一指标，需要综合考虑多个指标，并结合实际情况进行分析。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/06449303494e/</id>
    <link href="https://blog.zisheng.pro/posts/06449303494e/"/>
    <published>2023-05-11T06:12:57.000Z</published>
    <summary>RPM（Revenue per mille）广告是指每千次广告展示可以获得的收入。它是根据广告的展示次数、点击率和广告主出价等因素计算出来的。</summary>
    <title>RPM 广告是什么?</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="VS Code" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/VS-Code/"/>
    <category term="vscode" scheme="https://blog.zisheng.pro/tags/vscode/"/>
    <category term="光标" scheme="https://blog.zisheng.pro/tags/%E5%85%89%E6%A0%87/"/>
    <content>
      <![CDATA[<p>这是一种非常方便的操作方法，特别是在需要批量添加字符的时候。以下是这个方法的详细步骤：</p><ol><li>首先，在 Mac 上打开文本编辑器或其他编程工具。</li><li>选中你想要添加字符的多行文本。</li><li>按住 <kbd>shift</kbd> 和 <kbd>option</kbd> 键，并用鼠标点击其中一行的行首或行尾。</li><li>这样就可以在所有选中的行的行首或行尾添加同样的字符了。</li></ol><kbd>shift</kbd> 和 <kbd>option</kbd> 键需要同时按住，且在点击行首或行尾之前不要释放它们。<p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1683688081979.png" alt="vscode 多行光标"></p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1683688107699.png" alt="vscode 多行光标"></p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/bb4c7efe8b3e/</id>
    <link href="https://blog.zisheng.pro/posts/bb4c7efe8b3e/"/>
    <published>2023-05-10T03:06:14.000Z</published>
    <summary>这是一种方便的方法，可用于在 Mac 上批量添加字符。选中多行文本后，按住 shift + option 键，然后点击其中一行的行首或行尾即可实现。</summary>
    <title>vscode 选择多行，将光标定位至每行行首或行尾</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="Gatsby" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/Gatsby/"/>
    <category term="React" scheme="https://blog.zisheng.pro/tags/React/"/>
    <category term="TypeScript" scheme="https://blog.zisheng.pro/tags/TypeScript/"/>
    <category term="Gatsby" scheme="https://blog.zisheng.pro/tags/Gatsby/"/>
    <content>
      <![CDATA[<p>在这篇文章中，我们将逐步了解如何向我们的 gatsby 站点添加导入别名。</p><p>我们设置导入别名的原因更多是为了代码的可读性和导入组件时代码外观的美观。</p><p>我的意思是，如果我们看一下以下示例：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">Subscribe</span> <span class="keyword">from</span> <span class="string">&#x27;../../../../../../../core/modules/newsletter/mixins/Subscribe&#x27;</span></span><br></pre></td></tr></table></figure><p>在我看来，这样看起来非常丑陋，那么我们该如何改进呢？</p><p>我们可以通过更新 webpack 配置来包含针对我们已知将成为组件基础的主目录的别名来实现这一点。</p><p>一旦我们完成了这篇文章中的所有步骤，最终结果将如下所示：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">Subscribe</span> <span class="keyword">from</span> <span class="string">&#x27;@/core/modules/newsletter/mixins/Subscribe&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="给-Gatsby-添加依赖"><a href="#给-Gatsby-添加依赖" class="headerlink" title="给 Gatsby 添加依赖"></a>给 Gatsby 添加依赖</h2><p>考虑到 Gatsby 使用 Webpack 作为其核心，并且默认情况下不会公开配置，因此我们可以使用 Gatsby 的 <code>onCreateWebpackConfig</code> API 添加自定义 Webpack 配置，这将导致自定义配置被合并，使您能够修改默认的 webpack 配置。</p><p>要添加自定义Webpack配置，我们需要编辑（或在项目根目录中创建文件，如果不存在）gatsby-node.js，并将新的配置添加到其中。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">&quot;path&quot;</span>);</span><br><span class="line"><span class="built_in">exports</span>.<span class="property">onCreateWebpackConfig</span> = <span class="function">(<span class="params">&#123; actions &#125;</span>) =&gt;</span> &#123;</span><br><span class="line">  actions.<span class="title function_">setWebpackConfig</span>(&#123;</span><br><span class="line">    <span class="attr">resolve</span>: &#123;</span><br><span class="line">      <span class="attr">alias</span>: &#123;</span><br><span class="line">        <span class="string">&quot;@/components&quot;</span>: path.<span class="title function_">resolve</span>(__dirname, <span class="string">&quot;src/components&quot;</span>),</span><br><span class="line">        <span class="string">&quot;@/static&quot;</span>: path.<span class="title function_">resolve</span>(__dirname, <span class="string">&quot;static&quot;</span>)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>正如我们在上面的代码片段中看到的那样，actions 对象提供了使用 <code>setWebpackConfig</code> 选项的选项，该选项接受我们的自定义 webpack 配置并将其合并到 Gatsby 的 webpack 配置中。</p><p>为了添加新的别名，我们将使用 webpack 的 <code>resolve.alias</code>，这将允许我们在组件内部使用新创建的导入别名。</p><p>如下所示，在添加新别名后的最终结果将如下所示：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">Layout</span> <span class="keyword">from</span> <span class="string">&#x27;@components/Layout&#x27;</span>;</span><br></pre></td></tr></table></figure><h2 id="解析-ESlint-import"><a href="#解析-ESlint-import" class="headerlink" title="解析 ESlint import"></a>解析 ESlint import</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">settings</span>: &#123;</span><br><span class="line">    <span class="string">&#x27;import/parsers&#x27;</span>: &#123;</span><br><span class="line">      <span class="string">&#x27;@typescript-eslint/parser&#x27;</span>: [<span class="string">&#x27;.ts&#x27;</span>],</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="string">&#x27;import/resolver&#x27;</span>: &#123;</span><br><span class="line">      <span class="comment">// use &lt;root&gt;/tsconfig.json</span></span><br><span class="line">      <span class="attr">typescript</span>: &#123;</span><br><span class="line">        <span class="attr">alwaysTryTypes</span>: <span class="literal">true</span>, <span class="comment">// always try to resolve types under `&lt;roo/&gt;@types` directory even it doesn&#x27;t contain any source code, like `@types/unist`</span></span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Typescript"><a href="#Typescript" class="headerlink" title="Typescript"></a>Typescript</h2><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;compilerOptions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;baseUrl&quot;</span><span class="punctuation">:</span> <span class="string">&quot;./&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;paths&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;@/components/*&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;src/components/*&quot;</span><span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/d36cd5be8989/</id>
    <link href="https://blog.zisheng.pro/posts/d36cd5be8989/"/>
    <published>2023-05-08T10:33:33.000Z</published>
    <summary>在这篇文章中，我们将逐步了解如何向我们的 gatsby 站点添加导入别名。</summary>
    <title>如何设置 Gatsby 绝对路径引用别名</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React/"/>
    <category term="React" scheme="https://blog.zisheng.pro/tags/React/"/>
    <category term="TypeScript" scheme="https://blog.zisheng.pro/tags/TypeScript/"/>
    <category term="compilerOptions" scheme="https://blog.zisheng.pro/tags/compilerOptions/"/>
    <content>
      <![CDATA[<p>本文讲解了如何设置 React Typescript 项目绝对路径引用别名</p><h2 id="craco"><a href="#craco" class="headerlink" title="craco"></a>craco</h2><p>1、安装修改 CRA 配置的包：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add @craco/craco</span><br></pre></td></tr></table></figure><p>2、在项目根目录中创建 craco 的配置文件：<code>craco.config.js</code>，并在配置文件中配置路径别名</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">&#x27;path&#x27;</span>)</span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">webpack</span>: &#123;</span><br><span class="line">    <span class="attr">alias</span>: &#123;</span><br><span class="line">      <span class="comment">// 约定：使用 @ 表示 src 文件所在路径</span></span><br><span class="line">      <span class="string">&#x27;@&#x27;</span>: path.<span class="title function_">resolve</span>(__dirname, <span class="string">&#x27;src&#x27;</span>),</span><br><span class="line">      <span class="comment">// 约定：使用 @scss 表示 样式 文件所在路径</span></span><br><span class="line">      <span class="string">&#x27;@scss&#x27;</span>: path.<span class="title function_">resolve</span>(__dirname, <span class="string">&#x27;src&#x27;</span>, <span class="string">&#x27;assets&#x27;</span>, <span class="string">&#x27;styles&#x27;</span>)</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>3、修改 package.json 中的脚本命令</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="comment">// 将 start/build/test 三个命令修改为 craco 方式</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;start&quot;</span><span class="punctuation">:</span> <span class="string">&quot;craco start&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;build&quot;</span><span class="punctuation">:</span> <span class="string">&quot;craco build&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;test&quot;</span><span class="punctuation">:</span> <span class="string">&quot;craco test&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;eject&quot;</span><span class="punctuation">:</span> <span class="string">&quot;react-scripts eject&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="Typescript"><a href="#Typescript" class="headerlink" title="Typescript"></a>Typescript</h2><p>1、创建 path.tsconfig.json 配置文件</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;compilerOptions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;baseUrl&quot;</span><span class="punctuation">:</span> <span class="string">&quot;./&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;paths&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;@/*&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;src/*&quot;</span><span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>2、在 <code>tsconfig.json</code> 中导入该配置文件，让配置生效</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="comment">// 导入配置文件</span></span><br><span class="line">  <span class="attr">&quot;extends&quot;</span><span class="punctuation">:</span> <span class="string">&quot;./path.tsconfig.json&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>配置完这一步，vscode 也会有目录提示了</p><h2 id="Eslint"><a href="#Eslint" class="headerlink" title="Eslint"></a>Eslint</h2><p>配置 <code>@typescript-eslint/parser</code> 并在 <code>parserOptions</code> 中指定 <code>project</code> 选项，以便在 Eslint 中使用 TypeScript 的类型检查。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">parser</span>: <span class="string">&#x27;@typescript-eslint/parser&#x27;</span>,</span><br><span class="line">  <span class="attr">parserOptions</span>: &#123;</span><br><span class="line">    <span class="attr">project</span>: [<span class="string">&#x27;./tsconfig.json&#x27;</span>], <span class="comment">// Specify it only for TypeScript files</span></span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/94e8b5cabf01/</id>
    <link href="https://blog.zisheng.pro/posts/94e8b5cabf01/"/>
    <published>2023-05-08T10:18:10.000Z</published>
    <summary>本文讲解了如何设置 React Typescript 项目绝对路径引用别名</summary>
    <title>如何设置 React Typescript 项目绝对路径引用别名</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React-Native/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/tags/React-Native/"/>
    <category term="TypeScript" scheme="https://blog.zisheng.pro/tags/TypeScript/"/>
    <category term="export" scheme="https://blog.zisheng.pro/tags/export/"/>
    <category term="全局类型" scheme="https://blog.zisheng.pro/tags/%E5%85%A8%E5%B1%80%E7%B1%BB%E5%9E%8B/"/>
    <category term="global" scheme="https://blog.zisheng.pro/tags/global/"/>
    <content>
      <![CDATA[<p>要在 TypeScript 中声明全局类型：</p><ol><li>创建一个 <code>global.d.ts</code> 文件并在全局命名空间中声明类型。</li><li>添加需要全局访问的类型或接口。</li><li>使用 <code>export {}</code> 使文件成为模块。</li></ol><p>在项目的 src 目录中，创建一个包含以下 <code>global.d.ts</code> 文件的 <code>types</code> 目录。</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> &#123;&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">declare</span> <span class="variable language_">global</span> &#123;</span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 现在声明进入全局命名空间的类型，或者增加全局命名空间中的现有声明。</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="keyword">interface</span> <span class="title class_">Employee</span> &#123;</span><br><span class="line">    <span class="attr">id</span>: <span class="built_in">number</span>;</span><br><span class="line">    <span class="attr">name</span>: <span class="built_in">string</span>;</span><br><span class="line">    <span class="attr">salary</span>: <span class="built_in">number</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">type</span> <span class="title class_">Person</span> = &#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="built_in">string</span>;</span><br><span class="line">    <span class="attr">age</span>: <span class="built_in">number</span>;</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面的例子展示了如何创建一个修改全局命名空间的模块。 我们创建了一个全局可访问的 Employee 和 Person 类型。</p><p>现在我可以访问我项目中的类型，而无需导入它们。</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="attr">emp</span>: <span class="title class_">Employee</span> = &#123;</span><br><span class="line">  <span class="attr">id</span>: <span class="number">1</span>,</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;James&#x27;</span>,</span><br><span class="line">  <span class="attr">salary</span>: <span class="number">100</span>,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(emp);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="attr">person</span>: <span class="title class_">Person</span> = &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;Tom&#x27;</span>,</span><br><span class="line">  <span class="attr">age</span>: <span class="number">30</span>,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(person);</span><br></pre></td></tr></table></figure><p>如果我们在 IDE 中遇到错误，请尝试将类型目录的路径添加到 <code>tsconfig.json</code> 文件中。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;compilerOptions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;typeRoots&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;./types&quot;</span><span class="punctuation">,</span> <span class="string">&quot;./node_modules/@types&quot;</span><span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>我们在 <code>global.d.ts</code> 文件中使用 <code>export {}</code> 行将其标记为外部模块。 模块是包含至少 1 个导入或导出语句的文件。 我们必须这样做才能扩大全局范围。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/d46ebc530d48/</id>
    <link href="https://blog.zisheng.pro/posts/d46ebc530d48/"/>
    <published>2023-05-08T10:10:31.000Z</published>
    <summary>本文讲解了如何在 React Native 中声明全局类型</summary>
    <title>如何在 React Native 中声明全局类型</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="issues" scheme="https://blog.zisheng.pro/categories/issues/"/>
    <category term="antd" scheme="https://blog.zisheng.pro/categories/issues/antd/"/>
    <category term="antd" scheme="https://blog.zisheng.pro/tags/antd/"/>
    <category term="datepicker" scheme="https://blog.zisheng.pro/tags/datepicker/"/>
    <category term="date.clone" scheme="https://blog.zisheng.pro/tags/date-clone/"/>
    <content>
      <![CDATA[<p>本文介绍了 Antd <DatePicker /> 组件报 date.clone is not a function 错误的解决方法</p><p>当我们给 Antd DatePicker 组件设置默认值或通过 <code>form.setFieldsValue</code> 设置值时，可能会遇到如下错误：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;DatePicker /&gt; date.clone is not a <span class="keyword">function</span></span><br></pre></td></tr></table></figure><p>这是因为 Antd DatePicker 组件的默认值必须是 moment 对象，而我们设置的值可能是字符串或者 Date 对象，所以需要将其转换为 moment 对象：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1683531634144.png" alt="&lt;DatePicker &#x2F;&gt; date.clone is not a function"></p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/f6ee9e277c41/</id>
    <link href="https://blog.zisheng.pro/posts/f6ee9e277c41/"/>
    <published>2023-05-08T07:36:04.000Z</published>
    <summary>
      <![CDATA[本文介绍了 Antd <DatePicker /> 组件报 date.clone is not a function 错误的解决方法]]>
    </summary>
    <title>
      <![CDATA[Antd <DatePicker /> date.clone is not a function]]>
    </title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="Google" scheme="https://blog.zisheng.pro/categories/Google/"/>
    <category term="Adsense" scheme="https://blog.zisheng.pro/categories/Google/Adsense/"/>
    <category term="站点运营" scheme="https://blog.zisheng.pro/categories/%E7%AB%99%E7%82%B9%E8%BF%90%E8%90%A5/"/>
    <category term="广告" scheme="https://blog.zisheng.pro/categories/%E7%AB%99%E7%82%B9%E8%BF%90%E8%90%A5/%E5%B9%BF%E5%91%8A/"/>
    <category term="Adsense" scheme="https://blog.zisheng.pro/tags/Adsense/"/>
    <category term="Google" scheme="https://blog.zisheng.pro/tags/Google/"/>
    <category term="W-8BEN" scheme="https://blog.zisheng.pro/tags/W-8BEN/"/>
    <content>
      <![CDATA[<p>本文讲解了如何填写 Google Adsense 税务信息可以免税，以及如何填写 W-8 BEN 表格。</p><h2 id="开始填表"><a href="#开始填表" class="headerlink" title="开始填表"></a>开始填表</h2><p>以此点击 <code>收款</code> -&gt; <code>收款信息</code> -&gt; <code>美国税务信息</code> -&gt; <code>开始填表</code></p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1683361664395.png" alt="Google Adsense 税务信息"><br><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1683361691097.png" alt="Google Adsense 税务信息"><br><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1683361711328.png" alt="Google Adsense 税务信息"></p><h2 id="基本信息"><a href="#基本信息" class="headerlink" title="基本信息"></a>基本信息</h2><ol><li>您使用的是哪种类型的账号？：个人</li><li>您是否是美国公民或居民？：否</li><li>请选择 W-8BEN：选择</li></ol><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1683361782277.png" alt="Google Adsense 税务信息"></p><h2 id="W-8BEN-表单"><a href="#W-8BEN-表单" class="headerlink" title="W-8BEN 表单"></a>W-8BEN 表单</h2><p>按照下列图中的信息填写即可，需要注意的有：</p><ol><li>个人姓名填写拼音大写，比如 <code>LI MEI</code></li><li>外国的纳税人税号填写身份证号</li><li>报税选择无纸化</li></ol><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1683362043300.png" alt="Google Adsense 税务信息"><br><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1683362207801.png" alt="Google Adsense 税务信息"><br><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1683362383338.png" alt="Google Adsense 税务信息"><br><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1683362398811.png" alt="Google Adsense 税务信息"><br><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1683362490608.png" alt="Google Adsense 税务信息"><br><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1683362505420.png" alt="Google Adsense 税务信息"></p><h2 id="被接受通知"><a href="#被接受通知" class="headerlink" title="被接受通知"></a>被接受通知</h2><p>如下图，显示“您的税务信息已被接受”，说明设置成功了。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1683363578326.png" alt="Google Adsense 税务信息"></p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/66ec8687cd46/</id>
    <link href="https://blog.zisheng.pro/posts/66ec8687cd46/"/>
    <published>2023-05-06T11:47:53.000Z</published>
    <summary>本文讲解了如何填写 Google Adsense 税务信息可以免税，以及如何填写 W-8 BEN 表格。</summary>
    <title>如何填写 Google Adsense 税务信息可以免税 | W-8BEN</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="Google" scheme="https://blog.zisheng.pro/categories/Google/"/>
    <category term="Adsense" scheme="https://blog.zisheng.pro/categories/Google/Adsense/"/>
    <category term="站点运营" scheme="https://blog.zisheng.pro/categories/%E7%AB%99%E7%82%B9%E8%BF%90%E8%90%A5/"/>
    <category term="广告" scheme="https://blog.zisheng.pro/categories/%E7%AB%99%E7%82%B9%E8%BF%90%E8%90%A5/%E5%B9%BF%E5%91%8A/"/>
    <category term="Adsense" scheme="https://blog.zisheng.pro/tags/Adsense/"/>
    <category term="内容变现" scheme="https://blog.zisheng.pro/tags/%E5%86%85%E5%AE%B9%E5%8F%98%E7%8E%B0/"/>
    <category term="博客变现" scheme="https://blog.zisheng.pro/tags/%E5%8D%9A%E5%AE%A2%E5%8F%98%E7%8E%B0/"/>
    <category term="账号注册" scheme="https://blog.zisheng.pro/tags/%E8%B4%A6%E5%8F%B7%E6%B3%A8%E5%86%8C/"/>
    <category term="中国" scheme="https://blog.zisheng.pro/tags/%E4%B8%AD%E5%9B%BD/"/>
    <category term="大陆" scheme="https://blog.zisheng.pro/tags/%E5%A4%A7%E9%99%86/"/>
    <category term="Google" scheme="https://blog.zisheng.pro/tags/Google/"/>
    <category term="google adsense" scheme="https://blog.zisheng.pro/tags/google-adsense/"/>
    <content>
      <![CDATA[<p>Google Adsense 是一个由 Google 公司设置的广告计划，会员可以利用 Youtube 流量和 Blogspot 功能置入广告服务，以赚取佣金。会员可分得搜索广告收益的 51%、内容广告收益的 68%。本文将介绍如何注册 Google Adsense 账户。</p><h2 id="前提"><a href="#前提" class="headerlink" title="前提"></a>前提</h2><ul><li>一个可以访问 Google 的代理，你可以在 <a href="https://blog.zisheng.pro/vpn/">科学上网工具</a> 中选择一款</li><li>一个 Google 账号，可以参考 <a href="https://blog.zisheng.pro/382c92b68823/">2023 大陆人如何成功创建 Google 账户</a> 进行注册。</li></ul><h2 id="扬帆启航"><a href="#扬帆启航" class="headerlink" title="扬帆启航"></a>扬帆启航</h2><p>当你决定做一件事，就已经成功了一半，现在就让我们从点击 <a href="https://adsense.google.com/adsense/signup/create?referer=https://adsense.google.com/start/&sac=true&pli=1&authuser=0">Google AdSense 注册</a> 开始内容变现之路吧。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1682570658242.png" alt="Google AdSense 注册"></p><ul><li>您的网站：如果没有可以先不填写</li><li>让 AdSense 发挥更大作用：可选可不选</li><li>您的收款地址所在的国家&#x2F;地区：中国大陆可以设置免税，就选中国大陆</li><li>我们的条款及条件：勾选</li></ul><p>点击确认之后，就会进入到控制台：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1682571240158.png" alt="Google AdSense 注册"></p><h2 id="填写客户信息"><a href="#填写客户信息" class="headerlink" title="填写客户信息"></a>填写客户信息</h2><div class="note primary modern"><p>注意：这里的信息涉及以后得收款，一定要要仔细看下面的步骤，否则可能影响收款。</p></div><p>点击上一步的“请输入信息”卡片，进入到填写客户信息页面：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1682571783990.png" alt="Google AdSense 注册"></p><ul><li>账号类型：个人</li><li>姓名和地址：这里就填写真实的就可以，要注意的是一点要 <strong>在地址第 1 行或者地址第 2 行 中备注上电话</strong>，因为账号收入达到 5 美金之后 Google 就会给你寄一个用于验证身份的 PIN 码，由于是通过国际平信的方式发出，所以不会有电话，而你备注在地址里，邮递员就可以看到了。</li></ul><p>点击确认之后返回首页看到成功的提示就可以了。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1682572039101.png" alt="Google AdSense 注册"></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>内容变现是一个需要漫长积累的事情，你可能会在这个过程中遇到很多困难，但是只要你坚持下去，就一定会成功的。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/5c6d54a112a6/</id>
    <link href="https://blog.zisheng.pro/posts/5c6d54a112a6/"/>
    <published>2023-04-27T04:36:47.000Z</published>
    <summary>Google Adsense 是一个由 Google 公司设置的广告计划，会员可以利用 Youtube 流量和 Blogspot 功能置入广告服务，以赚取佣金。会员可分得搜索广告收益的51%、内容广告收益的68%。本文将介绍如何注册 Google Adsense 账户。</summary>
    <title>2023 Google Adsense 中国大陆账户注册教程</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="微信" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/%E5%BE%AE%E4%BF%A1/"/>
    <category term="微信" scheme="https://blog.zisheng.pro/tags/%E5%BE%AE%E4%BF%A1/"/>
    <category term="oAuth" scheme="https://blog.zisheng.pro/tags/oAuth/"/>
    <category term="网页授权" scheme="https://blog.zisheng.pro/tags/%E7%BD%91%E9%A1%B5%E6%8E%88%E6%9D%83/"/>
    <category term="微信网页授权" scheme="https://blog.zisheng.pro/tags/%E5%BE%AE%E4%BF%A1%E7%BD%91%E9%A1%B5%E6%8E%88%E6%9D%83/"/>
    <category term="微信公众号" scheme="https://blog.zisheng.pro/tags/%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7/"/>
    <content>
      <![CDATA[<p>如果用户在微信客户端中访问第三方网页，公众号可以通过微信网页授权机制，来获取用户基本信息，进而实现业务逻辑。</p><h2 id="网页授权回调域名"><a href="#网页授权回调域名" class="headerlink" title="网页授权回调域名"></a>网页授权回调域名</h2><p>1、在微信公众号请求用户网页授权之前，开发者需要先到公众平台官网中的 <code>开发</code> → <code>接口权限</code> → <code>网页服务</code> → <code>网页授权</code> - <code>网页授权获取用户基本信息</code> 的配置选项中，修改授权回调域名。请注意，这里填写的是域名（是一个字符串），而不是 URL，因此请勿加 <code>http://</code> 等协议头。</p><div class="note info modern"><p>注意：通过 <code>设置</code> → <code>公众号设置</code> → <code>功能设置</code> → <code>网页授权域名</code></p></div><p>2、授权回调域名配置规范为全域名，比如需要网页授权的域名为：<code>www.qq.com</code>，配置以后此域名下面的页面 <code>http://www.qq.com/music.html</code>、<code>http://www.qq.com/login.html</code> 都可以进行 OAuth2.0 鉴权。但 <code>http://pay.qq.com</code>、<code>http://music.qq.com</code>、<code>http://qq.com</code> 无法进行 OAuth2.0 鉴权</p><p>3、如果公众号登录授权给了第三方开发者来进行管理，则不必做任何设置，由第三方代替公众号实现网页授权即可</p><h2 id="业务域名"><a href="#业务域名" class="headerlink" title="业务域名"></a>业务域名</h2><p>设置业务域名后，在微信内访问该域名下页面时，不会被重新排版。用户在该域名上进行输入时，不出现下图所示的安全提示。<br>注意事项：</p><p>1、可填写三个域名或路径（例：<code>wx.qq.com</code> 或 <code>wx.qq.com/mp</code>），需使用字母、数字及 <code>-</code> 的组合，不支持 IP 地址、端口号及短链域名。</p><p>2、填写的域名须通过ICP备案的验证。</p><p>3、将文件 <code>MP_verify_naGVGuiJ6KOsOwCA.txt</code>（点击下载）上传至填写域名或路径指向的 web 服务器（或虚拟主机）的目录（若填写域名，将文件放置在域名根目录下，例如 <code>wx.qq.com/MP_verify_naGVGuiJ6KOsOwCA.txt</code>；若填写路径，将文件放置在路径目录下，例如 <code>wx.qq.com/mp/MP_verify_naGVGuiJ6KOsOwCA.txt</code>），并确保可以访问。</p><p>4、一个自然月内最多可修改并保存三次，本月剩余保存次数：3</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/f6ae9b237db3/</id>
    <link href="https://blog.zisheng.pro/posts/f6ae9b237db3/"/>
    <published>2023-04-27T03:02:56.000Z</published>
    <summary>本文讲述了如果用户在微信客户端中访问第三方网页，公众号可以通过微信网页授权机制，来获取用户基本信息，进而实现业务逻辑。</summary>
    <title>微信 oAuth 网页授权流程</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React-Native/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/tags/React-Native/"/>
    <category term="Android SDK" scheme="https://blog.zisheng.pro/tags/Android-SDK/"/>
    <content>
      <![CDATA[<p>在 React Native 开发中，如果一个原生 SDK 没有适配 React Native 的插件，原则上我们不推荐你使用。但是万不得已的情况下，我们有办法吗？答案是肯定的。步骤大致分为集成 SDK、编写桥接代码。知难行易，本文就是顺着这个思路来解决让前端工程师脑壳疼的集成第三方 SDK 并编写桥接代码的问题。</p><h2 id="预备知识"><a href="#预备知识" class="headerlink" title="预备知识"></a>预备知识</h2><p>在我们开发安卓项目的时候，不会所有的功能都自己去造轮子，经常要使用到各种的其他包，其中有谷歌给我们提供的各种 <code>support</code> 包，也有各种第三方的功能库，有时候我们自己也会将一些功能封装成包。这些包存在和导入的形式也多种多样，有远程仓库的，有直接拷贝到本地的，<code>jar</code> 包、<code>aar</code> 包、<code>so</code> 包等。所幸我们都可以在主工程和各个 <code>Module</code> 的 <code>build.gradle</code> 里进行统一管理。— <a href="http://t.cn/Ai9T02Jq">Android 依赖导入全攻略</a></p><h3 id="依赖引入方式"><a href="#依赖引入方式" class="headerlink" title="依赖引入方式"></a>依赖引入方式</h3><p><code>Android Gradle plugin 3.0</code> 几个引入依赖的方法：</p><p><strong>implementation</strong></p><p>对于使用了该命令编译的依赖，对该项目有依赖的项目将无法访问到使用该命令编译的依赖中的任何程序，也就是将该依赖隐藏在内部，而不对外部公开。<code>react-native link</code> 命令即使用该方式</p><p>使用 <code>implementation</code> 会使编译速度有所增快：比如我在一个 <code>library</code> 中使用 <code>implementation</code> 依赖了 <code>gson</code> 库，然后我的主项目依赖了 <code>library</code>，那么，我的主项目就无法访问 <code>gson</code> 库中的方法。这样的好处是编译速度会加快，我换了一个版本的 <code>Gson</code> 库，但只要 <code>library</code> 的代码不改动，就不会重新编译主项目的代码。</p><p><strong>api</strong></p><p>等同于 compile 指令</p><p><strong>compileOnly</strong></p><p>等同于 <code>provided</code>，只在编译时有效，不会参与打包，不会包含到 <code>apk</code> 文件中。可以用来解决重复导入库的冲突。</p><h2 id="远程仓库依赖"><a href="#远程仓库依赖" class="headerlink" title="远程仓库依赖"></a>远程仓库依赖</h2><blockquote><p>这里我们以 LeanCloud Android SDK 的引入来演示</p></blockquote><p>引入远程仓库依赖是很方便的，但在之前我们需要在项目根目录的 <code>build.gradle</code> 声明远程仓库的地址。</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">buildscript &#123;</span><br><span class="line">    repositories &#123;</span><br><span class="line">        jcenter()</span><br><span class="line"><span class="addition">+        //这里是 LeanCloud 的包仓库</span></span><br><span class="line"><span class="addition">+        maven &#123;</span></span><br><span class="line"><span class="addition">+            url &quot;http://mvn.leancloud.cn/nexus/content/repositories/public&quot;</span></span><br><span class="line"><span class="addition">+        &#125;</span></span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">    dependencies &#123;</span><br><span class="line">        classpath &#x27;com.android.tools.build:gradle:1.0.0&#x27;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">allprojects &#123;</span><br><span class="line">    repositories &#123;</span><br><span class="line">        jcenter()</span><br><span class="line"><span class="addition">+        //这里是 LeanCloud 的包仓库</span></span><br><span class="line"><span class="addition">+        maven &#123;</span></span><br><span class="line"><span class="addition">+            url &quot;http://mvn.leancloud.cn/nexus/content/repositories/public&quot;</span></span><br><span class="line"><span class="addition">+        &#125;</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后打开 <code>app</code> 目录下的 <code>build.gradle</code> 进行如下配置：</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">android &#123;</span><br><span class="line"><span class="addition">+    //为了解决部分第三方库重复打包了META-INF的问题</span></span><br><span class="line"><span class="addition">+    packagingOptions&#123;</span></span><br><span class="line"><span class="addition">+        exclude &#x27;META-INF/LICENSE.txt&#x27;</span></span><br><span class="line"><span class="addition">+        exclude &#x27;META-INF/NOTICE.txt&#x27;</span></span><br><span class="line"><span class="addition">+    &#125;</span></span><br><span class="line">    lintOptions &#123;</span><br><span class="line">        abortOnError false</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">dependencies &#123;</span><br><span class="line">    compile (&#x27;com.android.support:support-v4:21.0.3&#x27;)</span><br><span class="line"></span><br><span class="line"><span class="addition">+    // LeanCloud 基础包</span></span><br><span class="line"><span class="addition">+    compile (&#x27;cn.leancloud.android:avoscloud-sdk:4.7.10&#x27;)</span></span><br><span class="line"><span class="addition">+</span></span><br><span class="line"><span class="addition">+    // 推送与即时通讯需要的包</span></span><br><span class="line"><span class="addition">+    compile (&#x27;cn.leancloud.android:avoscloud-push:4.7.10@aar&#x27;)&#123;transitive = true&#125;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="本地依赖"><a href="#本地依赖" class="headerlink" title="本地依赖"></a>本地依赖</h2><blockquote><p>使用 Android Studio 的同学请参考: <a href="http://t.cn/Ai9TFsHH">Android Studio 引入 jar 包和 so 文件（armeabi 和 armeabi-v7a）</a></p></blockquote><h3 id="jar-包"><a href="#jar-包" class="headerlink" title="jar 包"></a>jar 包</h3><p>1、将 <code>jar</code> 文件复制、粘贴到 <code>app/libs</code> 目录中，React Native 默认没有该文件夹，清新建一个</p><p>2、打开 <code>app/build.gradle</code>，进行如下配置以列出包含 <code>jar</code> 包的文件夹路径。</p><blockquote><p>注意：React Native 默认已经进行了这个配置</p></blockquote><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">dependencies &#123;</span><br><span class="line">    implementation fileTree(dir: &quot;libs&quot;, include: [&quot;*.jar&quot;])</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>和远程仓库依赖引入方式不同，如果本地同时存在两个不同的 <code>jar</code> 包，或者本地已有 <code>jar</code> 包，再去远程依赖不同版本的 <code>jar</code> 包，就会报错。</p><p><img src="https://i.loli.net/2019/06/05/5cf73b7ea3e2b53276.jpg" alt="紫升"></p><p>解决方式：将其中的一个采用 <code>compileOnly</code> 替换 <code>implementation</code>。顾名思义，<code>compileOnly</code> 只在编译时起作用，不会包含到 <code>APK</code> 里面，在运行时也就避免找到重复的类了。</p><h3 id="aar-包"><a href="#aar-包" class="headerlink" title="aar 包"></a>aar 包</h3><p><code>arr</code> 全称是 <code>Andorid Archive</code>,是一个 Android 库项目的二进制归档文件，使用 Android Studio ，非常简单可以生成一个 AAR 文件。</p><p>和 <code>jar</code> 包不同，<code>aar</code> 包存放的路径声明和依赖引入是分开的：</p><p>1、将 <code>aar</code> 包复制到 <code>lib</code> 目录下</p><p>2、在项目根目录 <code>build.gradle</code>中声明 <code>aar</code> 文件存放路径</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">buildscript &#123;</span><br><span class="line">  repositories &#123;</span><br><span class="line"><span class="addition">+    flatDir &#123; // 引用本项目的libs下的aar</span></span><br><span class="line"><span class="addition">+      dir &quot;$rootDir/libs&quot;</span></span><br><span class="line"><span class="addition">+    &#125;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">allprojects &#123;</span><br><span class="line">  repositories &#123;</span><br><span class="line"><span class="addition">+    flatDir &#123; // 引用本项目的libs下的aar</span></span><br><span class="line"><span class="addition">+      dir &quot;$rootDir/libs&quot;</span></span><br><span class="line"><span class="addition">+    &#125;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>3、在 <code>app/build.gradle</code> 中注入依赖</p><blockquote><p>注意：远程 <code>aar</code> 引入形式是：<code>implementation(&#39;com.sishu.android:watermelondb:0.7.0@aar&#39;)</code></p></blockquote><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">dependencies &#123;</span><br><span class="line"><span class="addition">+    implementation(name: &#x27;aar名字&#x27;, ext: &#x27;aar&#x27;)</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="so-文件"><a href="#so-文件" class="headerlink" title="so 文件"></a>so 文件</h3><p>直接在 <code>src-&gt;main</code> 下新建一个文件夹 <code>jniLib</code> ，然后再把 <code>so</code> 文件所在的那个文件夹 <code>armeabi</code> 复制过去。</p><blockquote><p>注：<code>jniLib</code> 是 so 文件默认的放置目录</p></blockquote><h2 id="坑"><a href="#坑" class="headerlink" title="坑"></a>坑</h2><h3 id="都是-Proguard-惹的祸"><a href="#都是-Proguard-惹的祸" class="headerlink" title="都是 Proguard 惹的祸"></a>都是 Proguard 惹的祸</h3><p>有时候明明导入了 <code>jar</code> 包，却仍然找不到 <code>jar</code> 包中的方法呢？八成是因为你开启了混淆，最安全的就是空间换安全。但是一个较真的程序员不能满足于此，我们还是要搞清楚 Proguard 惹了什么祸的。这里分享给大家一招：</p><p>打开 Android Studio 像原生开发工程师一样使用 Logcat 查看应用日志，比如你找到是 <code>com.huawei.**</code> 这个库找不到，那么进行如下配置：</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="addition">+ -dontwarn com.huawei.**</span></span><br><span class="line"><span class="addition">+ -keep class com.huawei.**&#123;*;&#125;</span></span><br></pre></td></tr></table></figure><ul><li><code>-dontwarn</code> 表示让 ProGuard 不要警告找不到 <code>com.huawei.**</code> 这个包里面的类的相关引用</li><li><code>-keep class</code> 表示保持 <code>com.huawei.**</code> 这个包里面的所有类和所有方法不被混淆。再次编译打包，发现 apk 大小要明显大于之前的包。运行 app，问题解决！</li></ul><h3 id="aar-包中的资源文件重复了"><a href="#aar-包中的资源文件重复了" class="headerlink" title="aar 包中的资源文件重复了"></a>aar 包中的资源文件重复了</h3><p>资源文件重复了，主工程的资源文件会直接覆盖 <code>aar</code> 包中的文件，并且不会有任何报错或者提示，最终 <code>aar</code> 包中也会直接用主工程的资源文件，所以需要注意命名方式。暂时没有更好的解决方法。</p><h3 id="AndroidManifest-合并错误"><a href="#AndroidManifest-合并错误" class="headerlink" title="AndroidManifest 合并错误"></a>AndroidManifest 合并错误</h3><p>同样也是发生在 <code>aar</code> 包上， Android Studio 项目每个 module 中都可以有一个 <code>AndroidManifest.xml</code> 文件，但最终的 APK 文件只能包含一个 <code>AndroidManifest.xml</code> 文件。在构建应用时，Gradle 构建会将所有清单文件合并到一个封装到 APK 的清单文件中。aar 包的清单文件和我们的 app 清单文件属性冲突时：用 <code>tools:replace=&quot;属性名&quot;</code> 解决。</p><h3 id="annotationProcessor-与-compileOnly-的区别"><a href="#annotationProcessor-与-compileOnly-的区别" class="headerlink" title="annotationProcessor 与 compileOnly 的区别"></a>annotationProcessor 与 compileOnly 的区别</h3><p>上文说了 <code>annotationProcessor</code> 与 <code>compileOnly</code> 都是只编译并不打入 apk 中，他俩到底有什么区别呢？扮演的角色不一样，<code>annotationProcessor</code> 作用是编译时生成代码，编译完真的就不需要了，<code>compileOnly</code> 是有重复的库，为的是剃除只保留一个库，最终还是需要的。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="http://t.cn/Ai9HmlWb">Android Studio 引入 jar 包和 so 库</a></li><li><a href="http://t.cn/Ai9T02Jq">Android 依赖导入全攻略</a></li><li><a href="http://t.cn/Ai98bNbj">React-native 使用原生(ios, android)第三方 sdk</a></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/ec072d4935bc/</id>
    <link href="https://blog.zisheng.pro/posts/ec072d4935bc/"/>
    <published>2023-04-21T10:40:00.000Z</published>
    <summary>在 React Native 开发中，如果一个原生 SDK 没有适配 React Native 的插件，原则上我们不推荐你使用。但是万不得已的情况下，我们有办法吗？答案是肯定的。步骤大致分为集成 SDK、编写桥接代码。知难行易，本文就是顺着这个思路来解决让前端工程师脑壳疼的集成第三方 SDK 并编写桥接代码的问题。</summary>
    <title>React Native 引入第三方 Android SDK</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React/"/>
    <category term="面试题" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/%E9%9D%A2%E8%AF%95%E9%A2%98/"/>
    <category term="前端" scheme="https://blog.zisheng.pro/tags/%E5%89%8D%E7%AB%AF/"/>
    <category term="React" scheme="https://blog.zisheng.pro/tags/React/"/>
    <category term="VDOM" scheme="https://blog.zisheng.pro/tags/VDOM/"/>
    <category term="Virtual DOM" scheme="https://blog.zisheng.pro/tags/Virtual-DOM/"/>
    <category term="面试题" scheme="https://blog.zisheng.pro/tags/%E9%9D%A2%E8%AF%95%E9%A2%98/"/>
    <category term="虚拟 DOM" scheme="https://blog.zisheng.pro/tags/%E8%99%9A%E6%8B%9F-DOM/"/>
    <content>
      <![CDATA[<p>在 Web 前端开发中，React 是一种非常流行的 JavaScript 库，它的 Virtual DOM（虚拟 DOM）技术被广泛地应用于构建高性能、可维护的 Web 应用程序。那么，React VDOM 技术到底是什么？它又是如何工作的呢？</p><h2 id="VDOM-是什么？"><a href="#VDOM-是什么？" class="headerlink" title="VDOM 是什么？"></a>VDOM 是什么？</h2><p>VDOM（Virtual DOM）是 React 中的一个重要概念，它是一个虚拟的、内存中的 DOM 树。与真实的 DOM 树不同，VDOM 只存在于 JavaScript 内存中，它是 React 组件的一部分，并且可以随时更新和修改。</p><h2 id="React-VDOM-技术的优势"><a href="#React-VDOM-技术的优势" class="headerlink" title="React VDOM 技术的优势"></a>React VDOM 技术的优势</h2><p>React VDOM 技术主要有以下几个优势：</p><ol><li>提高 Web 应用程序的性能：由于真实的 DOM 树非常庞大，而且每次更新都需要重新计算布局和重新渲染，因此更新频繁的 Web 应用程序很容易出现性能问题。而 VDOM 只是一个轻量级的 JavaScript 对象，它可以快速地进行比较和更新，从而大大提高了 Web 应用程序的性能。</li><li>提高 Web 应用程序的可维护性：由于 VDOM 只存在于 JavaScript 内存中，因此开发人员可以更方便地对其进行操作和管理，从而提高了 Web 应用程序的可维护性。</li><li>提高 Web 应用程序的开发效率：由于 VDOM 可以快速进行比较和更新，因此开发人员可以更快地进行开发和测试，从而提高了 Web 应用程序的开发效率。</li></ol><h2 id="React-VDOM-技术的工作原理"><a href="#React-VDOM-技术的工作原理" class="headerlink" title="React VDOM 技术的工作原理"></a>React VDOM 技术的工作原理</h2><p>React VDOM 技术的工作原理主要包括以下几个步骤：</p><ol><li>首先，React 会将组件的 JSX 代码转换为 VDOM 对象，并将其存储在内存中。</li><li>当组件的状态发生变化时，React 会重新生成一个新的 VDOM 对象，并将其与之前的 VDOM 对象进行比较。</li><li>在比较过程中，React 会遍历新旧 VDOM 对象，找出它们之间的差异，并将这些差异记录在一个“差异对象”中。</li><li>最后，React 会根据“差异对象”中的记录，更新真实的 DOM 树，从而实现组件的重新渲染。</li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>React VDOM 技术是 React 中的一个重要概念，它可以提高 Web 应用程序的性能、可维护性和开发效率。虽然 VDOM 技术看起来比较复杂，但是它背后的原理非常简单，只需要理解其基本的工作原理，就可以快速上手使用。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/7c184cbc698e/</id>
    <link href="https://blog.zisheng.pro/posts/7c184cbc698e/"/>
    <published>2023-04-20T09:55:04.000Z</published>
    <summary>在 Web 前端开发中，React 是一种非常流行的 JavaScript 库，它的 Virtual DOM（虚拟 DOM）技术被广泛地应用于构建高性能、可维护的 Web 应用程序。那么，React VDOM 技术到底是什么？它又是如何工作的呢？</summary>
    <title>React VDOM 原理</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="Deno" scheme="https://blog.zisheng.pro/categories/Deno/"/>
    <category term="GitHub Action" scheme="https://blog.zisheng.pro/tags/GitHub-Action/"/>
    <category term="CI/CD" scheme="https://blog.zisheng.pro/tags/CI-CD/"/>
    <category term="Travis CI" scheme="https://blog.zisheng.pro/tags/Travis-CI/"/>
    <category term="Circle CI" scheme="https://blog.zisheng.pro/tags/Circle-CI/"/>
    <content>
      <![CDATA[<p>GitHub Action 是 GitHub 官方的 CI&#x2F;CD 工具，相较于 Travis CI 和 Circle CI，更轻量和易于扩展，<a href="https://github.com/marketplace?type=actions">marketplace</a> 中有大量社区贡献的插件。各大开源项目都纷纷转向使用 GitHub Action 作为持续集成的工具，比如本文的主角 Deno。</p><p>GitHub 的文档中有很多概念写的十分晦涩，有些翻译很僵硬影响理解。截止发稿时，Deno 的 <a href="https://github.com/youngjuning/deno/blob/master/.github/workflows/ci.yml">ci.yml</a> 文件有 323 行，是一个很好的学习范本。想要对 GitHub Action 有个了解或对 Deno 的持续集成部分感兴趣的同学都可以一起来探究下。</p><h2 id="Action-结构"><a href="#Action-结构" class="headerlink" title="Action 结构"></a>Action 结构</h2><blockquote><p>注意：GitHub Action 的 ci 脚本需要放在 <code>.github/workflows</code> 文件夹下</p></blockquote><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">ci</span></span><br><span class="line"></span><br><span class="line"><span class="attr">on:</span> [<span class="string">push</span>, <span class="string">pull_request</span>]</span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br></pre></td></tr></table></figure><ul><li><code>name</code>：工作流程的名称。GitHub 在仓库的操作页面上显示工作流程的名称。如果省略 name，GitHub 将其设置为相对于仓库根目录的工作流程文件路径。</li><li><code>on</code>：必填。是<a href="https://docs.github.com/cn/articles/events-that-trigger-workflows">触发工作流程的事件</a>。Deno 中 的 <code>[push,pull_request]</code> 代表有主分支有提交代码或 pull_request 时触发 CI 流程。</li><li><code>jobs</code>：工作流程运行包括一项或多项作业。作业默认是并行运行。Deno 中只有一个 job —— <code>build</code></li></ul><h2 id="build-job"><a href="#build-job" class="headerlink" title="build job"></a>build job</h2><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">build:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">$&#123;&#123;</span> <span class="string">matrix.kind</span> <span class="string">&#125;&#125;</span> <span class="string">$&#123;&#123;</span> <span class="string">matrix.os</span> <span class="string">&#125;&#125;</span></span><br><span class="line">    <span class="attr">if:</span> <span class="string">|</span></span><br><span class="line"><span class="string">      github.event_name == &#x27;push&#x27; ||</span></span><br><span class="line"><span class="string">      !startsWith(github.event.pull_request.head.label, &#x27;denoland:&#x27;)</span></span><br><span class="line"><span class="string"></span>    <span class="attr">runs-on:</span> <span class="string">$&#123;&#123;</span> <span class="string">matrix.os</span> <span class="string">&#125;&#125;</span></span><br><span class="line">    <span class="attr">timeout-minutes:</span> <span class="number">60</span></span><br><span class="line">    <span class="attr">strategy:</span></span><br><span class="line">      <span class="comment"># ...</span></span><br><span class="line">    <span class="attr">env:</span></span><br><span class="line">      <span class="comment"># ...</span></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">      <span class="comment"># ...</span></span><br></pre></td></tr></table></figure><ul><li>&#96;name：作业显示在 GitHub 上的名称。<ul><li><code>matrix</code>：用于访问为当前作业配置的矩阵参数。例如，Deno 使用 kind 和 os 版本配置矩阵构建，matrix 上下文对象将包含当前作业的 kind 和 os 版本。</li></ul></li><li><code>if</code>：您可以使用 if 条件阻止作业在条件得到满足之前运行。Deno 中判断 <code>github.event_name</code> 等于 <code>&quot;push&quot;</code> 或 <code>label</code> 以 <code>&quot;denoland:&quot;</code> 开头的 <code>pull_request</code> 事件</li><li><code>runs-on</code>：<strong>必填</strong>。 任务运行的机器。机器可以是 GitHub 托管的运行器或自托管的运行器。定义操作系统矩阵时，必须将 <code>runs-on</code> 的值设置为您定义的 <code>matrix.os</code> 上下文属性。Deno 是跨平台的，因此创建了矩阵以在多个运行器操作系统上运行构建工作流程。</li><li><code>timeout-minutes</code>: 在 GitHub 自动取消运行之前可让作业运行的最大分钟数。 默认值：360。Deno 中设置为 60 来尽早暴露问题。</li><li><code>strategy</code>：策略，用于创建作业的构建矩阵。您可以定义要在其中运行每项作业的不同变种。这可能不太好理解，在 Deno 中，其实就是为了同时构建出多个平台的产物。后面的章节我们单独解析 Deno 中具体的应用。</li><li><code>env</code>：环境变量的 <code>map</code> 可用于作业中的所有步骤。 您也可以设置整个工作流程或单个步骤的环境变量。后面章节我们单独解析 Deno 中具体的应用。</li><li><code>steps</code>：作业包含一系列任务，称为 steps。步骤可以运行命令、运行设置任务，或者运行您的仓库、公共仓库中的操作或 Docker 注册表中发布的操作。后面章节我们单独解析 Deno 中具体的应用。</li></ul><h2 id="strategy-策略"><a href="#strategy-策略" class="headerlink" title="strategy 策略"></a>strategy 策略</h2><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">strategy:</span></span><br><span class="line">  <span class="attr">matrix:</span></span><br><span class="line">    <span class="attr">include:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">os:</span> <span class="string">macos-10.15</span></span><br><span class="line">        <span class="attr">kind:</span> <span class="string">test_release</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">os:</span> <span class="string">windows-2019</span></span><br><span class="line">        <span class="attr">kind:</span> <span class="string">test_release</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">os:</span> <span class="string">$&#123;&#123;</span> <span class="string">github.repository</span> <span class="string">==</span> <span class="string">&#x27;denoland/deno&#x27;</span> <span class="string">&amp;&amp;</span> <span class="string">&#x27;ubuntu-latest&#x27;</span> <span class="string">||</span> <span class="string">&#x27;ubuntu-18.04&#x27;</span> <span class="string">&#125;&#125;</span></span><br><span class="line">        <span class="attr">kind:</span> <span class="string">test_release</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">os:</span> <span class="string">$&#123;&#123;</span> <span class="string">github.repository</span> <span class="string">==</span> <span class="string">&#x27;denoland/deno&#x27;</span> <span class="string">&amp;&amp;</span> <span class="string">&#x27;ubuntu-latest&#x27;</span> <span class="string">||</span> <span class="string">&#x27;ubuntu-18.04&#x27;</span> <span class="string">&#125;&#125;</span></span><br><span class="line">        <span class="attr">kind:</span> <span class="string">test_debug</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">os:</span> <span class="string">$&#123;&#123;</span> <span class="string">github.repository</span> <span class="string">==</span> <span class="string">&#x27;denoland/deno&#x27;</span> <span class="string">&amp;&amp;</span> <span class="string">&#x27;ubuntu-latest&#x27;</span> <span class="string">||</span> <span class="string">&#x27;ubuntu-18.04&#x27;</span> <span class="string">&#125;&#125;</span></span><br><span class="line">        <span class="attr">kind:</span> <span class="string">bench</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">os:</span> <span class="string">$&#123;&#123;</span> <span class="string">github.repository</span> <span class="string">==</span> <span class="string">&#x27;denoland/deno&#x27;</span> <span class="string">&amp;&amp;</span> <span class="string">&#x27;ubuntu-latest&#x27;</span> <span class="string">||</span> <span class="string">&#x27;ubuntu-18.04&#x27;</span> <span class="string">&#125;&#125;</span></span><br><span class="line">        <span class="attr">kind:</span> <span class="string">lint</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># Always run master branch builds to completion. This allows the cache to</span></span><br><span class="line">  <span class="comment"># stay mostly up-to-date in situations where a single job fails due to</span></span><br><span class="line">  <span class="comment"># e.g. a flaky test.</span></span><br><span class="line">  <span class="comment"># Don&#x27;t fast-fail on tag build because publishing binaries shouldn&#x27;t be</span></span><br><span class="line">  <span class="comment"># prevented if &#x27;cargo publish&#x27; fails (which can be a false negative).</span></span><br><span class="line">  <span class="attr">fail-fast:</span> <span class="string">$&#123;&#123;</span> <span class="string">github.event_name</span> <span class="string">==</span> <span class="string">&#x27;pull_request&#x27;</span> <span class="string">||</span> <span class="string">(github.ref</span> <span class="type">!=</span></span><br><span class="line">    <span class="string">&#x27;refs/heads/master&#x27;</span> <span class="string">&amp;&amp;</span> <span class="type">!startsWith(github.ref,</span> <span class="string">&#x27;refs/tags/&#x27;</span><span class="string">))</span> <span class="string">&#125;&#125;</span></span><br></pre></td></tr></table></figure><ul><li><code>strategy</code>：策略会为您的工作创建构建矩阵。您可以定义不同的变体来运行每个作业。这可能不太好理解，在 Deno 中，其实就是为了同时构建出多个平台的产物。<ul><li><code>matrix</code>：您可以定义不同作业配置的矩阵。Deno 中定义了 6 个矩阵。它们分别用来执行测试、调试、压测、lint 等工作<ul><li><code>include</code>：使用 <code>matrix</code> 只能定义使用某个 os，没办法添加其他配置。用 <code>include</code> 可以解决这个问题</li></ul></li><li><code>fail-fast</code>：设置为 <code>true</code> 时，如果任何 <code>matrix</code> 作业失败，GitHub 将取消所有进行中的作业。 默认值：<code>true</code>。ry 在这个 <a href="https://github.com/denoland/deno/commit/46d5843f753548415c87f3c8a868bba49c203b92#diff-b803fcb7f17ed9235f1e5cb1fcd2f5d3b2838429d4368ae4c57ce4436577f03f">commit</a> 中删除了 <code>cargo publish</code>，从注释中看来这里的 <code>fail-fast</code> 配置已经是冗余的了，我顺手就是一个 fork、commit、<a href="https://github.com/denoland/deno/pull/9449">pr</a> 三连。</li></ul></li></ul><p><strong>插曲</strong></p><p><code>$&#123;&#123; github.repository == &#39;denoland/deno&#39; &amp;&amp; &#39;ubuntu-latest&#39; || &#39;ubuntu-18.04&#39; &#125;&#125;</code> 这种写法我看到时很疑惑，直接取 ‘ubuntu-latest’ 不行吗？如果不行是为什么呢？抱着求真的态度，我翻阅到是在这个 <a href="https://github.com/denoland/deno/commit/1a27c19c583fa6bd1eaaec93b513bfbac37fc53c#diff-b803fcb7f17ed9235f1e5cb1fcd2f5d3b2838429d4368ae4c57ce4436577f03f">commit</a> 加入的这个判断，在一番询问后得到了下面的答案：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bf73fe62dcbc48f69272f97a1a139444~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>在 justjavac 大佬那里也得到了印证：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/04d3d4f533e74d0795d70e5cffd69699~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>然后我还有个疑问是为什么不使用三元表达式，是 YAML 语法不支持吗？</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b76f02c91de841d587084fbc73a2cca3~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><h2 id="env-环境变量"><a href="#env-环境变量" class="headerlink" title="env 环境变量"></a>env 环境变量</h2><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">env:</span></span><br><span class="line">  <span class="attr">CARGO_INCREMENTAL:</span> <span class="number">0</span></span><br><span class="line">  <span class="attr">RUST_BACKTRACE:</span> <span class="string">full</span></span><br><span class="line">  <span class="attr">CARGO_TERM_COLOR:</span> <span class="string">always</span></span><br></pre></td></tr></table></figure><ul><li><code>env</code>：环境变量的 <code>map</code> 可用于作业中的所有步骤。您也可以设置整个工作流程或单个步骤的环境变量。代码中的三个变量是整个流程的环境变量。<ul><li><code>CARGO_INCREMENTAL</code>、<code>CARGO_TERM_COLOR</code>、<code>RUST_BACKTRACE</code> 是配置给 Cargo 用的，具体的参考<a href="https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-reads">environment-variables-cargo-reads</a>，这里不展开解读了。</li></ul></li></ul><h2 id="steps-步骤"><a href="#steps-步骤" class="headerlink" title="steps 步骤"></a>steps 步骤</h2><p>Deno 的 CI 中截止写下这段话时有 35 个步骤，相当考验我继续解读的勇气。。。</p><blockquote><p>作业包含一系列任务，称为 <code>steps</code>。步骤可以运行命令、运行设置任务，或者运行您的仓库、公共仓库中的操作或 Docker 注册表中发布的操作。并非所有步骤都会运行操作，但所有操作都会作为步骤运行。每个步骤在运行器环境中以其自己的进程运行，且可以访问工作区和文件系统。因为步骤以自己的进程运行，所以步骤之间不会保留环境变量的更改。 GitHub 提供内置的步骤来设置和完成作业。—— 文档</p></blockquote><h3 id="1、Configure-git"><a href="#1、Configure-git" class="headerlink" title="1、Configure git"></a>1、Configure git</h3><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Configure</span> <span class="string">git</span></span><br><span class="line">  <span class="attr">run:</span> <span class="string">git</span> <span class="string">config</span> <span class="string">--global</span> <span class="string">core.symlinks</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><ul><li><code>git config --global core.symlinks true</code>：克隆一个包含子模块和符号链接的存储库。这里是为了兼容 windows，详细的解释请查看 <a href="https://github.community/t/git-bash-symbolic-links-on-windows/522">git bash symbolic links on windows</a></li></ul><h3 id="2、Clone-repository"><a href="#2、Clone-repository" class="headerlink" title="2、Clone repository"></a>2、Clone repository</h3><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Clone</span> <span class="string">repository</span></span><br><span class="line">  <span class="attr">uses:</span> <span class="string">actions/checkout@v2</span></span><br><span class="line">  <span class="attr">with:</span></span><br><span class="line">    <span class="comment"># 使用 `depth&gt; 1`，因为有时我们需要重新构建 master 分支，如果 checkout 太浅，其他 commits push 之后将不可能重新构建。</span></span><br><span class="line">    <span class="attr">fetch-depth:</span> <span class="number">5</span></span><br><span class="line">    <span class="attr">submodules:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><ul><li><code>actions/checkout@v2</code>：checkout action 很常用<ul><li><code>fetch-depth: 5</code>：默认情况下，仅提取一次提交。这里设置的 5，就会提取最近的 5 次提交。</li><li><code>submodules: true</code>：构建需要把子模块也获取到，Deno 中包含了子模块。</li></ul></li></ul><h3 id="3、Create-source-tarballs-release-linux"><a href="#3、Create-source-tarballs-release-linux" class="headerlink" title="3、Create source tarballs (release, linux)"></a>3、Create source tarballs (release, linux)</h3><blockquote><p>创建源文件压缩包 (release, linux)</p></blockquote><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Create</span> <span class="string">source</span> <span class="string">tarballs</span> <span class="string">(release,</span> <span class="string">linux)</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    startsWith(matrix.os, &#x27;ubuntu&#x27;) &amp;&amp;</span></span><br><span class="line"><span class="string">    matrix.kind == &#x27;test_release&#x27; &amp;&amp;</span></span><br><span class="line"><span class="string">    github.repository == &#x27;denoland/deno&#x27; &amp;&amp;</span></span><br><span class="line"><span class="string">    startsWith(github.ref, &#x27;refs/tags/&#x27;)</span></span><br><span class="line"><span class="string"></span>  <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    mkdir -p target/release</span></span><br><span class="line"><span class="string">    tar --exclude=.cargo_home --exclude=&quot;.git*&quot; --exclude=target --exclude=third_party/prebuilt -czvf target/release/deno_src.tar.gz -C .. deno</span></span><br></pre></td></tr></table></figure><ul><li>执行条件：构建矩阵 os 是 <code>&#39;ubuntu&#39;</code>、kind 是 <code>&#39;test_release&#39;</code>、仓库是 <code>&#39;denoland/deno&#39;</code>、本地执行 <code>git push --tags</code> 或 <code>git push origin v0.0.1</code></li><li><code>run</code>：其实发 release 的时候会自动打包源文件，这里之所以多打一个，是因为要忽略很多文件夹，打出纯 deno 的源文件</li></ul><h3 id="4、Install-rust"><a href="#4、Install-rust" class="headerlink" title="4、Install rust"></a>4、Install rust</h3><blockquote><p>安装 rust</p></blockquote><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Install</span> <span class="string">rust</span></span><br><span class="line">  <span class="attr">uses:</span> <span class="string">hecrj/setup-rust-action@v1</span></span><br><span class="line">  <span class="attr">with:</span></span><br><span class="line">    <span class="attr">rust-version:</span> <span class="number">1.49</span><span class="number">.0</span></span><br></pre></td></tr></table></figure><ul><li>Deno 源码是 RUST 编写的，所以是用 <code>hecrj/setup-rust-action@v1</code>，每个语言都有自己的 <code>setup</code>，商店里能搜到 <a href="https://github.com/marketplace?type=actions&query=setup">358</a> 条记录。</li></ul><h3 id="5、Install-clippy-and-rustfmt"><a href="#5、Install-clippy-and-rustfmt" class="headerlink" title="5、Install clippy and rustfmt"></a>5、Install clippy and rustfmt</h3><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Install</span> <span class="string">clippy</span> <span class="string">and</span> <span class="string">rustfmt</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">matrix.kind</span> <span class="string">==</span> <span class="string">&#x27;lint&#x27;</span></span><br><span class="line">  <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    rustup component add clippy</span></span><br><span class="line"><span class="string">    rustup component add rustfmt</span></span><br></pre></td></tr></table></figure><h3 id="6、Install-Deno（非-Windows）"><a href="#6、Install-Deno（非-Windows）" class="headerlink" title="6、Install Deno（非 Windows）"></a>6、Install Deno（非 Windows）</h3><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Install</span> <span class="string">Deno</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    !startsWith(matrix.os, &#x27;windows&#x27;)</span></span><br><span class="line"><span class="string"></span>  <span class="attr">run:</span> <span class="string">|-</span></span><br><span class="line"><span class="string">    curl -fsSL https://deno.land/x/install/install.sh | sh -s v1.5.1</span></span><br><span class="line"><span class="string">    echo &quot;$HOME/.deno/bin&quot; &gt;&gt; $GITHUB_PATH</span></span><br></pre></td></tr></table></figure><ul><li>执行条件：构建矩阵 os 不是 windows</li><li><code>run</code><ul><li><code>|-</code> 和 <code>|</code>是 yaml 的语法。这样，您就可以有效地声明多行 yaml 字符串。语法详情查看<a href="https://yaml-multiline.info/">这里</a></li><li><code>curl -fsSL https://deno.land/x/install/install.sh | sh -s v1.5.1</code> 安装 deno（这里应该可以 pr）</li><li><code>echo &quot;$HOME/.deno/bin&quot; &gt;&gt; $GITHUB_PATH</code>：设置环境变量，设置的指向文件系统上某个位置的任何新环境变量都应该有 <code>_PATH</code> 后缀。</li></ul></li></ul><h3 id="7、Install-Deno-Windows"><a href="#7、Install-Deno-Windows" class="headerlink" title="7、Install Deno (Windows)"></a>7、Install Deno (Windows)</h3><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Install</span> <span class="string">Deno</span> <span class="string">(Windows)</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">startsWith(matrix.os,</span> <span class="string">&#x27;windows&#x27;</span><span class="string">)</span></span><br><span class="line">  <span class="attr">run:</span> <span class="string">|-</span></span><br><span class="line"><span class="string">    curl -fsSL https://deno.land/x/install/install.sh | sh -s v1.5.1</span></span><br><span class="line"><span class="string">    echo &quot;$HOME/.deno/bin&quot; &gt;&gt; $env:GITHUB_PATH</span></span><br></pre></td></tr></table></figure><ul><li>执行条件：构建矩阵 os 是 windows</li><li><code>run</code>：和上文一样</li></ul><h3 id="8、Install-Python"><a href="#8、Install-Python" class="headerlink" title="8、Install Python"></a>8、Install Python</h3><blockquote><p>安装 python</p></blockquote><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Install</span> <span class="string">Python</span></span><br><span class="line">  <span class="attr">uses:</span> <span class="string">actions/setup-python@v1</span></span><br><span class="line">  <span class="attr">with:</span></span><br><span class="line">    <span class="attr">python-version:</span> <span class="string">&#x27;3.8&#x27;</span></span><br><span class="line">    <span class="attr">architecture:</span> <span class="string">x64</span></span><br></pre></td></tr></table></figure><h3 id="9、Install-Node"><a href="#9、Install-Node" class="headerlink" title="9、Install Node"></a>9、Install Node</h3><blockquote><p>安装 Node</p></blockquote><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Install</span> <span class="string">Node</span></span><br><span class="line">  <span class="attr">uses:</span> <span class="string">actions/setup-node@v2</span></span><br><span class="line">  <span class="attr">with:</span></span><br><span class="line">    <span class="attr">node-version:</span> <span class="string">&#x27;14&#x27;</span></span><br><span class="line">    <span class="attr">check-latest:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><h3 id="10、Remove-unused-versions-of-Python"><a href="#10、Remove-unused-versions-of-Python" class="headerlink" title="10、Remove unused versions of Python"></a>10、Remove unused versions of Python</h3><blockquote><p>删除没有用到的 Python 版本</p></blockquote><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Remove</span> <span class="string">unused</span> <span class="string">versions</span> <span class="string">of</span> <span class="string">Python</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">startsWith(matrix.os,</span> <span class="string">&#x27;windows&#x27;</span><span class="string">)</span></span><br><span class="line">  <span class="attr">run:</span> <span class="string">|-</span></span><br><span class="line"><span class="string">    $env:PATH -split &quot;;&quot; |</span></span><br><span class="line"><span class="string">      Where-Object &#123; Test-Path &quot;$_\python.exe&quot; &#125; |</span></span><br><span class="line"><span class="string">      Select-Object -Skip 1 |</span></span><br><span class="line"><span class="string">      ForEach-Object &#123; Move-Item &quot;$_&quot; &quot;$_.disabled&quot; &#125;</span></span><br></pre></td></tr></table></figure><h3 id="11、Setup-gcloud-unix"><a href="#11、Setup-gcloud-unix" class="headerlink" title="11、Setup gcloud (unix)"></a>11、Setup gcloud (unix)</h3><blockquote><p>安装 Google Cloud SDK（unix）</p></blockquote><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Setup</span> <span class="string">gcloud</span> <span class="string">(unix)</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    runner.os != &#x27;Windows&#x27; &amp;&amp;</span></span><br><span class="line"><span class="string">    matrix.kind == &#x27;test_release&#x27; &amp;&amp;</span></span><br><span class="line"><span class="string">    github.repository == &#x27;denoland/deno&#x27; &amp;&amp;</span></span><br><span class="line"><span class="string">    (github.ref == &#x27;refs/heads/master&#x27; ||</span></span><br><span class="line"><span class="string">    startsWith(github.ref, &#x27;refs/tags/&#x27;))</span></span><br><span class="line"><span class="string"></span>  <span class="attr">uses:</span> <span class="string">google-github-actions/setup-gcloud@master</span></span><br><span class="line">  <span class="attr">with:</span></span><br><span class="line">    <span class="attr">project_id:</span> <span class="string">denoland</span></span><br><span class="line">    <span class="attr">service_account_key:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.GCP_SA_KEY</span> <span class="string">&#125;&#125;</span></span><br><span class="line">    <span class="attr">export_default_credentials:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><ul><li>执行条件：运行系统不是 ‘Windows’、构建矩阵 kind 是 <code>&#39;test_release&#39;</code>、仓库是 <code>&#39;denoland/deno&#39;</code>、分支是 master 或是 tag 提交</li><li><code>google-github-actions/setup-gcloud</code>：与 Google Cloud Platform 交互的 GitHub Actions 的集合。</li></ul><h3 id="12、Setup-gcloud-windows"><a href="#12、Setup-gcloud-windows" class="headerlink" title="12、Setup gcloud (windows)"></a>12、Setup gcloud (windows)</h3><blockquote><p>同 11 作用一样，不过是为 windows 设置 gcloud</p></blockquote><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Setup</span> <span class="string">gcloud</span> <span class="string">(windows)</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    runner.os == &#x27;Windows&#x27; &amp;&amp;</span></span><br><span class="line"><span class="string">    matrix.kind == &#x27;test_release&#x27; &amp;&amp;</span></span><br><span class="line"><span class="string">    github.repository == &#x27;denoland/deno&#x27; &amp;&amp;</span></span><br><span class="line"><span class="string">    (github.ref == &#x27;refs/heads/master&#x27; ||</span></span><br><span class="line"><span class="string">    startsWith(github.ref, &#x27;refs/tags/&#x27;))</span></span><br><span class="line"><span class="string"></span>  <span class="attr">uses:</span> <span class="string">google-github-actions/setup-gcloud@master</span></span><br><span class="line">  <span class="attr">env:</span></span><br><span class="line">    <span class="attr">CLOUDSDK_PYTHON:</span> <span class="string">$&#123;&#123;env.pythonLocation&#125;&#125;\python.exe</span></span><br><span class="line">  <span class="attr">with:</span></span><br><span class="line">    <span class="attr">project_id:</span> <span class="string">denoland</span></span><br><span class="line">    <span class="attr">service_account_key:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.GCP_SA_KEY</span> <span class="string">&#125;&#125;</span></span><br><span class="line">    <span class="attr">export_default_credentials:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><h3 id="13、Configure-canary-build"><a href="#13、Configure-canary-build" class="headerlink" title="13、Configure canary build"></a>13、Configure canary build</h3><blockquote><p>配置 canary 渠道的 build（Deno 发布氛围 canary 和 release 渠道）</p></blockquote><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Configure</span> <span class="string">canary</span> <span class="string">build</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    matrix.kind == &#x27;test_release&#x27; &amp;&amp;</span></span><br><span class="line"><span class="string">    github.repository == &#x27;denoland/deno&#x27; &amp;&amp;</span></span><br><span class="line"><span class="string">    github.ref == &#x27;refs/heads/master&#x27;</span></span><br><span class="line"><span class="string"></span>  <span class="attr">shell:</span> <span class="string">bash</span></span><br><span class="line">  <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line">    <span class="string">echo</span> <span class="string">&quot;DENO_CANARY=true&quot;</span> <span class="string">&gt;&gt;</span> <span class="string">$GITHUB_ENV</span></span><br></pre></td></tr></table></figure><ul><li>执行条件：构建矩阵 <code>kind</code> 为 <code>&#39;test_release&#39;</code>、github repo 是 <code>&#39;denoland/deno&#39;</code>、master 分支</li><li><code>run</code> 将 <code>&quot;DENO_CANARY=true&quot;</code> 设置进环境变量。</li></ul><blockquote><p>您也可以使用 <code>GITHUB_ENV</code> environment file 设置工作流程中的以下步骤可以使用的环境变量。 —— 文档</p></blockquote><h3 id="14、Log-versions"><a href="#14、Log-versions" class="headerlink" title="14、Log versions"></a>14、Log versions</h3><blockquote><p>检查前面安装的程序的版本</p></blockquote><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Log</span> <span class="string">versions</span></span><br><span class="line">  <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    node -v</span></span><br><span class="line"><span class="string">    python --version</span></span><br><span class="line"><span class="string">    rustc --version</span></span><br><span class="line"><span class="string">    cargo --version</span></span><br><span class="line"><span class="string">    deno --version</span></span><br></pre></td></tr></table></figure><h3 id="15、lint-js"><a href="#15、lint-js" class="headerlink" title="15、lint.js"></a>15、lint.js</h3><blockquote><p>执行 lint</p></blockquote><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">lint.js</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">matrix.kind</span> <span class="string">==</span> <span class="string">&#x27;lint&#x27;</span></span><br><span class="line">  <span class="attr">run:</span> <span class="string">deno</span> <span class="string">run</span> <span class="string">--unstable</span> <span class="string">--allow-write</span> <span class="string">--allow-read</span> <span class="string">--allow-run</span> <span class="string">./tools/lint.js</span></span><br></pre></td></tr></table></figure><ul><li>执行条件：构建矩阵 <code>kind</code> 类型 为 <code>&#39;lint&#39;</code></li></ul><h3 id="16、test-format-js"><a href="#16、test-format-js" class="headerlink" title="16、test_format.js"></a>16、test_format.js</h3><blockquote><p>测试格式化程序</p></blockquote><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">test_format.js</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">matrix.kind</span> <span class="string">==</span> <span class="string">&#x27;lint&#x27;</span></span><br><span class="line">  <span class="attr">run:</span> <span class="string">deno</span> <span class="string">run</span> <span class="string">--unstable</span> <span class="string">--allow-write</span> <span class="string">--allow-read</span> <span class="string">--allow-run</span> <span class="string">./tools/format.js</span> <span class="string">--check</span></span><br></pre></td></tr></table></figure><ul><li>执行条件：构建矩阵 <code>kind</code> 类型 为 <code>&#39;lint&#39;</code></li></ul><h3 id="17、Build-release"><a href="#17、Build-release" class="headerlink" title="17、Build release"></a>17、Build release</h3><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Build</span> <span class="string">release</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    matrix.kind == &#x27;test_release&#x27; ||</span></span><br><span class="line"><span class="string">    matrix.kind == &#x27;bench&#x27;</span></span><br><span class="line"><span class="string"></span>  <span class="attr">run:</span> <span class="string">cargo</span> <span class="string">build</span> <span class="string">--release</span> <span class="string">--locked</span> <span class="string">--all-targets</span> <span class="string">-vv</span></span><br></pre></td></tr></table></figure><ul><li>执行条件：构建矩阵 <code>kind</code> 类型 为 <code>&#39;test_release&#39;</code> 或 <code>&#39;bench&#39;</code></li></ul><h3 id="18、Build-debug"><a href="#18、Build-debug" class="headerlink" title="18、Build debug"></a>18、Build debug</h3><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Build</span> <span class="string">debug</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">matrix.kind</span> <span class="string">==</span> <span class="string">&#x27;test_debug&#x27;</span></span><br><span class="line">  <span class="attr">run:</span> <span class="string">cargo</span> <span class="string">build</span> <span class="string">--locked</span> <span class="string">--all-targets</span></span><br></pre></td></tr></table></figure><ul><li>执行条件：构建矩阵 <code>kind</code> 类型 为 <code>&#39;test_debug&#39;</code></li></ul><h3 id="19、Pre-release-linux"><a href="#19、Pre-release-linux" class="headerlink" title="19、Pre-release (linux)"></a>19、Pre-release (linux)</h3><blockquote><p>发布之前的准备工作（linux）</p></blockquote><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Pre-release</span> <span class="string">(linux)</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    startsWith(matrix.os, &#x27;ubuntu&#x27;) &amp;&amp;</span></span><br><span class="line"><span class="string">    matrix.kind == &#x27;test_release&#x27;</span></span><br><span class="line"><span class="string"></span>  <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    cd target/release</span></span><br><span class="line"><span class="string">    zip -r deno-x86_64-unknown-linux-gnu.zip deno</span></span><br><span class="line"><span class="string">    zip -r denort-x86_64-unknown-linux-gnu.zip denort</span></span><br><span class="line"><span class="string">    ./deno types &gt; lib.deno.d.ts</span></span><br></pre></td></tr></table></figure><ul><li>执行条件：构建矩阵 os 为 <code>&#39;ubuntu&#39;</code>、构建矩阵 <code>kind</code> 为 <code>&#39;test_release&#39;</code></li><li><code>run</code>：做的事情其实就是打压缩包，这些压缩包最终会发布在 GitHub Release 中供安装脚本使用</li></ul><h3 id="20、Pre-release-mac"><a href="#20、Pre-release-mac" class="headerlink" title="20、Pre-release (mac)"></a>20、Pre-release (mac)</h3><blockquote><p>和 19 作用一样，这里没有 <code>./deno types &gt; lib.deno.d.ts</code>，私以为漏了。</p></blockquote><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Pre-release</span> <span class="string">(mac)</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    startsWith(matrix.os, &#x27;macOS&#x27;) &amp;&amp;</span></span><br><span class="line"><span class="string">    matrix.kind == &#x27;test_release&#x27;</span></span><br><span class="line"><span class="string"></span>  <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    cd target/release</span></span><br><span class="line"><span class="string">    zip -r deno-x86_64-apple-darwin.zip deno</span></span><br><span class="line"><span class="string">    zip -r denort-x86_64-apple-darwin.zip denort</span></span><br></pre></td></tr></table></figure><h3 id="21、Pre-release-windows"><a href="#21、Pre-release-windows" class="headerlink" title="21、Pre-release (windows)"></a>21、Pre-release (windows)</h3><blockquote><p>和 19、20 一样，just for windows，可以学习一下 Windows 下的压缩命令怎么写！</p></blockquote><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Pre-release</span> <span class="string">(windows)</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    startsWith(matrix.os, &#x27;windows&#x27;) &amp;&amp;</span></span><br><span class="line"><span class="string">    matrix.kind == &#x27;test_release&#x27;</span></span><br><span class="line"><span class="string"></span>  <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    Compress-Archive -CompressionLevel Optimal -Force -Path target/release/deno.exe -DestinationPath target/release/deno-x86_64-pc-windows-msvc.zip</span></span><br><span class="line"><span class="string">    Compress-Archive -CompressionLevel Optimal -Force -Path target/release/denort.exe -DestinationPath target/release/denort-x86_64-pc-windows-msvc.zip</span></span><br></pre></td></tr></table></figure><h3 id="22、Upload-canary-to-dl-deno-land-unix"><a href="#22、Upload-canary-to-dl-deno-land-unix" class="headerlink" title="22、Upload canary to dl.deno.land (unix)"></a>22、Upload canary to dl.deno.land (unix)</h3><blockquote><p>上传 canary 包到 dl.deno.land（unix）(我们只要知道托管在 Google 云就好了)</p></blockquote><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Upload</span> <span class="string">canary</span> <span class="string">to</span> <span class="string">dl.deno.land</span> <span class="string">(unix)</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    runner.os != &#x27;Windows&#x27; &amp;&amp;</span></span><br><span class="line"><span class="string">    matrix.kind == &#x27;test_release&#x27; &amp;&amp;</span></span><br><span class="line"><span class="string">    github.repository == &#x27;denoland/deno&#x27; &amp;&amp;</span></span><br><span class="line"><span class="string">    github.ref == &#x27;refs/heads/master&#x27;</span></span><br><span class="line"><span class="string"></span>  <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    gsutil cp ./target/release/*.zip gs://dl.deno.land/canary/$(git rev-parse HEAD)/</span></span><br><span class="line"><span class="string">    echo $(git rev-parse HEAD) &gt; canary-latest.txt</span></span><br><span class="line"><span class="string">    gsutil cp canary-latest.txt gs://dl.deno.land/canary-latest.txt</span></span><br></pre></td></tr></table></figure><ul><li>执行条件：操作系统不是 Windows、构建矩阵 kind 是 <code>&#39;test_release&#39;</code>、github repo 是 <code>&#39;denoland/deno&#39;</code>、master 分支</li><li><code>run</code><ul><li><code>gsutil cp ./target/release/*.zip gs://dl.deno.land/canary/$(git rev-parse HEAD)/</code>：上传压缩包到服务器<ul><li><code>$(git rev-parse HEAD)</code>：获取最新的 git commit hash，你可以再任一 git 库中执行 <code>echo $(git rev-parse HEAD)</code> 验证</li></ul></li><li><code>echo $(git rev-parse HEAD) &gt; canary-latest.txt</code>：将 commit hash 值作为版本写入 canary-latest.txt</li><li><code>gsutil cp canary-latest.txt gs://dl.deno.land/canary-latest.txt</code>：上传 <code>canary-latest.txt</code> 到服务器</li></ul></li></ul><h3 id="23、Upload-canary-to-dl-deno-land-windows"><a href="#23、Upload-canary-to-dl-deno-land-windows" class="headerlink" title="23、Upload canary to dl.deno.land (windows)"></a>23、Upload canary to dl.deno.land (windows)</h3><blockquote><p>和 22 作用一样，just for windows</p></blockquote><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Upload</span> <span class="string">canary</span> <span class="string">to</span> <span class="string">dl.deno.land</span> <span class="string">(windows)</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    runner.os == &#x27;Windows&#x27; &amp;&amp;</span></span><br><span class="line"><span class="string">    matrix.kind == &#x27;test_release&#x27; &amp;&amp;</span></span><br><span class="line"><span class="string">    github.repository == &#x27;denoland/deno&#x27; &amp;&amp;</span></span><br><span class="line"><span class="string">    github.ref == &#x27;refs/heads/master&#x27;</span></span><br><span class="line"><span class="string"></span>  <span class="attr">env:</span></span><br><span class="line">    <span class="attr">CLOUDSDK_PYTHON:</span> <span class="string">$&#123;&#123;env.pythonLocation&#125;&#125;\python.exe</span></span><br><span class="line">  <span class="attr">shell:</span> <span class="string">bash</span></span><br><span class="line">  <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    gsutil cp ./target/release/*.zip gs://dl.deno.land/canary/$(git rev-parse HEAD)/</span></span><br><span class="line"><span class="string">    echo $(git rev-parse HEAD) &gt; canary-latest.txt</span></span><br><span class="line"><span class="string">    gsutil cp canary-latest.txt gs://dl.deno.land/canary-latest.txt</span></span><br></pre></td></tr></table></figure><ul><li><code>CLOUDSDK_PYTHON: $&#123;&#123;env.pythonLocation&#125;&#125;\python.exe</code>：从这里可以看到 Python 环境是给 google cloud sdk 用的</li></ul><h3 id="24、Test-release"><a href="#24、Test-release" class="headerlink" title="24、Test release"></a>24、Test release</h3><blockquote><p>测试发布</p></blockquote><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Test</span> <span class="string">release</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">matrix.kind</span> <span class="string">==</span> <span class="string">&#x27;test_release&#x27;</span></span><br><span class="line">  <span class="attr">run:</span> <span class="string">cargo</span> <span class="string">test</span> <span class="string">--release</span> <span class="string">--locked</span> <span class="string">--all-targets</span></span><br></pre></td></tr></table></figure><h3 id="25、Test-debug"><a href="#25、Test-debug" class="headerlink" title="25、Test debug"></a>25、Test debug</h3><blockquote><p>测试 debug</p></blockquote><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Test</span> <span class="string">debug</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">matrix.kind</span> <span class="string">==</span> <span class="string">&#x27;test_debug&#x27;</span></span><br><span class="line">  <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    cargo test --locked --doc</span></span><br><span class="line"><span class="string">    cargo test --locked --all-targets</span></span><br></pre></td></tr></table></figure><h3 id="26、Configure-hosts-file-for-WPT-unix"><a href="#26、Configure-hosts-file-for-WPT-unix" class="headerlink" title="26、Configure hosts file for WPT (unix)"></a>26、Configure hosts file for WPT (unix)</h3><h3 id="26、Configure-hosts-file-for-WPT-unix-1"><a href="#26、Configure-hosts-file-for-WPT-unix-1" class="headerlink" title="26、Configure hosts file for WPT (unix)"></a>26、Configure hosts file for WPT (unix)</h3><blockquote><p>WPT 是 Web Platform Test 的意思，对应仓库 <a href="https://github.com/denoland/wpt">denoland&#x2F;wpt</a></p></blockquote><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Configure</span> <span class="string">hosts</span> <span class="string">file</span> <span class="string">for</span> <span class="string">WPT</span> <span class="string">(unix)</span></span><br><span class="line"> <span class="attr">if:</span> <span class="string">runner.os</span> <span class="type">!=</span> <span class="string">&#x27;Windows&#x27;</span></span><br><span class="line"> <span class="attr">run:</span> <span class="string">./wpt</span> <span class="string">make-hosts-file</span> <span class="string">|</span> <span class="string">sudo</span> <span class="string">tee</span> <span class="string">-a</span> <span class="string">/etc/hosts</span></span><br><span class="line"> <span class="attr">working-directory:</span> <span class="string">test_util/wpt/</span></span><br></pre></td></tr></table></figure><h3 id="27、Configure-hosts-file-for-WPT-windows"><a href="#27、Configure-hosts-file-for-WPT-windows" class="headerlink" title="27、Configure hosts file for WPT (windows)"></a>27、Configure hosts file for WPT (windows)</h3><blockquote><p>和 26 作用一样，just for windows</p></blockquote><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Configure</span> <span class="string">hosts</span> <span class="string">file</span> <span class="string">for</span> <span class="string">WPT</span> <span class="string">(windows)</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">runner.os</span> <span class="string">==</span> <span class="string">&#x27;Windows&#x27;</span></span><br><span class="line">  <span class="attr">working-directory:</span> <span class="string">test_util/wpt/</span></span><br><span class="line">  <span class="attr">run:</span> <span class="string">python</span> <span class="string">wpt</span> <span class="string">make-hosts-file</span> <span class="string">|</span> <span class="string">Out-File</span> <span class="string">$env:SystemRoot\System32\drivers\etc\hosts</span> <span class="string">-Encoding</span> <span class="string">ascii</span> <span class="string">-Append</span></span><br></pre></td></tr></table></figure><h3 id="28、Run-web-platform-tests-release"><a href="#28、Run-web-platform-tests-release" class="headerlink" title="28、Run web platform tests (release)"></a>28、Run web platform tests (release)</h3><blockquote><p>在 web 平台下运行测试（release）</p></blockquote><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Run</span> <span class="string">web</span> <span class="string">platform</span> <span class="string">tests</span> <span class="string">(release)</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">matrix.kind</span> <span class="string">==</span> <span class="string">&#x27;test_release&#x27;</span></span><br><span class="line">  <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    deno run --unstable --allow-write --allow-read --allow-net --allow-env --allow-run ./tools/wpt.ts setup</span></span><br><span class="line"><span class="string">    deno run --unstable --allow-write --allow-read --allow-net --allow-env --allow-run ./tools/wpt.ts run --quiet --release</span></span><br></pre></td></tr></table></figure><h3 id="29、Run-web-platform-tests-debug"><a href="#29、Run-web-platform-tests-debug" class="headerlink" title="29、Run web platform tests (debug)"></a>29、Run web platform tests (debug)</h3><blockquote><p>在 web 平台下运行测试（debug）</p></blockquote><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Run</span> <span class="string">web</span> <span class="string">platform</span> <span class="string">tests</span> <span class="string">(debug)</span></span><br><span class="line"> <span class="attr">if:</span> <span class="string">matrix.kind</span> <span class="string">==</span> <span class="string">&#x27;test_debug&#x27;</span></span><br><span class="line"> <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">   deno run --unstable --allow-write --allow-read --allow-net --allow-env --allow-run ./tools/wpt.ts setup</span></span><br><span class="line"><span class="string">   deno run --unstable --allow-write --allow-read --allow-net --allow-env --allow-run ./tools/wpt.ts run --quiet</span></span><br></pre></td></tr></table></figure><h3 id="30、Run-Benchmarks"><a href="#30、Run-Benchmarks" class="headerlink" title="30、Run Benchmarks"></a>30、Run Benchmarks</h3><blockquote><p>压力测试</p></blockquote><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Run</span> <span class="string">Benchmarks</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">matrix.kind</span> <span class="string">==</span> <span class="string">&#x27;bench&#x27;</span></span><br><span class="line">  <span class="attr">run:</span> <span class="string">cargo</span> <span class="string">bench</span></span><br></pre></td></tr></table></figure><ul><li>执行条件：构建矩阵 kind 为 <code>&#39;bench&#39;</code></li></ul><h3 id="31、Post-Benchmarks"><a href="#31、Post-Benchmarks" class="headerlink" title="31、Post Benchmarks"></a>31、Post Benchmarks</h3><blockquote><p>看起来是把压测数据存到 <code>denoland/benchmark_data</code></p></blockquote><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Post</span> <span class="string">Benchmarks</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    matrix.kind == &#x27;bench&#x27; &amp;&amp;</span></span><br><span class="line"><span class="string">    github.repository == &#x27;denoland/deno&#x27; &amp;&amp;</span></span><br><span class="line"><span class="string">    github.ref == &#x27;refs/heads/master&#x27;</span></span><br><span class="line"><span class="string"></span>  <span class="attr">env:</span></span><br><span class="line">    <span class="attr">DENOBOT_PAT:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.DENOBOT_PAT</span> <span class="string">&#125;&#125;</span></span><br><span class="line">  <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    git clone --depth 1 -b gh-pages https://$&#123;DENOBOT_PAT&#125;@github.com/denoland/benchmark_data.git gh-pages</span></span><br><span class="line"><span class="string">    deno run --unstable -A ./tools/build_benchmark_jsons.js --release</span></span><br><span class="line"><span class="string">    cd gh-pages</span></span><br><span class="line"><span class="string">    git config user.email &quot;propelml@gmail.com&quot;</span></span><br><span class="line"><span class="string">    git config user.name &quot;denobot&quot;</span></span><br><span class="line"><span class="string">    git add .</span></span><br><span class="line"><span class="string">    git commit --message &quot;Update benchmarks&quot;</span></span><br><span class="line"><span class="string">    git push origin gh-pages</span></span><br></pre></td></tr></table></figure><ul><li>执行条件：<code>denoland/deno</code> 库的 master 分支且构建矩阵的 kind 是 <code>&#39;bench&#39;</code></li></ul><h3 id="32、Worker-info"><a href="#32、Worker-info" class="headerlink" title="32、Worker info"></a>32、Worker info</h3><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Worker</span> <span class="string">info</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">matrix.kind</span> <span class="string">==</span> <span class="string">&#x27;bench&#x27;</span></span><br><span class="line">  <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    cat /proc/cpuinfo</span></span><br><span class="line"><span class="string">    cat /proc/meminfo</span></span><br></pre></td></tr></table></figure><ul><li>执行条件：构建矩阵 kind 是 <code>bench</code></li><li>run<ul><li><code>cat /proc/cpuinfo</code>：查看 cpu 信息</li><li><code>cat /proc/meminfo</code>：查看内存信息</li></ul></li></ul><h3 id="33、Upload-release-to-dl-deno-land-unix"><a href="#33、Upload-release-to-dl-deno-land-unix" class="headerlink" title="33、Upload release to dl.deno.land (unix)"></a>33、Upload release to dl.deno.land (unix)</h3><blockquote><p>上传 unix release 到 dl.deno.land</p></blockquote><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Upload</span> <span class="string">release</span> <span class="string">to</span> <span class="string">dl.deno.land</span> <span class="string">(unix)</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    runner.os != &#x27;Windows&#x27; &amp;&amp;</span></span><br><span class="line"><span class="string">    matrix.kind == &#x27;test_release&#x27; &amp;&amp;</span></span><br><span class="line"><span class="string">    github.repository == &#x27;denoland/deno&#x27; &amp;&amp;</span></span><br><span class="line"><span class="string">    startsWith(github.ref, &#x27;refs/tags/&#x27;)</span></span><br><span class="line"><span class="string"></span>  <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    gsutil cp ./target/release/*.zip gs://dl.deno.land/release/$&#123;GITHUB_REF#refs/*/&#125;/</span></span><br><span class="line"><span class="string">    echo $&#123;GITHUB_REF#refs/*/&#125; &gt; release-latest.txt</span></span><br><span class="line"><span class="string">    gsutil cp release-latest.txt gs://dl.deno.land/release-latest.txt</span></span><br></pre></td></tr></table></figure><h3 id="34、Upload-release-to-dl-deno-land-windows"><a href="#34、Upload-release-to-dl-deno-land-windows" class="headerlink" title="34、Upload release to dl.deno.land (windows)"></a>34、Upload release to dl.deno.land (windows)</h3><blockquote><p>上传 windows release 到 dl.deno.land</p></blockquote><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Upload</span> <span class="string">release</span> <span class="string">to</span> <span class="string">dl.deno.land</span> <span class="string">(windows)</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    runner.os == &#x27;Windows&#x27; &amp;&amp;</span></span><br><span class="line"><span class="string">    matrix.kind == &#x27;test_release&#x27; &amp;&amp;</span></span><br><span class="line"><span class="string">    github.repository == &#x27;denoland/deno&#x27; &amp;&amp;</span></span><br><span class="line"><span class="string">    startsWith(github.ref, &#x27;refs/tags/&#x27;)</span></span><br><span class="line"><span class="string"></span>  <span class="attr">env:</span></span><br><span class="line">    <span class="attr">CLOUDSDK_PYTHON:</span> <span class="string">$&#123;&#123;env.pythonLocation&#125;&#125;\python.exe</span></span><br><span class="line">  <span class="attr">shell:</span> <span class="string">bash</span></span><br><span class="line">  <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    gsutil cp ./target/release/*.zip gs://dl.deno.land/release/$&#123;GITHUB_REF#refs/*/&#125;/</span></span><br><span class="line"><span class="string">    echo $&#123;GITHUB_REF#refs/*/&#125; &gt; release-latest.txt</span></span><br><span class="line"><span class="string">    gsutil cp release-latest.txt gs://dl.deno.land/release-latest.txt</span></span><br></pre></td></tr></table></figure><h3 id="35、Upload-release-to-GitHub"><a href="#35、Upload-release-to-GitHub" class="headerlink" title="35、Upload release to GitHub"></a>35、Upload release to GitHub</h3><blockquote><p>上传 release 到 GitHub</p></blockquote><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Upload</span> <span class="string">release</span> <span class="string">to</span> <span class="string">GitHub</span></span><br><span class="line">  <span class="attr">uses:</span> <span class="string">softprops/action-gh-release@v1</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">|</span></span><br><span class="line"><span class="string">    matrix.kind == &#x27;test_release&#x27; &amp;&amp;</span></span><br><span class="line"><span class="string">    github.repository == &#x27;denoland/deno&#x27; &amp;&amp;</span></span><br><span class="line"><span class="string">    startsWith(github.ref, &#x27;refs/tags/&#x27;)</span></span><br><span class="line"><span class="string"></span>  <span class="attr">env:</span></span><br><span class="line">    <span class="attr">GITHUB_TOKEN:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.GITHUB_TOKEN</span> <span class="string">&#125;&#125;</span></span><br><span class="line">  <span class="attr">with:</span></span><br><span class="line">    <span class="attr">files:</span> <span class="string">|</span></span><br><span class="line"><span class="string">      target/release/deno-x86_64-pc-windows-msvc.zip</span></span><br><span class="line"><span class="string">      target/release/deno-x86_64-unknown-linux-gnu.zip</span></span><br><span class="line"><span class="string">      target/release/deno-x86_64-apple-darwin.zip</span></span><br><span class="line"><span class="string">      target/release/deno_src.tar.gz</span></span><br><span class="line"><span class="string">      target/release/lib.deno.d.ts</span></span><br><span class="line"><span class="string"></span>    <span class="attr">draft:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><ul><li><code>softprops/action-gh-release@v1</code>：发布 github release 用的 action，这个确实很棒，可以执行直接上传 release asset 等高级操作。我在 <a href="https://github.com/youngjuning/tuya-panel-demo/blob/master/.github/workflows/release.yml">tuya-panel-demo</a> 中使用了该插件。</li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/8fcf3519517c/</id>
    <link href="https://blog.zisheng.pro/posts/8fcf3519517c/"/>
    <published>2023-04-19T10:50:31.000Z</published>
    <summary>GitHub Action 是 GitHub 官方的 CI/CD 工具，相较于 Travis CI 和 Circle CI，更轻量和易于扩展，marketplace 中有大量社区贡献的插件。各大开源项目都纷纷转向使用 GitHub Action 作为持续集成的工具，比如本文的主角 Deno。</summary>
    <title>叮，一份 Deno GitHub Action 源码解析请查收</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React-Native/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/tags/React-Native/"/>
    <category term="调试" scheme="https://blog.zisheng.pro/tags/%E8%B0%83%E8%AF%95/"/>
    <category term="调试工具" scheme="https://blog.zisheng.pro/tags/%E8%B0%83%E8%AF%95%E5%B7%A5%E5%85%B7/"/>
    <category term="调试技巧" scheme="https://blog.zisheng.pro/tags/%E8%B0%83%E8%AF%95%E6%8A%80%E5%B7%A7/"/>
    <category term="Reactotron" scheme="https://blog.zisheng.pro/tags/Reactotron/"/>
    <category term="Flipper" scheme="https://blog.zisheng.pro/tags/Flipper/"/>
    <category term="React Native Debugger" scheme="https://blog.zisheng.pro/tags/React-Native-Debugger/"/>
    <category term="React Native Debug" scheme="https://blog.zisheng.pro/tags/React-Native-Debug/"/>
    <category term="React Native Debugging" scheme="https://blog.zisheng.pro/tags/React-Native-Debugging/"/>
    <content>
      <![CDATA[<p>人非圣贤孰能无过。</p><p>这句话还（更确切地说：尤其是…）适用于软件开发者。</p><p>这就是为什么在每一个开发者的职业生涯中最重要的是知道如何处理这些失误。作为一个 React Native 开发者，我经常想要写出没有 BUG 的代码，但是当出现问题时，我需要确保我能够追踪并解决问题。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b361ca265e1d478faf33e97edd22b1e2~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>在这篇文章中，我想讨论一些日常用于移动应用程序开发的工具和技术。我希望你可以发现一些对你的项目有帮助的东西。</p><h2 id="静态检测，类型检查和格式化"><a href="#静态检测，类型检查和格式化" class="headerlink" title="静态检测，类型检查和格式化"></a>静态检测，类型检查和格式化</h2><h2 id="静态检测"><a href="#静态检测" class="headerlink" title="静态检测"></a>静态检测</h2><p>Linting 是执行程序的过程，用于分析潜在的语法程序错误。JavaScript 中最著名的 Linting 插件有：</p><ul><li>ESLint - JavaScript 类型检查和格式化工具</li><li>Google’s Closure Compiler – 一个 JavaScript 优化器，可以更快速，更简洁地重写代码并检查本机 JavaScript 函数的使用。</li><li>JSLint – JavaScript 语法检查器和验证器</li></ul><p>现在 ESLint 处于一统江湖的地位，也是 TypeScript 官方的 Linting 插件。</p><h2 id="类型检查"><a href="#类型检查" class="headerlink" title="类型检查"></a>类型检查</h2><p>我使用 TypeScript（TS）或者 Flow 来做 类型检查。两者最主要的区别是前者是编程语言，而 Flow 是类型检查器。在我看来，TS 是更优的选择，因为大量类似于自动导入、自我证明和使用下一代 JavaScript 的能力。</p><blockquote><p>原文中说 TS 是编程语言，我不完全赞同。毕竟官方的定位也只是 JavaScript 的超集。</p></blockquote><h2 id="格式化代码"><a href="#格式化代码" class="headerlink" title="格式化代码"></a>格式化代码</h2><p>程序员大部分时间都在阅读代码，因此重点不是更快地阅读代码，而是快速理解代码。</p><p>为了做到快读理解代码，我们需要最有效的视觉表示。这就是为什么我们需要很好地格式化代码。</p><p>比较一下下面的代码片段：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b3f4cfbcf58a4726af1eb510386dc12a~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ec3a96ee27354026a8013168bbb9b053~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>我建议大家使用 Prettier 格式化代码 - 它很容易集成和配置。你也可以通过 <code>eslint-config-prettier</code> 和 <code>eslint-plugin-prettier</code> 这两个插件和 ESLint 配合使用。</p><h2 id="如何调试-React-Native-应用"><a href="#如何调试-React-Native-应用" class="headerlink" title="如何调试 React Native 应用"></a>如何调试 React Native 应用</h2><p>React Native 具有开发人员友好的环境，会告诉我们我们在警告或错误方面做错了什么：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5b33a0defc7b47c8b7de95526dd76514~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><h2 id="内置调试模式"><a href="#内置调试模式" class="headerlink" title="内置调试模式"></a>内置调试模式</h2><p>首先，您可以使用浏览器的内置调试模式（例如 Chrome 或 Safari）调试应用程序。</p><p>在 Chrome 中使用，你需要安装 <code>react-devtools</code>：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install -g react-devtools</span><br></pre></td></tr></table></figure><p>在开发模式下，您可以打开开发人员菜单并从那里开始调试您的应用程序。只需激活 <code>debug</code> 选项：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/682b8dec24284e48a01f6647eb1b333b~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>它将打开带有 <code>http://localhost8081/debugger-ui</code> 路径的 Chrome 浏览器标签。</p><p>在您的 Chrome 浏览器中，您应该看到以下屏幕：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a119459876cc44d7a6a7388114b92aa3~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>您也可以在 Safari 中调试应用的 iOS 版本，而不必启用 <code>Debug JS Remotely</code>。</p><p>怎么做？在 Safari 浏览器中，只需打开：</p><p><strong>Preferences → Advanced → Select “Show Develop menu in menu bar</strong></p><p>并选择 App 的 JSContext:</p><p><strong>Develop → Simulator → JSContext</strong></p><p>Safari 的 Web 检查器应打开，并应显示一个 <code>Console</code> 和一个 <code>Debugger</code>。每次重新加载应用程序时（使用实时 <code>live reload</code>、<code>fast refresh</code> 或通过手动重新加载），一个新的 JSContext 将被创建。只需选择 <code>Automatically Show Web Inspectors for JSContexts</code>，就可以避免手动选择最新的 JSContext。</p><h2 id="Reactotron"><a href="#Reactotron" class="headerlink" title="Reactotron"></a>Reactotron</h2><p>如果您习惯将 Redux 用于 React Native 或 ReactJS 的状态管理，<a href="https://infinite.red/reactotron">Reactotron</a> 是调试状态的绝佳工具：</p><ul><li>查看应用状态</li><li>显示 API 请求和响应</li><li>执行快速的性能压测</li><li>订阅应用的部分状态</li><li>显示类似于 <code>console.log</code> 的消息</li><li>使用 <code>source-mapped</code> 堆栈跟踪（包括 saga 堆栈跟踪）跟踪全局错误！</li><li>dispatch actions like a government-run mind control experiment</li><li>使用 Redux 或 mobx-state-tree 热交换您应用的状态</li><li>追踪你的 saga</li><li>在 React Native 中显示图像浮层</li><li>在 React Native 中跟踪您的异步存储</li></ul><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2e43cd0a09634d70891b33ae4b563a4a~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><h2 id="React-Native-Debugger"><a href="#React-Native-Debugger" class="headerlink" title="React Native Debugger"></a>React Native Debugger</h2><p><a href="https://github.com/jhen0409/react-native-debugger">react-native-debugger</a> 这是一个桌面应用程序，具有许多可调试应用程序的功能。至于应该提到的最重要的优点：</p><ul><li>它基于官方的 Remote Debugger 并提供更多功能。</li><li>它包括来自 react-devtools-core 的 React Inspector。</li><li>它包括 Redux DevTools，并使用 redux-devtools-extension 制作了相同的 API。</li></ul><blockquote><p>注意: 如果你使用了 0.62 版本以上的 React Native，请使用 React Native Debugger v0.11</p></blockquote><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/be725c70791d4971b5a4eaefb43ad740~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><h2 id="Native-Logs"><a href="#Native-Logs" class="headerlink" title="Native Logs"></a>Native Logs</h2><p>根据<a href="https://reactnative.dev/docs/debugging#accessing-console-logs">官方</a>的 React Native 文档，您可以轻松记录两种平台的报告：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ npx react-native log-ios</span><br><span class="line">$ npx react-native log-android</span><br></pre></td></tr></table></figure><p>如果您需要进一步研究，可以使用特定于平台的 IDE（例如 <a href="https://developer.apple.com/xcode/">XCode</a> 或 <a href="https://developer.android.com/studio">Android Studio</a>）来分析应用程序的本机代码并解决问题。</p><h2 id="Flipper"><a href="#Flipper" class="headerlink" title="Flipper"></a>Flipper</h2><p><a href="https://fbflipper.com/">Flipper</a> 是用于调试 iOS、Android 和 React Native 应用程序的下一代平台。这是一个桌面应用程序，可让您灵活地检查、可视化和控制应用程序开发调试。这里很酷的功能是您可以通过扩展来更新 Flipper 的功能，例如：</p><ul><li><a href="https://github.com/jk-gan/redux-flipper">https://github.com/jk-gan/redux-flipper</a></li><li><a href="https://github.com/blankapp/flipper-plugin-reduxinspector">https://github.com/blankapp/flipper-plugin-reduxinspector</a></li></ul><p>如果您想集成 Redux 日志支持。</p><p>Flipper 支持 React Native Debugger 所有的功能，但是它也增加了很多。查看<a href="https://www.flippercloud.io/docs">官方文档</a>，以确保并可能创建自己的插件并支持社区。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/14f985e6f5da4c238c751d873772cc7c~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><blockquote><p>本文翻译自 <a href="https://www.ideamotive.co/blog/how-to-debug-your-react-native-apps-like-a-pro">How To Debug React Native Apps Like A Pro? (Tools And Best Practices)</a></p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/1219c44abdaa/</id>
    <link href="https://blog.zisheng.pro/posts/1219c44abdaa/"/>
    <published>2023-04-19T10:43:42.000Z</published>
    <summary>在每一个开发者的职业生涯中最重要的是知道如何处理这些失误。作为一个 React Native 开发者，我经常想要写出没有 BUG 的代码，但是当出现问题时，我需要确保我能够追踪并解决问题。</summary>
    <title>React Native 调试最佳实践</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="Rust" scheme="https://blog.zisheng.pro/categories/Rust/"/>
    <category term="Deno" scheme="https://blog.zisheng.pro/categories/Deno/"/>
    <category term="Node.js" scheme="https://blog.zisheng.pro/tags/Node-js/"/>
    <category term="Rust" scheme="https://blog.zisheng.pro/tags/Rust/"/>
    <category term="Deno" scheme="https://blog.zisheng.pro/tags/Deno/"/>
    <category term="WebAssembly" scheme="https://blog.zisheng.pro/tags/WebAssembly/"/>
    <category term="Web" scheme="https://blog.zisheng.pro/tags/Web/"/>
    <content>
      <![CDATA[<h2 id="为什么选择-Rust"><a href="#为什么选择-Rust" class="headerlink" title="为什么选择 Rust?"></a>为什么选择 Rust?</h2><p>Rust 是一门可以帮助你开发出高效率、高可靠性软件的编程语言。相较于 C&#x2F;C++，Rust 力图同时提供强大的工程能力及良好的开发体验，在给予开发者控制底层能力（比如内存操作）的同时，避免传统语言带来的诸多麻烦。</p><h3 id="高性能"><a href="#高性能" class="headerlink" title="高性能"></a>高性能</h3><p>Rust 速度惊人且内存利用率极高。由于没有运行时和垃圾回收，它能够胜任对性能要求特别高的服务，可以在嵌入式设备上运行，还能轻松和其他语言集成。</p><h3 id="可靠性"><a href="#可靠性" class="headerlink" title="可靠性"></a>可靠性</h3><p>Rust 丰富的类型系统和所有权模型保证了内存安全和线程安全，让您在编译期就能够消除各种各样的错误。</p><h3 id="生产力"><a href="#生产力" class="headerlink" title="生产力"></a>生产力</h3><p>Rust 拥有出色的文档、友好的编译器和清晰的错误提示信息，还集成了一流的工具——包管理器和构建工具， 能地自动补全和类型检验的多编辑器支持，以及自动格式化代码等等。</p><h2 id="Rust-落地情况"><a href="#Rust-落地情况" class="headerlink" title="Rust 落地情况"></a>Rust 落地情况</h2><p>全世界已有数百家公司在生产环境中使用 Rust，以达到快速、跨平台、低资源占用的目的。很多著名且受欢迎的软件，例如 Firefox、 Dropbox 和 Cloudflare 都在使用 Rust。从初创公司到大型企业，从嵌入式设备到可扩展的 Web 服务，Rust 都完全合适。</p><h3 id="Dropbox"><a href="#Dropbox" class="headerlink" title="Dropbox"></a>Dropbox</h3><p>Dropbox 核心文件存储系统的多个模块是用 Rust 写的，因为它作为大型项目的一部分，需要追求更高的数据中心效率。目前，它应用于所有的 Dropbox 存储系统，为超过 5 亿用户服务。</p><h3 id="Yelp"><a href="#Yelp" class="headerlink" title="Yelp"></a>Yelp</h3><p>Yelp 使用 Rust 开发了一个实时 A&#x2F;B 测试的框架。它广泛应用于所有的 Yelp 网页和应用，选择 Rust 是因为它和 C 语言一样快（运行代价小），同时比 C 语言更安全（易于维护）。</p><h2 id="Deno-与-Rust"><a href="#Deno-与-Rust" class="headerlink" title="Deno 与 Rust"></a>Deno 与 Rust</h2><p>Deno 早期是 Golang + TypeScript 的，后来因为 Golang 和 V8 的 GC 问题，Ryan 下了大工夫把 golang 统统换成没有 GC 的 Rust了。知乎看到的一个<a href="https://www.zhihu.com/question/291707891/answer/481908886">回答</a>觉得很有道理：</p><ol><li>Go 自己有 GC，V8 自己也有 GC，双 GC 和复杂的运行时会造成多少性能损失。</li><li>Rust 具有高性能、内存安全等特性，非常适合写这种对性能要求高的程序。</li></ol><blockquote><p>GC（Garbage Collector，垃圾回收机制），Rust 和其他没有垃圾回收机制，而是使用一种叫做所有权的机制代替。由于是入门篇，此处不展开。</p></blockquote><h2 id="安装-Rust"><a href="#安装-Rust" class="headerlink" title="安装 Rust"></a>安装 Rust</h2><p>和其他不太一样的是，Rust 官方提供了 rustup 命令行工具来下载、安装、管理不同的 Rust 发行版本以及其附带的工具链。</p><blockquote><p>注意：Rust 的稳定性保证了所有发行版本都是向后兼容的。</p></blockquote><p><strong>安装 rustup：</strong></p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl --proto <span class="string">&#x27;=https&#x27;</span> --tlsv1.2 -sSf https://sh.rustup.rs | sh</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line">$ curl --proto <span class="string">&#x27;=https&#x27;</span> --tlsv1.2 -sSf https://sh.rustup.rs | sh</span><br><span class="line">info: downloading installer</span><br><span class="line"></span><br><span class="line">Welcome to Rust!</span><br><span class="line"></span><br><span class="line">This will download and install the official compiler <span class="keyword">for</span> the Rust</span><br><span class="line">programming language, and its package manager, Cargo.</span><br><span class="line"></span><br><span class="line">Rustup metadata and toolchains will be installed into the Rustup</span><br><span class="line">home directory, located at:</span><br><span class="line"></span><br><span class="line">  /Users/luozhu/.rustup</span><br><span class="line"></span><br><span class="line">This can be modified with the RUSTUP_HOME environment variable.</span><br><span class="line"></span><br><span class="line">The Cargo home directory located at:</span><br><span class="line"></span><br><span class="line">  /Users/luozhu/.cargo</span><br><span class="line"></span><br><span class="line">This can be modified with the CARGO_HOME environment variable.</span><br><span class="line"></span><br><span class="line">The cargo, rustc, rustup and other commands will be added to</span><br><span class="line">Cargo<span class="string">&#x27;s bin directory, located at:</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  /Users/luozhu/.cargo/bin</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">This path will then be added to your PATH environment variable by</span></span><br><span class="line"><span class="string">modifying the profile files located at:</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  /Users/luozhu/.profile</span></span><br><span class="line"><span class="string">  /Users/luozhu/.zshenv</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">You can uninstall at any time with rustup self uninstall and</span></span><br><span class="line"><span class="string">these changes will be reverted.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">Current installation options:</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">   default host triple: x86_64-apple-darwin</span></span><br><span class="line"><span class="string">     default toolchain: stable (default)</span></span><br><span class="line"><span class="string">               profile: default</span></span><br><span class="line"><span class="string">  modify PATH variable: yes</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">1) Proceed with installation (default)</span></span><br><span class="line"><span class="string">2) Customize installation</span></span><br><span class="line"><span class="string">3) Cancel installation</span></span><br><span class="line"><span class="string">&gt;1</span></span><br></pre></td></tr></table></figure><p>以上信息可以总结为：</p><ol><li>Rustup 家目录：<code>~/.rustup</code>，可以用 <code>RUSTUP_HOME</code> 环境变量修改</li><li>Cargo 家目录：<code>~/.cargo</code>，可以用 <code>CARGO_HOME</code> 环境变量修改</li><li>cargo、rustc、rustup 和其他命令都安装在 <code>~/.cargo/bin</code></li><li>PATH 环境变量是放在 <code>~/.zshenv</code> 中的（这个方式第一次见）</li></ol><p>这里让我们选择安装方式，由于是入门，我们直接选择默认安装，输入如下：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">info: profile <span class="built_in">set</span> to <span class="string">&#x27;default&#x27;</span></span><br><span class="line">info: default host triple is x86_64-apple-darwin</span><br><span class="line">info: syncing channel updates <span class="keyword">for</span> <span class="string">&#x27;stable-x86_64-apple-darwin&#x27;</span></span><br><span class="line">info: latest update on 2021-02-11, rust version 1.50.0 (cb75ad5db 2021-02-10)</span><br><span class="line">info: downloading component <span class="string">&#x27;cargo&#x27;</span></span><br><span class="line">  4.3 MiB /   4.3 MiB (100 %)   3.1 MiB/s <span class="keyword">in</span>  1s ETA:  0s</span><br><span class="line">info: downloading component <span class="string">&#x27;clippy&#x27;</span></span><br><span class="line">info: downloading component <span class="string">&#x27;rust-docs&#x27;</span></span><br><span class="line"> 14.7 MiB /  14.7 MiB (100 %)   2.2 MiB/s <span class="keyword">in</span>  6s ETA:  0s</span><br><span class="line">info: downloading component <span class="string">&#x27;rust-std&#x27;</span></span><br><span class="line"> 23.3 MiB /  23.3 MiB (100 %)   2.2 MiB/s <span class="keyword">in</span> 11s ETA:  0s</span><br><span class="line">info: downloading component <span class="string">&#x27;rustc&#x27;</span></span><br><span class="line"> 61.0 MiB /  61.0 MiB (100 %)   2.3 MiB/s <span class="keyword">in</span> 26s ETA:  0s</span><br><span class="line">info: downloading component <span class="string">&#x27;rustfmt&#x27;</span></span><br><span class="line">info: installing component <span class="string">&#x27;cargo&#x27;</span></span><br><span class="line">info: using up to 500.0 MiB of RAM to unpack components</span><br><span class="line">info: installing component <span class="string">&#x27;clippy&#x27;</span></span><br><span class="line">info: installing component <span class="string">&#x27;rust-docs&#x27;</span></span><br><span class="line"> 14.7 MiB /  14.7 MiB (100 %)   5.8 MiB/s <span class="keyword">in</span>  1s ETA:  0s</span><br><span class="line">info: installing component <span class="string">&#x27;rust-std&#x27;</span></span><br><span class="line"> 23.3 MiB /  23.3 MiB (100 %)  13.0 MiB/s <span class="keyword">in</span>  3s ETA:  0s</span><br><span class="line">info: installing component <span class="string">&#x27;rustc&#x27;</span></span><br><span class="line"> 61.0 MiB /  61.0 MiB (100 %)  13.3 MiB/s <span class="keyword">in</span>  4s ETA:  0s</span><br><span class="line">info: installing component <span class="string">&#x27;rustfmt&#x27;</span></span><br><span class="line">info: default toolchain <span class="built_in">set</span> to <span class="string">&#x27;stable-x86_64-apple-darwin&#x27;</span></span><br><span class="line"></span><br><span class="line">  stable-x86_64-apple-darwin installed - rustc 1.50.0 (cb75ad5db 2021-02-10)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">Rust is installed now. Great!</span><br><span class="line"></span><br><span class="line">To get started you need Cargo<span class="string">&#x27;s bin directory ($HOME/.cargo/bin) in your PATH</span></span><br><span class="line"><span class="string">environment variable. Next time you log in this will be done</span></span><br><span class="line"><span class="string">automatically.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">To configure your current shell, run:</span></span><br><span class="line"><span class="string">source $HOME/.cargo/env</span></span><br></pre></td></tr></table></figure><p>以上这个过程是安装 rust 及其工具链并自动设置环境的过程，你可以根据提示输入 <code>source $HOME/.cargo/env</code> 让配置立即生效，也可以重新打开一个命令行让配置生效。</p><p><strong>验证是否安装成功：</strong></p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rustc --version</span><br></pre></td></tr></table></figure><p>顺利的话，你会看到 <code>rustc x.y.z (版本的哈希码 yyyy-mm-dd)</code> 格式的输出。</p><p><strong>更新：</strong></p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rustup update</span><br></pre></td></tr></table></figure><p><strong>卸载：</strong></p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rustup self uninstall</span><br></pre></td></tr></table></figure><h2 id="本地文档"><a href="#本地文档" class="headerlink" title="本地文档"></a>本地文档</h2><p>安装工具在执行的过程中会在本地生成一份离线文档，你可以通过命令 <code>rustup doc</code> 在浏览器中打开它。但是我觉得一般用不到，我们在 vscode 中安装 Rust 插件有文档提示的功能。</p><h2 id="Hello-World"><a href="#Hello-World" class="headerlink" title="Hello,World!"></a>Hello,World!</h2><h3 id="vscode"><a href="#vscode" class="headerlink" title="vscode"></a>vscode</h3><p>安装 Rust 插件：</p><blockquote><p>rust-analyzer 也是一个很好的替代品，不过目前还不完善！</p></blockquote><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1719649428934.png" alt="紫升"></p><p>安装成功后将 <code>&quot;rust-client.rustupPath&quot;: &quot;$HOME/.cargo/bin/rustup&quot;</code> 添加到 <code>setting.json</code> 来修复 <a href="https://github.com/rust-lang/vscode-rust/issues/622">couldn’t start client Rust Language Server</a></p><p>为了能调试软件，再安装插件 CodeLLDB：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1719649496835.png" alt="紫升"></p><p>crates 是辅助开发者在使用 <code>Cargo.toml</code>时管理依赖的插件：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1719732946888.png" alt="紫升"></p><p>由于 Rust 的依赖管理工具 Cargo 使用 TOML 作为配置文件，所以需要下载 <code>Even Better TOML</code> 插件支持语法。</p><p>最后设置下保存时自动格式化：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// settings.json</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;[rust]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;editor.formatOnSave&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="第一行代码"><a href="#第一行代码" class="headerlink" title="第一行代码"></a>第一行代码</h3><blockquote><p>注意：Rust 文件总是以 <code>rs</code> 结尾。如果在名字中使用了多个单词，那么你可以使用下划线来隔开它们。比如你最好使用 <code>hello_world.rs</code> 作为文件名而不是 helloworld.rs</p></blockquote><p>新建一个 <code>main.rs</code> 并写入以下代码：</p><figure class="highlight rs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">  <span class="built_in">println!</span>(<span class="string">&quot;Hello,World!&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>使用关键字 <code>fn</code> 定义了一个函数。（Go（<code>func</code>）、JS（<code>function</code>）、数学（<code>f</code>）），看起来 Rust 很省布料。</li><li>main 函数比较特殊：当你运行一个可执行 Rust 程序的时候，所有的代码都会从这个入口函数开始运行（Go 也是一样）</li><li>按照惯例，Rust 推荐把左花括号与函数声明置于同一行并以空格分隔。</li><li>我们调用了一个被叫作 <code>println!</code> 的宏：假如我们调用的是一个普通函数，那么这里会以去掉 <code>!</code> 符号的 <code>println</code> 来进行标记。</li><li>大部分 Rust 代码都会以分号来结尾。</li></ul><blockquote><p>所谓宏（Macro），是一种批量处理的称谓。计算机科学里的宏是一种抽象(Abstraction)，它根据一系列预定义的规则替换一定的文本模式。解释器或编译器在遇到宏时会自动进行这一模式替换。—— 可以简单理解为全局定义的函数、变量等</p></blockquote><p>然后在命令行输入 <code>rustc main.rs</code> 编译代码，编译成功后会生成可执行文件 <code>main</code>:</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">├── main</span><br><span class="line">└── main.rs</span><br></pre></td></tr></table></figure><p>执行 <code>./main</code> 便会输出 <code>Hello,World!</code></p><blockquote><p>windows 下会生成 <code>main.exe</code></p></blockquote><blockquote><p>和 Ruby、Python 或 JavaScript 之类的动态语言不同，Rust、Go、Deno 都属于编译语言（JIT技术不在讨论范围），这意味这当你编译完 Rust 程序之后，便可以将可执行文件交付于其他人，并运行在没有安装 Rust 的环境中。如果你使用 Node 开发的应用交付给别人，别人必须要拥有 Node 开发环境。</p></blockquote><h2 id="Hello-Cargo"><a href="#Hello-Cargo" class="headerlink" title="Hello,Cargo!"></a>Hello,Cargo!</h2><p>和 Go 一样（go build），使用系统的编译工具（rustc）编译简单的程序足够了，但是它们无法满足规模大、协同开发人员多的项目。Go 有 gox、goxc、xgo，Rust 有 Cargo 来帮助我们管理简化项目依赖、代码构建这样的事情。</p><p>Cargo 是 Rust 工具链中内置的构建系统及包管理器。由于它可以处理众多诸如构建代码、下载编译依赖库等琐碎但重要的任务，绝大多数 Rust 用会选择它来管理自己的 Rust 项目（比如 Deno）。</p><p>前面我们的 helloworld 示例在 vscode 中，Rust 插件会报以下错误，可见 Cargo 是 Rust 的标配：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1719733630580.png" alt="紫升"></p><h2 id="使用-Cargo-创建项目"><a href="#使用-Cargo-创建项目" class="headerlink" title="使用 Cargo 创建项目"></a>使用 Cargo 创建项目</h2><p>使用 <code>cargo new hello_cargo</code> 新建项目：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1719733650171.png" alt="紫升"></p><p>Cargo 为我们生成了两个文件与一个目录：一个名为 <code>Cargo.toml</code> 的文件，以及一个名为 <code>main.rs</code> 的源代码文件<br>，该源代码被放置在 src 目录下。于此同时，Cargo 还会初始化一个新的 Git 仓库并升成默认的 <code>.gitignore</code> 文件。</p><h3 id="Cargo-toml"><a href="#Cargo-toml" class="headerlink" title="Cargo.toml"></a>Cargo.toml</h3><figure class="highlight toml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[package]</span></span><br><span class="line"><span class="attr">name</span> = <span class="string">&quot;hello_cargo&quot;</span></span><br><span class="line"><span class="attr">version</span> = <span class="string">&quot;0.1.0&quot;</span></span><br><span class="line"><span class="attr">authors</span> = [<span class="string">&quot;youngjuning &lt;youngjuning@163.com&gt;&quot;</span>]</span><br><span class="line"><span class="attr">edition</span> = <span class="string">&quot;2018&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html</span></span><br><span class="line"></span><br><span class="line"><span class="section">[dependencies]</span></span><br></pre></td></tr></table></figure><ul><li><code>[package]</code>：是一个区域标签，它表明接下来的语句会被用于配置当前的程序包。<code>author</code> 这里的邮箱和名字是从 <code>~/.gitconfig</code> 中读取的</li><li><code>[dependencies]</code>：同样是一个区域标签，它表明随后的区域会被用来声明项目的依赖。</li></ul><blockquote><p>TOML 是一种旨在成为一个小规模、易于使用的语义化的配置文件格式，“TOML”这个名字是“Tom’s Obvious, Minimal Language（汤姆的浅显的、极简的语言）”的首字母略写词。“Tom”指它的作者 Tom Preston-Werner。</p></blockquote><p>按照惯例，Cargo 会默认把所有的源代码文件保存到 <code>src</code> 目录下，而项目根目录只被用来存放诸如 <code>README</code> 文档、许可声明、配置文件等与源代码无关的文件。使用 Cargo 可以帮助你合理并一致地组织自己的项目文件，从而使一切井井有条。</p><h3 id="使用-Cargo-构建和运行项目"><a href="#使用-Cargo-构建和运行项目" class="headerlink" title="使用 Cargo 构建和运行项目"></a>使用 Cargo 构建和运行项目</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cargo build</span><br></pre></td></tr></table></figure><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1719733752129.png" alt="紫升"></p><p>与之前不同，这个命令会将可执行程序生成在路径 <code>target/debug/hello_cargo</code> 下：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1719734023741.png" alt="紫升"></p><p>首次使用命令 cargo build 构建的时候，会生成 <code>Cargo.lock</code>（类似于 yarn.lock），不要手动修改该文件，默认 <code>.gitignore</code> 中只忽略了 <code>target</code> 目录，我们从 <code>https://github.com/github/gitignore/blob/master/Rust.gitignore</code> 中复制最佳实践过来。</p><p>使用 <code>cargo run</code> 命令可以简单地以此完成编译和运行工作：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1719734443602.png" alt="紫升"></p><blockquote><p>我们还可以使用 <code>cargo check</code> 快速检查项目是否可以通过编译</p></blockquote><h3 id="以-Release-模式构建"><a href="#以-Release-模式构建" class="headerlink" title="以 Release 模式构建"></a>以 Release 模式构建</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cargo build --release</span><br></pre></td></tr></table></figure><p>生成的可执行文件会被放置在 <code>target/release</code> 目录下，而不是之前的 <code>target/debug</code> 目录下。</p><p>假如你想要对代码的运行效率进行基准测试，请通过 <code>cargo run --release</code> 命令进行构建，并使用 <code>target/release</code> 目录下的可执行程序完成基准测试。</p><h3 id="crate"><a href="#crate" class="headerlink" title="crate"></a>crate</h3><p>在 Rust 中，我们把代码的集合称作包（crate）。crate 是 Rust 中最小的编译单元，package 是单个或多个 crate 的集合，crate 和 package 都可以被叫作包，因为单个 crate 也是一个 package，但 package 通常倾向于多个 crate 的组合。<a href="https://crates.io/">crates.io</a> 是 Rust 社区的 crate 代理。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/395237906371/</id>
    <link href="https://blog.zisheng.pro/posts/395237906371/"/>
    <published>2023-04-19T10:32:26.000Z</published>
    <summary>Rust 是一门可以帮助你开发出高效率、高可靠性软件的编程语言。相较于 C/C++，Rust 力图同时提供强大的工程能力及良好的开发体验，在给予开发者控制底层能力（比如内存操作）的同时，避免传统语言带来的诸多麻烦。</summary>
    <title>Rust 极速入门教程</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="Rust" scheme="https://blog.zisheng.pro/categories/Rust/"/>
    <category term="紫升的博客" scheme="https://blog.zisheng.pro/tags/%E7%B4%AB%E5%8D%87%E7%9A%84%E5%8D%9A%E5%AE%A2/"/>
    <category term="紫升" scheme="https://blog.zisheng.pro/tags/%E7%B4%AB%E5%8D%87/"/>
    <category term="Rust 语法" scheme="https://blog.zisheng.pro/tags/Rust-%E8%AF%AD%E6%B3%95/"/>
    <content>
      <![CDATA[<div class="note primary modern"><p>本文部分示例代码在 <a href="https://github.com/youngjuning/learn-rust">youngjuning&#x2F;learn-rust</a></p></div><p>语言之间都有着类似的核心特性，比如变量、基本类型、函数、注释和控制流程等概念。但是每个语言有都有自己的独有概念，本文便是记录了我在学习 Rust 语法过程中遇到的 Rust 独有概念。</p><h2 id="变量与可变性"><a href="#变量与可变性" class="headerlink" title="变量与可变性"></a>变量与可变性</h2><p>Rust 中的变量默认是不可变的。Rust 语言提供这一概念是为了能够让你安全且方便地写出复杂、甚至是并行的代码。当然，Rust 也提供了让你可以使用可变变量的方法。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// main.rs</span></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">  <span class="keyword">let</span> <span class="variable">x</span> = <span class="number">5</span>;</span><br><span class="line">  <span class="built_in">println!</span>(<span class="string">&quot;The value of x is: &#123;&#125;&quot;</span>, x);</span><br><span class="line">  x = <span class="number">6</span>;</span><br><span class="line">  <span class="built_in">println!</span>(<span class="string">&quot;The value of x is: &#123;&#125;&quot;</span>, x)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>x = 6</code> 属于重复赋值，所以执行 <code>cargo run</code> 编译是不会通过的：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7c69b2d8dd78470d917c730a5fe5a0d0~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dcfbdcb5c80944149b31b331b8165aee~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>我们可以通过在声明的变量名称前添加 <code>mut</code> 关键字来使其可变。除了使变量的值可变，<code>mut</code> 还会向阅读代码的人暗示其他代码可能会改变这个变量的值。</p><h2 id="常量与变量的不同"><a href="#常量与变量的不同" class="headerlink" title="常量与变量的不同"></a>常量与变量的不同</h2><ol><li>我们不能使用 <code>mut</code> 关键字来修饰一个变量。常量不仅是默认不可变的，它还总是不可变的。</li><li>你需要使用 <code>const</code> 关键字而不是一个 <code>let</code> 关键字来声明一个常量。</li><li>在声明的同时，你必须显式地标注值的类型。</li><li>常量可以被声明在任何作用域中，甚至包括全局作用域。这在一个值需要被不同部分的代码共同引用时十分有用</li><li>你只能将一个常量绑定到一个常量表达式上，而无法将一个函数的返回值，或其他需要在运行时计算的值绑定到常量上。</li></ol><blockquote><p>我们约定俗称地使用以下划线分割的全大写字母来命名一个常量，并在数值中插入下划线来提高可读性：<code>const MAX_POINTS: u32 = 100_000;</code></p></blockquote><h2 id="隐藏"><a href="#隐藏" class="headerlink" title="隐藏"></a>隐藏</h2><p>在 Rust 中，新声明的变量可以覆盖掉旧的同名变量，我们把这一现象描述为：第一个变量被第二个变量隐藏（shadow）了。这意味着我们随后使用这个名称时，它指向的将会是第二个变量。我们可以重复使用 <code>let</code> 关键字并分配以相同的名称来不断地隐藏变量：</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">x</span> = <span class="number">5</span>;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">x</span> = x + <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">x</span> = x * <span class="number">2</span>;</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;The value of x is: &#123;&#125;&quot;</span>, x)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如下图，我们可以看到 JavaScript 中并没有该特性：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/42b566023d5f486996638d08cdbf31b3~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>隐藏机制和变量声明为 <code>mut</code> 的不同：</p><ol><li>如果不是在使用 <code>let</code> 重新为这个变量赋值，则会导致编译错误。通过使用 <code>let</code>，我们可以对这个值执行一系列的变换操作，并允许这个变量在操作完成后保持自己的不可变性。</li><li>由于重复使用 <code>let</code> 关键字会创建出新的变量，所以我们可以在复用变量名称的同时改变它的类型。</li></ol><h2 id="数据类型"><a href="#数据类型" class="headerlink" title="数据类型"></a>数据类型</h2><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8b6b8078eff445709283898b757e9834~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>RUST 的编译器可以根据我们如何绑定、使用变量的值来自动推导出变量的类型。但在无法自动推导的场景，就必须显式地添加一个类型标注。</p><h3 id="标量类型（scalar）"><a href="#标量类型（scalar）" class="headerlink" title="标量类型（scalar）"></a>标量类型（scalar）</h3><p><strong>标量</strong>类型是单个值类型的统称。Rust 中内建了 4 种基础的标量类型：整数、浮点数、布尔值及字符。这类似于 JavaScript 中的基础类型。</p><h4 id="整数类型"><a href="#整数类型" class="headerlink" title="整数类型"></a>整数类型</h4><p>整数是指那些没有小数部分的数字。</p><table><thead><tr><th>长度</th><th>有符号</th><th>无符号</th></tr></thead><tbody><tr><td>8 bit</td><td>i8（-128 到 127）</td><td>u8（0 到 255）</td></tr><tr><td>16 bit</td><td>i16（-32768 到 32767）</td><td>u16（0 到 65535）</td></tr><tr><td>32 bit</td><td>i32(默认)</td><td>u32</td></tr><tr><td>64 bit</td><td>i64</td><td>u64</td></tr><tr><td>arch</td><td>isize</td><td>usize</td></tr></tbody></table><blockquote><p>Isize 和 usize 是特殊的整数类型，它们的长度取决于程序运行的目标平台。在 64 位架构上，它们就是 64 位的，而在 32 位架构上，它们就是 32 位的。</p></blockquote><blockquote><p>可以使用 <code>_</code> 作为分隔符以方便读数，比如 <code>1_000</code></p></blockquote><p>如何选择：</p><ul><li>如果拿不定注意，Rust 对于整数字面量默认推导类型 i32 通常就是一个很好的选择：它在大部分情况下都是运算速度最快的</li><li>较为特殊的两个整数类型 <code>usize</code> 和 <code>isize</code> 则主要用作某些集合的索引</li></ul><h4 id="浮点数类型"><a href="#浮点数类型" class="headerlink" title="浮点数类型"></a>浮点数类型</h4><ul><li>单精度浮点数（<code>f32</code>）</li><li>双精度浮点数（<code>f64</code>）(默认)</li></ul><blockquote><p>由于在现代 CPU 中双精度和单精度的运行效率相差无几，却拥有更高的精读，所以在 Rust 中，默认会将浮点数字面量的类型推到为 f64。</p></blockquote><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">  <span class="keyword">let</span> <span class="variable">x</span> = <span class="number">2.0</span>; <span class="comment">// f64</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> <span class="variable">y</span>: <span class="type">f32</span> = <span class="number">3.0</span>; <span class="comment">// f32</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="布尔值"><a href="#布尔值" class="headerlink" title="布尔值"></a>布尔值</h4><p>和 Go 语言一样，Rust 的布尔类型只拥有两个可能的值：<code>true</code>和 <code>false</code>。相较于 Javascript 中<code>false</code>、<code>0</code>、 <code>NaN</code>、<code>&#39;&#39;</code>、<code>null</code>、<code>undefined</code> 6 种都可以被转换成 <code>false</code> 真的是很节省脑容量。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">  <span class="keyword">let</span> <span class="variable">t</span> = <span class="literal">true</span>;</span><br><span class="line">  <span class="keyword">let</span> <span class="variable">f</span>: <span class="type">bool</span> = <span class="literal">false</span> <span class="comment">// 附带了显式类型标注的语句</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="字符类型"><a href="#字符类型" class="headerlink" title="字符类型"></a>字符类型</h4><p>在 Rust 中，<code>char</code> 类型被用于描述语言中最基础的单个字符。需要注意的是，<code>char</code> 类型使用单引号指定，而不同于字符串使用双引号指定。</p><h3 id="复合类型（compound）"><a href="#复合类型（compound）" class="headerlink" title="复合类型（compound）"></a>复合类型（compound）</h3><p>复合类型可以将多个不同类型的值组合为一个类型。Rust 提供了两种内置的基础复合类型：元组（tuple）和数组（array）。</p><h4 id="元组类型"><a href="#元组类型" class="headerlink" title="元组类型"></a>元组类型</h4><ul><li>元组可以将其他不同类型的多个值进行组合</li><li>元组拥有一个固定的长度，我们无法在声明结束后增加或减少其中的元素数量</li></ul><p><strong>创建元组：</strong></p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">tup</span>: (<span class="type">i32</span>, <span class="type">f64</span>, <span class="type">u8</span>) = (<span class="number">500</span>, <span class="number">6.4</span>, <span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>把一些列值使用逗号分隔后放置到一对圆括号中</li><li>元组每个位置的值都有一个类型，这些类型不需要是相同的。</li></ul><p>上面这段带来执行 <code>cargo run</code> 会有编译警告：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3db475c146fa43aabef6a2be17dbe1b4~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>意思是，如果你是故意声明一个 <code>unused variable</code>，那就给变量名加一个下划线前缀来忽略警告</p><p><strong>取值：</strong></p><p>1、解构（destructuring）：使用模式匹配来解构元组：</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">tup</span> = (<span class="number">500</span>, <span class="number">6.4</span>, <span class="number">1</span>);</span><br><span class="line">    <span class="keyword">let</span> (_x, y, _z) = tup;</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;The value of y is: &#123;&#125;&quot;</span>, y);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>2、通过索引并使用点号(<code>.</code>)来访问元组中的值：</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">  <span class="keyword">let</span> <span class="variable">x</span>: (<span class="type">i32</span>, <span class="type">f64</span>, <span class="type">u8</span>) = (<span class="number">500</span>, <span class="number">6.4</span>, <span class="number">1</span>);</span><br><span class="line">  <span class="keyword">let</span> <span class="variable">five_hundred</span> = x.<span class="number">0</span>;</span><br><span class="line">  <span class="keyword">let</span> <span class="variable">six_point_four</span> = x.<span class="number">1</span>;</span><br><span class="line">  <span class="keyword">let</span> <span class="variable">one</span> = x.<span class="number">2</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="数组类型"><a href="#数组类型" class="headerlink" title="数组类型"></a>数组类型</h4><h5 id="数组（array）"><a href="#数组（array）" class="headerlink" title="数组（array）"></a>数组（array）</h5><p>通常当你不想在栈上而不是堆上为数据分配空间时，或者想要确保总有固定数量的元素时，数组是一个非常有用的工具。</p><ul><li>与元组不同的是数组中的每一个元素都必须是相同的类型。</li><li>Rust 中的数组拥有固定的长度，一旦声明就再也不能随意更改大小，这与其他语言不同。</li></ul><p>在 Rust 中，你可以将以逗号分隔的值放置在一对方括号内来创建一个数组：</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">  <span class="keyword">let</span> <span class="variable">a</span> = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>为了写出数组的类型，你需要使用一对方括号，并在方括号中填写数组内所有元素的类型、一个分号以及数组内元素的数量，如下所示：</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="variable">a</span>: [<span class="type">i32</span>, <span class="number">5</span>] = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>]</span><br></pre></td></tr></table></figure><p><strong>创建一个含有相同元素的数组：</strong></p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="variable">a</span> = [<span class="number">3</span>; <span class="number">5</span>]</span><br></pre></td></tr></table></figure><p>以 <code>a</code> 命名的数组将会拥有 5 个元素，而这些元素全部拥有相同的初始值 3。这一写法等价于 <code>let a = [3, 3, 3, 3, 3]</code>。</p><p><strong>访问数组的元素：</strong></p><p>数组由一整块分配在栈上的内存组成，你可以通过索引来访问一个数组中的所有元素：</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line"><span class="keyword">let</span> <span class="variable">a</span> = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>];</span><br><span class="line">  <span class="keyword">let</span> <span class="variable">first</span> = a[<span class="number">0</span>];</span><br><span class="line">  <span class="keyword">let</span> <span class="variable">second</span> = a[<span class="number">1</span>];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>非法的数组访问</strong></p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">  <span class="keyword">let</span> <span class="variable">a</span> = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>];</span><br><span class="line">  <span class="keyword">let</span> <span class="variable">index</span> = <span class="number">10</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> <span class="variable">element</span> = a[index];</span><br><span class="line">  <span class="built_in">println!</span>(<span class="string">&quot;The value of elements is: &#123;&#125;&quot;</span>, element)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 <a href="https://play.rust-lang.org/">Rust Playground</a> 运行这段代码，编译器提示我们，这个操作将会在运行时崩溃。原因是索引越界（Java 常见错误），数组长度是 5，但是我们给的索引是 10。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/af82e05277824ab285e1c35224c07996~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><blockquote><p>许多底层语言并没有类似的检查，一旦尝试使用非法索引，你就会访问到某块无效的内存空间（JavaScript 中则返回一个 <code>undefined</code>）</p></blockquote><h5 id="动态数组（vector）"><a href="#动态数组（vector）" class="headerlink" title="动态数组（vector）"></a>动态数组（vector）</h5><p>动态数组是一个类似于数组的集合结构，但它允许用户自由地调整数组长度。如果你不确定使用数组还是动态数组，那就先使用动态数组吧。动态数组属于高级语法，不在本文讨论范围，后期会出一期更深入的解析。</p><h2 id="函数（function）"><a href="#函数（function）" class="headerlink" title="函数（function）"></a>函数（function）</h2><ul><li><code>main</code> 函数是大部分程序开始的地方。</li><li>使用 <code>fn</code> 关键字来声明一个新的函数。</li><li>Rust 代码使用**蛇形命名法（snake case）**来作为规范函数和变量名称的风格。蛇形命名法只使用小写的字母进行命名，并以下划线分隔单词。</li></ul><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span> () &#123;</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;Hello world!&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="title function_ invoke__">another_function</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">another_function</span>() &#123;</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;Another function&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="函数参数"><a href="#函数参数" class="headerlink" title="函数参数"></a>函数参数</h3><p>函数参数是一种特殊的变量，并被视作函数签名的一部分。当函数存在参数时，你需要在调用函数时为这些变量提供具体的值。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">  <span class="title function_ invoke__">another_function</span>(<span class="number">5</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">another_function</span>(x: <span class="type">i32</span>) &#123;</span><br><span class="line">  <span class="built_in">println!</span>(<span class="string">&quot;The value of x is: &#123;&#125;&quot;</span>, x)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>和 Go 语言一样，在 Rust 函数签名中，你必须显式地声明每个参数的类型。</p></blockquote><p>和其他编程语言一样，Rust 中也是使用 <code>,</code> 来分隔多个参数：</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">  <span class="title function_ invoke__">another_function</span>(<span class="number">5</span>, <span class="number">6</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">another_function</span>(x: <span class="type">i32</span>, y: <span class="type">i32</span>) &#123;</span><br><span class="line">  <span class="built_in">println!</span>(<span class="string">&quot;The value of x is: &#123;&#125;&quot;</span>, x);</span><br><span class="line">  <span class="built_in">println!</span>(<span class="string">&quot;The value of y is: &#123;&#125;&quot;</span>, y);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="函数体中的语句和表达式"><a href="#函数体中的语句和表达式" class="headerlink" title="函数体中的语句和表达式"></a>函数体中的语句和表达式</h3><p>由于 Rust 是一门基于表达式的语言，所以它将语句（statement）与表达式（expression）区别为两个不同的概念。语句指那些执行操作但不返回值的指令，而表达式则是指会进行计算并产生一个值作为结果的指令。这个其他语言不太一样：</p><p>在 C 语言、Ruby、JavaScript 中 <code>var x = y = 6</code> 这种赋值语句会返回所赋的值，但是 Rust 中是行不通的。</p><p>值得注意的是，下面代码中的 <code>x+1</code> 是表达式。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">  <span class="keyword">let</span> <span class="variable">x</span> = <span class="number">5</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> <span class="variable">y</span> = &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">x</span> = <span class="number">3</span>;</span><br><span class="line"></span><br><span class="line">    x + <span class="number">1</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="built_in">println!</span>(<span class="string">&quot;The value of y is: &#123;&#125;&quot;</span>, y);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>但是如果我们在表达式末尾加上了分号，这一段代码就变成了语句而不会返回任何值。</p><h3 id="函数的返回值"><a href="#函数的返回值" class="headerlink" title="函数的返回值"></a>函数的返回值</h3><ul><li>在 Rust 中，函数的返回值等同于函数体最后一个表达式的值。</li><li>和其他编程语言不同的是，Rust 中函数的 <code>return</code> 语句不是必需的，只是一个用来提前返回的关键字。而且大多数函数都隐式地返回了最后的表达式。</li><li>需要在瘦箭头（<code>-&gt;</code>）的后面声明它的类型。</li></ul><p>函数体最后一行必须是表达式，不要加分号，下面是反例：</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">  <span class="keyword">let</span> <span class="variable">x</span> = <span class="title function_ invoke__">plus_one</span>(<span class="number">5</span>);</span><br><span class="line"></span><br><span class="line">  <span class="built_in">println!</span>(<span class="string">&quot;The value of x is: &#123;&#125;&quot;</span>, x);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">plus_one</span>(x: <span class="type">i32</span>) <span class="punctuation">-&gt;</span> <span class="type">i32</span> &#123;</span><br><span class="line">  x + <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>尝试编译这段代码会产生如下错误信息：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/68a309eddc6244e4a5b45bcd39553156~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>可以看出，由于 <code>x +1;</code> 是表达式没有返回值，函数体隐式地返回了空元祖（<code>()</code>），进而导致编译时 <code>mismatched types</code> 错误。编译器给的建议是删除 <code>x +1;</code> 语句的分号。</p><h2 id="控制流"><a href="#控制流" class="headerlink" title="控制流"></a>控制流</h2><h3 id="if-表达式"><a href="#if-表达式" class="headerlink" title="if 表达式"></a>if 表达式</h3><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">number</span> = <span class="number">3</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> number &lt; <span class="number">5</span> &#123;</span><br><span class="line">        <span class="built_in">println!</span>(<span class="string">&quot;condition was true&quot;</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="built_in">println!</span>(<span class="string">&quot;condition was false&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>和 Go 一样，判断条件不需要圆括号包裹。</li><li>代码中的条件表达式必须产生一个 <code>bool</code> 类型的值，否则就会触发编译错误。</li><li>与 Ruby 或 JavaScript 等语言不同，Rust 没有隐式转换</li></ul><h4 id="在-let-语句中使用-if"><a href="#在-let-语句中使用-if" class="headerlink" title="在 let 语句中使用 if"></a>在 <code>let</code> 语句中使用 <code>if</code></h4><p>由于<code> if</code> 是一个表达式，所以我们可以再 <code>let</code> 语句的右侧使用它来生成一个值。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">  <span class="keyword">let</span> <span class="variable">condition</span> = <span class="literal">true</span>;</span><br><span class="line">  <span class="keyword">let</span> <span class="variable">number</span> = <span class="keyword">if</span> condition &#123;</span><br><span class="line">    <span class="number">5</span></span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="number">6</span></span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="built_in">println!</span>(<span class="string">&quot;The value of number is: &#123;&#125;&quot;</span>, number);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>代码块输出的值就是其中最后一个表达式的值，另外，数字本身也可以作为一个表达式使用。</li><li>整个 <code>if</code> 表达式的值取决于究竟是哪一个代码块得到了执行。这意味着，所有 <code>if</code> 分支可能返回的值都必须是一种类型的。</li></ul><h2 id="使用循环重复执行代码"><a href="#使用循环重复执行代码" class="headerlink" title="使用循环重复执行代码"></a>使用循环重复执行代码</h2><p>Rust 提供了 3 种循环：<code>loop</code>、<code>while</code>和<code>for</code></p><h3 id="loop-循环"><a href="#loop-循环" class="headerlink" title="loop 循环"></a>loop 循环</h3><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">  <span class="keyword">loop</span> &#123;</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;again!&quot;</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>运行这段程序时，除非我们手动强制退出程序，否则 <code>again!</code> 会被反复地输出到屏幕上。</p><p><strong>从 loop 循环中返回值</strong></p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">  <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">counter</span> = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> <span class="variable">result</span> = <span class="keyword">loop</span> &#123;</span><br><span class="line">    counter += <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> counter == <span class="number">10</span> &#123;</span><br><span class="line">      <span class="keyword">break</span> counter * <span class="number">2</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="built_in">println!</span>(<span class="string">&quot;The result is &#123;&#125;&quot;</span>, result);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面的代码中我们将需要返回的值添加到 break 表达式后面，也就是我们用来终止循环的表达式后面。</p><h3 id="while-条件循环"><a href="#while-条件循环" class="headerlink" title="while 条件循环"></a>while 条件循环</h3><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">  <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">number</span> = <span class="number">3</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">while</span> number !=<span class="number">0</span> &#123;</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;&#123;&#125;!&quot;</span>, number);</span><br><span class="line"></span><br><span class="line">    number = number - <span class="number">1</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="built_in">println!</span>(<span class="string">&quot;LOFTOFF!!!&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>while</code> 循环的模式是会在每次执行循环体之前都判断一次条件，假如条件为真则执行代码片段，加入条件为假或在执行过程中碰到 <code>break</code> 就退出当前循环。这种模式可以通过 <code>loop</code>、<code>if</code>、<code>else</code> 及 <code>break</code> 关键字的组合使用来实现。</p><h3 id="使用-for-循环遍历集合"><a href="#使用-for-循环遍历集合" class="headerlink" title="使用 for 循环遍历集合"></a>使用 for 循环遍历集合</h3><p>我们可以使用 <code>for</code> 循环来遍历集合中的每一个元素。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">  <span class="keyword">let</span> <span class="variable">a</span> = [<span class="number">10</span>, <span class="number">20</span>, <span class="number">30</span>, <span class="number">40</span>, <span class="number">50</span>];</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> <span class="variable">element</span> <span class="keyword">in</span> a.<span class="title function_ invoke__">iter</span>() &#123;</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;The value is: &#123;&#125;&quot;</span>, element)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>for</code> 循环的安全性和简捷性使它成为了 Rust 中最为常用的循环结构。大部分的 Rust 开发者也会选择使用 for 循环。</p><p>下面的例子中，我们配合标准库中的 Range 来实现打印 <code>1</code> 到<code>4</code>:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">  <span class="keyword">for</span> <span class="variable">number</span> <span class="keyword">in</span> (<span class="number">1</span>..<span class="number">4</span>).<span class="title function_ invoke__">rev</span>() &#123;</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;&#123;&#125;!&quot;</span>, number);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="built_in">println!</span>(<span class="string">&quot;LIFTOFF!!!&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/38a92886ba96/</id>
    <link href="https://blog.zisheng.pro/posts/38a92886ba96/"/>
    <published>2023-04-19T10:19:08.000Z</published>
    <summary>语言之间都有着类似的核心特性，比如变量、基本类型、函数、注释和控制流程等概念。但是每个语言有都有自己的独有概念，本文便是记录了我在学习 Rust 语法过程中遇到的 Rust 独有概念。</summary>
    <title>Rust 语法学习笔记</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="Rust" scheme="https://blog.zisheng.pro/categories/Rust/"/>
    <category term="紫升的博客" scheme="https://blog.zisheng.pro/tags/%E7%B4%AB%E5%8D%87%E7%9A%84%E5%8D%9A%E5%AE%A2/"/>
    <category term="紫升" scheme="https://blog.zisheng.pro/tags/%E7%B4%AB%E5%8D%87/"/>
    <category term="Rust" scheme="https://blog.zisheng.pro/tags/Rust/"/>
    <content>
      <![CDATA[<div class="note primary modern"><p>猜数游戏代码在 <a href="https://github.com/youngjuning/learn-rust/tree/master/guessing_game">guessing_game</a></p></div><p>为了方便大家学习，需要运行（<code>cargo run</code>）看效果的地方，我都截了GIF图。喜欢的话可以给紫升个👍🏻鼓励一下。</p><h2 id="环境"><a href="#环境" class="headerlink" title="环境"></a>环境</h2><blockquote><ul><li><p>rustc：<code>1.50.0 (cb75ad5db 2021-02-10)</code></p></li><li><p>cargo：<code>1.50.0 (f04e7fab7 2021-02-04)</code></p></li><li><p><code>rand</code> crate：<code>0.8.3</code></p></li><li><p>vscode plugins</p><ul><li>Better TOML</li><li>CodeLLDB</li><li>crates</li><li>rust-analyzer</li></ul></li><li><p>oh-my-zsh theme：ys</p></li><li><p>截图软件：<a href="https://getkap.co/">Kap</a></p></li></ul></blockquote><h2 id="处理用户输入"><a href="#处理用户输入" class="headerlink" title="处理用户输入"></a>处理用户输入</h2><p>猜数游戏第一部分会请求用户进行输入，上代码：</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> std::io;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;Guess the number!&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;Please input your guess.&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">guess</span> = <span class="type">String</span>::<span class="title function_ invoke__">new</span>();</span><br><span class="line"></span><br><span class="line">    io::<span class="title function_ invoke__">stdin</span>()</span><br><span class="line">        .<span class="title function_ invoke__">read_line</span>(&amp;<span class="keyword">mut</span> guess)</span><br><span class="line">        .<span class="title function_ invoke__">expect</span>(<span class="string">&quot;Failded to read line&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;You guessed: &#123;&#125;&quot;</span>, guess);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="声明导入"><a href="#声明导入" class="headerlink" title="声明导入"></a>声明导入</h3><ul><li><code>use std::io;</code> 语句：把标准库（<a href="https://github.com/rust-lang/rust/tree/master/library/std/src/io">std</a>）中的 <code>io</code> 模块引入当前的作用域中。</li><li>Rust 默认会将预导入（prelude）模块内的条目自动引入每一段程序的作用域中，它包含了一小部分相当常用的类型。如果你需要的模块不在预导入模块内，那么我们就必须使用 <code>use</code> 语句来显式地进行导入声明。</li><li><code>std::id</code> 库包含了许多有用的功能，我们可以使用它来获得用户的输入数据。</li></ul><h3 id="使用变量来存储值"><a href="#使用变量来存储值" class="headerlink" title="使用变量来存储值"></a>使用变量来存储值</h3><ul><li><code>let mut guess</code>：创建出一个名为 <code>guess</code> 的可变变量，变量默认是不可变的，<code>mut</code> 关键字表示可变（mutable）变量，关于变量与可变性的概念在<a href="https://juejin.cn/post/6929080722565267469">两个番茄钟的Rust 语法学习笔记</a>中有介绍。</li><li><code>String::new</code>：返回一个新的 <code>String</code> 实例<ul><li><code>::</code> 语法表明 <code>new</code> 是 <code>String</code> 类型的一个关联函数（associated function）——其他语言中被称为静态方法（static method）</li><li><code>String</code>是标准库中的一个字符串类型，它在内部使用了 UTF-8 格式的编码并可以按照需求扩展自己的大小。</li></ul></li><li><code>io::stdin</code>：返回<code>std::io::Stdin</code> 的实例，它被用作句柄来处理终端中的标准输入。</li><li><code>.read_line</code> 方法用来获得用户输入。<ul><li><code>read_line</code> 的参数 <code>&amp;mut guess</code> 表示引用一个可变变量。</li><li><code>&amp;</code> 表示当前的参数是一个引用，这在 Go 中也是存在的。因为这些系统级编程语言大多都提供了操作内存的权限，而 JavaScript 是不给用户这些功能的。</li></ul></li></ul><blockquote><p>句柄：在上世纪80年代的操作系统（如 Mac OS 句柄 和Windows）的内存管理中，句柄被广泛应用。Unix系统的文件描述符基本上也属于句柄。和其它桌面环境一样，Windows API大量使用句柄来标识系统中的对象)，并创建操作系统与用户空间之间的通信渠道。例如，桌面上的一个窗体由一个<code>HWND</code>类型的句柄来标识。</p></blockquote><h3 id="处理可能失败的情况"><a href="#处理可能失败的情况" class="headerlink" title="处理可能失败的情况"></a>处理可能失败的情况</h3><p>我们先来看下 Go 里边的错误处理方式：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">  result,err := getResult();</span><br><span class="line">  <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">    <span class="built_in">panic</span>(err)</span><br><span class="line">  &#125;</span><br><span class="line">  fmt.Printf(<span class="string">&quot;The result is %s&quot;</span>, result);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">getResult</span><span class="params">()</span></span> <span class="type">string</span>,(<span class="type">string</span>, err <span class="type">error</span>) &#123;</span><br><span class="line">  <span class="comment">// some codes</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>一般，一个函数可能报错的话都会在返回 result 的同时返回一个 <code>error</code> 。这并不是 Go 专属的，Rust 语言中也是如此，只不过实现方式不一样。</p><p>前面代码中，<code>read_line</code> 会将用户输入的内容存储到我们传入的字符串中，但与此同时，它还会返回一个 <code>io::Result</code> 值。在 Rust 标准库中，你可以找到许多以 <code>Result</code> 命名的类型，它们通常是各个子模块中 <code>Result</code> 泛型的特定版本，比如这里的<code>io::Result</code>。</p><blockquote><p><code>Result</code> 是一个枚举类型。枚举类型由一系列固定的值组合而成，这些值被称作枚举的变体。（众所周知，Javascript 没有枚举类型）</p></blockquote><p>对于 <code>Result</code> 而言，它拥有 <code>Ok</code> 和 <code>Err</code> 两个变体。其中的 <code>Ok</code> 变体表明当前的操作执行成功，并附带代码产生的结果值。相应地，<code>Err</code> 变体则表明当前的操作执行失败，并附带引发失败的具体原因。</p><p><code>expect</code> 是 <code>Result</code> 类型的值的一系列方法中的一个。假如 <code>io::Result</code> 实例的值是 <code>Err</code>，那么 <code>expect</code> 方法就会中断当前的程序，并将传入的字符串参数显示出来。</p><blockquote><p><code>read_line</code> 方法有可能因为底层操作系统的错误而返回一个 <code>Err</code> 结果。</p><p>相应地，假如 <code>io::Result</code> 实例的值是 <code>Ok</code>，那么 <code>expect</code> 就会提取出 <code>Ok</code> 中附带的值，并将它作为结果返回给用户。在我们的例子中，这个值就是用户输入内容的字节数。</p></blockquote><p>即便我们没有在语句末尾调用 <code>expect</code>，这段程序也能够编译通过，但你会在编译过程中看到如下所示的警告信息。这一点和 Go 也基本一致。</p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/41b7da00e7764953a833d3aeb65b2142~tplv-k3u1fbpfcp-zoom-1.image" style="zoom:50%;" /><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fd34722caa964f5f801a77768de68fea~tplv-k3u1fbpfcp-zoom-1.image" style="zoom:50%;" /><p>Rust 编译器提醒我们 <code>read_line</code> 方法返回的 <code>Result</code> 值还没有被处理，这通常以为着我们的程序没有对潜在的错误进行处理。</p><p>消除警告最正确的方法当然是编写对应的错误处理代码，为了简单起见，我们在这里选择使用 <code>expect</code> 方法，它会让程序在出现错误时直接终止运行并退出。</p><h3 id="通过-println-中的占位符输出对应的值"><a href="#通过-println-中的占位符输出对应的值" class="headerlink" title="通过 println! 中的占位符输出对应的值"></a>通过 <code>println!</code> 中的占位符输出对应的值</h3><p><code>println!(&quot;You guessed: &#123;&#125;&quot;, guess);</code> 可以将我们存储的用户输入打印出来。这段宏调用的第一个参数是用于格式化的字符串，而字符串中的那些花括号<code>&#123;&#125;</code>则是一个占位符。</p><blockquote><p>举一反三：<code>&#123;&#125;</code> 作为占位符不是 Rust 特有的，我所知的 Shell 中也有类似的应用，比如 <code>ls | xargs -I &#123;&#125; tar zcvfm &#123;&#125;.tar.bz &#123;&#125;</code> 这段脚本的意思就是将 <code>ls</code> 输出的值逐个通过管道传入后面的命令，<code>&#123;&#125;</code> 便是接收管道传的值的占位符。顺便一提，这段脚本我在 <a href="https://github.com/youngjuning/tuya-panel-demo/blob/3b66bbaa45560fa247a76adc2ab06f5ca477fcc5/.github/workflows/release.yml#L20">tuya-panel-demo</a> 有使用。这里挖个坑，后续会出一个 Shell 脚本的系列教程。</p></blockquote><h3 id="尝试运行代码"><a href="#尝试运行代码" class="headerlink" title="尝试运行代码"></a>尝试运行代码</h3><p>现在，让我们借助 <code>cargo run</code> 命令来尝试运行一下这段代码：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/74adfa100e0147b083ae72c782928916~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><h2 id="生成一个保密数字"><a href="#生成一个保密数字" class="headerlink" title="生成一个保密数字"></a>生成一个保密数字</h2><p>解决了接收用户输入的数字之后，我们需要生成一个保密数字来供玩家进行猜测。为了保证一定的可玩性，并使每局游戏都有不同的体验，这个生成的保密数字将会是随机的。</p><h3 id="引入-rand-包"><a href="#引入-rand-包" class="headerlink" title="引入 rand 包"></a>引入 rand 包</h3><p>Rust 团队并没有把类似的随机数字生成功能内置到标准库中，而是选择将它作为 rand 包（rand crate）提供给用户。</p><blockquote><p>注意：Rust 中包（crate）代表了一系列源代码文件的集合。我们当前构建的项目是一个用于生成可执行程序的二进制包（binary crate），而我们引入<code>rand</code>包则是一个用于复用功能的库包（libray crate，代码包）。</p></blockquote><p>要使用第三方 <code>crate</code> ，我们需要在 <code>Cargo.toml</code> 文件中加入依赖：</p><figure class="highlight toml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line"><span class="section">[dependencies]</span></span><br><span class="line"></span><br><span class="line"><span class="attr">rand</span> = <span class="string">&quot;0.3.14&quot;</span></span><br></pre></td></tr></table></figure><p>在 <code>Cargo.toml</code> 文件中，从一个标题到下一个标题之间的所有内容都属于同一区域。这里的<code>[denpendencies]</code>区域被用来声明项目中需要用到的全部依赖包及其版本号。</p><blockquote><p>书中的版本号比较老，vscode crates 插件提示了版本❌</p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d11abe6e85e6441ca5469fe4cf70dcc3~tplv-k3u1fbpfcp-zoom-1.image" style="zoom:50%;" /><p>我们点击任意版本即可选择该版本作为我们依赖的版本，点击圆括号内的 docs 便可跳转到库文档。</p></blockquote><p>我们先不修改任何代码，直接重新构建这个项目：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5fe9b017f6e8431bb31df50335e7cd67~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>现在，我们的程序有了一个外部依赖，Cargo 可以从注册表（registry）中获取所有可用库的最新版本信息，而这些信息通常是从<a href="https://cartes.io/">cartes.io</a> 上拷贝过来的。</p><blockquote><p>cates.io 在 Rust 生态中是人们用于分享各种各样开源 Rust 项目的网站。</p></blockquote><p>现在，如果你没有做出任何改变，立即重新运行 <code>cargo build</code>，那么只会有 Finished 提示。Cargo 会自动分析当前已经下载或编译过的内容，并跳过无需重复的步骤。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/910a4092bb524a788621f193cde3618f~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><h3 id="生成一个随机数"><a href="#生成一个随机数" class="headerlink" title="生成一个随机数"></a>生成一个随机数</h3><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="addition">+ use rand::Rng;</span></span><br><span class="line">use std::io;</span><br><span class="line"></span><br><span class="line">fn main() &#123;</span><br><span class="line">    println!(&quot;Guess the number!&quot;);</span><br><span class="line"></span><br><span class="line"><span class="addition">+   let secret_number = rand::thread_rng().gen_range(1..101);</span></span><br><span class="line"></span><br><span class="line"><span class="addition">+   println!(&quot;The secret number is: &#123;&#125;&quot;, secret_number);</span></span><br><span class="line"></span><br><span class="line">    println!(&quot;Please input your guess.&quot;);</span><br><span class="line"></span><br><span class="line">    let mut guess = String::new();</span><br><span class="line"></span><br><span class="line">    io::stdin()</span><br><span class="line">        .read_line(&amp;mut guess)</span><br><span class="line">        .expect(&quot;Failded to read line&quot;);</span><br><span class="line"></span><br><span class="line">    println!(&quot;You guessed: &#123;&#125;&quot;, guess);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>我们额外增加了一行 <code>use</code> 语句：<code>use rand::Rng</code>。这里的 Rng 是一个 trait（特征），它定义了随机数生成器需要实现的方法集合。为了使用这些方法，我们需要显式地将它引入当前的作用域中。</li><li><code>rand::thread_rng()</code> 会返回一个特定的随机数生成器。随后我们调用这个生成器的<code>gen_range</code>方法。</li><li><code>gen_range</code> 方法是在刚刚引入作用域的 <code>Rng</code> trait 中定义的，它接收一个 Range 类型（<code>1..100</code>）的值作为参数，并生成一个范围在两者之前的随机数。</li></ul><blockquote><p>提醒：书中的 <code>gen_range</code> 接收的是两个参数，读到时不要奇怪，版本不一样而已。</p></blockquote><blockquote><p> 值得指出的是，<code>gen_range</code>生成的随机数空间包含下限但不包含上限。——做人呢，也是这样，要有底线，但不要给自己设上限。</p></blockquote><p>使用第三方包，不可避免要频繁查看文档。你可以去官方文档看，这没什么问题。但是你也可以借助<code>cargo doc --open</code> 命令在本地构建一份有关所有依赖的文档，并自动地在浏览器中将文档打来来供你查阅：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5bc83c1f8feb432884ceaef1794af0a0~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>上面代码中，我们将保密数字打印出来，只是为了调试，之后会删掉这段代码。</p><h2 id="比较猜测数字与保密数字"><a href="#比较猜测数字与保密数字" class="headerlink" title="比较猜测数字与保密数字"></a>比较猜测数字与保密数字</h2><p>现在，我们有了一个随机生成的保密数字，还有一个用户输入的猜测数字。接下来我们将比较这两个数字。</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">use rand::Rng;</span><br><span class="line"><span class="addition">+ use std::cmp::Ordering;</span></span><br><span class="line">use std::io;</span><br><span class="line"></span><br><span class="line">fn main() &#123;</span><br><span class="line">    println!(&quot;Guess the number!&quot;);</span><br><span class="line"></span><br><span class="line">    let secret_number = rand::thread_rng().gen_range(1..101);</span><br><span class="line"></span><br><span class="line">    println!(&quot;Please input your guess.&quot;);</span><br><span class="line"></span><br><span class="line">    let mut guess = String::new();</span><br><span class="line"></span><br><span class="line">    io::stdin()</span><br><span class="line">        .read_line(&amp;mut guess)</span><br><span class="line">        .expect(&quot;Failded to read line&quot;);</span><br><span class="line"></span><br><span class="line">    println!(&quot;You guessed: &#123;&#125;&quot;, guess);</span><br><span class="line"></span><br><span class="line"><span class="addition">+   match guess.cmp(&amp;secret_number) &#123;</span></span><br><span class="line"><span class="addition">+       Ordering::Less =&gt; println!(&quot;Too small!&quot;),</span></span><br><span class="line"><span class="addition">+       Ordering::Greater =&gt; println!(&quot;Too big!&quot;),</span></span><br><span class="line"><span class="addition">+       Ordering::Equal =&gt; println!(&quot;You win!&quot;),</span></span><br><span class="line"><span class="addition">+   &#125;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><ul><li>我们从标准库中引入了 <code>std::cmp::Ordering</code> 类型。与 Result 相同，Ordering 也是一个枚举类型，它拥有<code>Less</code>、<code>Greater</code>、<code>Equal</code> 这3个变体。（cmp 是 compare 的缩写）</li><li><code>match</code> 表达式由由数个分支（arm）组成，每个分支都包含一个用于匹配的模式（pattern），以及匹配成功后要执行的相应代码。</li></ul><blockquote><p>Rust 中的<code>match</code>结构及模式是一类非常强大的工具，它们提供了依据不同条件执行不同代码的能力（类似其他语言的<code>switch</code>），并能够确保你不会遗漏任何分支条件。</p></blockquote><p>上面这段代码，目前是无法通过编译的：</p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bee46fa3af2c4fd08bbef9df941af3bc~tplv-k3u1fbpfcp-zoom-1.image" style="zoom:50%;" /><p>错误原因是因为我们保存的 <code>guess</code> 变量类型是 String，保密数字是 integer 类型，不同的类型无法匹配（静态语言不存在隐式类型转换）。</p><p>为了正常进行比较操作，我们需要将程序中读取的输入从 String 类型转换为数值类型：</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">use rand::Rng;</span><br><span class="line">use std::cmp::Ordering;</span><br><span class="line">use std::io;</span><br><span class="line"></span><br><span class="line">fn main() &#123;</span><br><span class="line">    println!(&quot;Guess the number!&quot;);</span><br><span class="line"></span><br><span class="line">    let secret_number = rand::thread_rng().gen_range(1..101);</span><br><span class="line"></span><br><span class="line">    println!(&quot;Please input your guess.&quot;);</span><br><span class="line"></span><br><span class="line">    let mut guess = String::new();</span><br><span class="line"></span><br><span class="line">    io::stdin()</span><br><span class="line">        .read_line(&amp;mut guess)</span><br><span class="line">        .expect(&quot;Failded to read line&quot;);</span><br><span class="line"></span><br><span class="line"><span class="addition">+   let guess: i32 = guess.trim().parse().expect(&quot;Please type a number&quot;);</span></span><br><span class="line"></span><br><span class="line">    println!(&quot;You guessed: &#123;&#125;&quot;, guess);</span><br><span class="line"></span><br><span class="line">    match guess.cmp(&amp;secret_number) &#123;</span><br><span class="line">        Ordering::Less =&gt; println!(&quot;Too small!&quot;),</span><br><span class="line">        Ordering::Greater =&gt; println!(&quot;Too big!&quot;),</span><br><span class="line">        Ordering::Equal =&gt; println!(&quot;You win!&quot;),</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p>我们这里创建一个新的不可变变量<code>guess</code>，虽然和前面的同名，但这是有效的，Rust 允许使用同名的新变量 guess 来隐藏（shadow）旧变量的值。这一特性通常被用在需要转换值类型的场景中，它在本例中允许我们重用 guess 这个变量名，而无须创造出 guess_str 之类不同的名字。</p></li><li><p><code>guess.trim()</code> 是为了删除首尾所有空白字符。</p></li><li><p>字符串的 <code>parse</code> 方法会尝试将当前的字符串解析为某种数值。由于这个方法可以处理不同的数值类型，所以需要我们通过语句<code>let guess: i32</code> 来显式地声明我们需要的数值类型。</p></li></ul><p>现在我们重新 Run 一下我们的程序：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7a2e0066cbbb4948aacf17ad5ea8b24b~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>这个游戏已经大体成型了，但玩家只能做出一次猜测，这显然是不够的，接下来，我们会加入一个循环来完善这个游戏。</p><h2 id="使用循环来实现多次猜测"><a href="#使用循环来实现多次猜测" class="headerlink" title="使用循环来实现多次猜测"></a>使用循环来实现多次猜测</h2><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">use rand::Rng;</span><br><span class="line">use std::cmp::Ordering;</span><br><span class="line">use std::io;</span><br><span class="line"></span><br><span class="line">fn main() &#123;</span><br><span class="line">    println!(&quot;Guess the number!&quot;);</span><br><span class="line"></span><br><span class="line">    // `..` 语法是标准库中提供的 Range</span><br><span class="line">    let secret_number = rand::thread_rng().gen_range(1..101);</span><br><span class="line"><span class="addition">+   loop &#123;</span></span><br><span class="line">        println!(&quot;Please input your guess.&quot;);</span><br><span class="line"></span><br><span class="line">        let mut guess = String::new();</span><br><span class="line"></span><br><span class="line">        io::stdin()</span><br><span class="line">            .read_line(&amp;mut guess)</span><br><span class="line">            .expect(&quot;Failded to read line&quot;);</span><br><span class="line"></span><br><span class="line">        let guess: i32 = guess.trim().parse().expect(&quot;Please type a number&quot;);</span><br><span class="line"></span><br><span class="line">        println!(&quot;You guessed: &#123;&#125;&quot;, guess);</span><br><span class="line"></span><br><span class="line">        match guess.cmp(&amp;secret_number) &#123;</span><br><span class="line">            Ordering::Less =&gt; println!(&quot;Too small!&quot;),</span><br><span class="line">            Ordering::Greater =&gt; println!(&quot;Too big!&quot;),</span><br><span class="line"><span class="addition">+           Ordering::Equal =&gt; &#123;</span></span><br><span class="line"><span class="addition">+               println!(&quot;You win!&quot;);</span></span><br><span class="line"><span class="addition">+               break;</span></span><br><span class="line"><span class="addition">+           &#125;</span></span><br><span class="line">        &#125;</span><br><span class="line"><span class="addition">+   &#125;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>我们将提示用户做出猜测决定之后的所有内容都移动到了 <code>loop</code> 中。在 Rust 中，<code>loop</code> 关键字会创建一个无限循环。</li><li>我们还给程序增加了一条<code>break</code>语句，使得玩家在猜对数字后能够正常退出游戏。</li></ul><p>运行程序，玩家猜对数字，输出**You Win!**后，会退出程序：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d6b7b5953aec4e36a9fe7b44805100c0~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><h2 id="处理非法输入"><a href="#处理非法输入" class="headerlink" title="处理非法输入"></a>处理非法输入</h2><p>在转换 <code>guess</code> 字符串为 <code>number</code> 类型是，我们使用 <code>expect(&quot;Please type a number&quot;)</code>来处理可能出现的错误，我们尝试输入一个非 <code>number</code> 类型的值运行一下：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b33a4d37eeb648f9837aef91fea55b05~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>用户如果想要重新开始游戏，需要重启程序，这很明显，用户体验挺差的。为了改善游戏的可玩性，我们可以在用户输入了一个非数字数据时简单地忽略这次猜测行为，并使用户可以继续进行猜测，从而避免程序发生崩溃。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> rand::Rng;</span><br><span class="line"><span class="keyword">use</span> std::cmp::Ordering;</span><br><span class="line"><span class="keyword">use</span> std::io;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;Guess the number!&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// `..` 语法是标准库中提供的 Range</span></span><br><span class="line">    <span class="keyword">let</span> <span class="variable">secret_number</span> = rand::<span class="title function_ invoke__">thread_rng</span>().<span class="title function_ invoke__">gen_range</span>(<span class="number">1</span>..<span class="number">101</span>);</span><br><span class="line">    <span class="keyword">loop</span> &#123;</span><br><span class="line">        <span class="built_in">println!</span>(<span class="string">&quot;Please input your guess.&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">guess</span> = <span class="type">String</span>::<span class="title function_ invoke__">new</span>();</span><br><span class="line"></span><br><span class="line">        io::<span class="title function_ invoke__">stdin</span>()</span><br><span class="line">            .<span class="title function_ invoke__">read_line</span>(&amp;<span class="keyword">mut</span> guess)</span><br><span class="line">            .<span class="title function_ invoke__">expect</span>(<span class="string">&quot;Failded to read line&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">let</span> <span class="variable">guess</span>: <span class="type">i32</span> = <span class="keyword">match</span> guess.<span class="title function_ invoke__">trim</span>().<span class="title function_ invoke__">parse</span>() &#123;</span><br><span class="line">            <span class="title function_ invoke__">Ok</span>(num) =&gt; num,</span><br><span class="line">            <span class="title function_ invoke__">Err</span>(_) =&gt; &#123;</span><br><span class="line">              <span class="built_in">println!</span>(<span class="string">&quot;Please type a number!&quot;</span>);</span><br><span class="line">              <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line"></span><br><span class="line">        <span class="built_in">println!</span>(<span class="string">&quot;You guessed: &#123;&#125;&quot;</span>, guess);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">match</span> guess.<span class="title function_ invoke__">cmp</span>(&amp;secret_number) &#123;</span><br><span class="line">            Ordering::Less =&gt; <span class="built_in">println!</span>(<span class="string">&quot;Too small!&quot;</span>),</span><br><span class="line">            Ordering::Greater =&gt; <span class="built_in">println!</span>(<span class="string">&quot;Too big!&quot;</span>),</span><br><span class="line">            Ordering::Equal =&gt; &#123;</span><br><span class="line">                <span class="built_in">println!</span>(<span class="string">&quot;You win!&quot;</span>);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>我们使用了 <code>match</code> 表达式来替换之前的 <code>expect</code> 方法，这是我们处理错误行为的一种管用手段。</li><li><code>parse</code>会返回一个 Result 类型，而<code>Result</code>类型则包含了<code>Ok</code>与<code>Err</code>两个变体。</li></ul><p>万事具备，让我们运行这个项目试试看：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dd1f88a003e34ac0b23e1cd4e923cbbc~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/32241477f8c0/</id>
    <link href="https://blog.zisheng.pro/posts/32241477f8c0/"/>
    <published>2023-04-19T10:10:36.000Z</published>
    <summary>本文记录了使用 Rust 实现猜数游戏</summary>
    <title>Rust 编写猜数游戏</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="Rust" scheme="https://blog.zisheng.pro/categories/Rust/"/>
    <category term="Rust 变量隐藏" scheme="https://blog.zisheng.pro/tags/Rust-%E5%8F%98%E9%87%8F%E9%9A%90%E8%97%8F/"/>
    <content>
      <![CDATA[<h2 id="什么是变量隐藏"><a href="#什么是变量隐藏" class="headerlink" title="什么是变量隐藏"></a>什么是变量隐藏</h2><p>在 Rust 的世界中，变量可以被一个同名的变量覆盖掉的现象就叫做隐藏。这意味着我们随后使用这个名称时，他指向的将会是第二个变量。我们可以重复使用<code>let</code>关键字并配以相同的名称来不断地隐藏变量：</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">  <span class="keyword">let</span> <span class="variable">x</span> = <span class="number">5</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> <span class="variable">x</span> = x + <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> <span class="variable">x</span> = x + <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line">  <span class="built_in">println!</span>(<span class="string">&quot;The value of x is: &#123;&#125;&quot;</span>, x);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>按照世界上没有完全一样的两个东西的原则（上一秒的你也不等于下一秒的你），这前后两个变量并不会完全一样。我并没有看过 Rust 源码，个人理解是这两个变量指向的内存空间是不一样的。所谓隐藏不过是编译器在编译的时候去做了区分。子非编译器，安知compiler之乐乎？</p><p>隐藏机制与<code>mut</code>（mutable）的不同：</p><ol><li>如果不是在使用 <code>let</code> 关键字的情况下重新为这个变量赋值，则会导致编译错误。通过使用<code>let</code>，我们可以对这个值执行一些列的变化操作，并允许这个变量在操作完成后保持自己的不可变性。</li><li>由于重复使用 <code>let</code> 关键字会创建出新的变量，所以我们可以在复用变量名称的同时改变它的类型。</li></ol><blockquote><p>隐藏特性通常被用在需要转换值类型的场景中，如果只是改变值本身 <code>mut</code> 关键字就够用。</p></blockquote><h2 id="为什么-Rust-需要变量隐藏？"><a href="#为什么-Rust-需要变量隐藏？" class="headerlink" title="为什么 Rust 需要变量隐藏？"></a>为什么 Rust 需要变量隐藏？</h2><blockquote><p>这是 reddit rust 话题下的一个问题，我这里翻译一下。</p></blockquote><h3 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h3><p>我最近发现 Rust 不像其他类C语言，它允许在一个块作用域中定义多个拥有相同名称的变量：</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="variable">name</span> = <span class="number">10</span>i;</span><br><span class="line"><span class="keyword">let</span> <span class="variable">name</span> = <span class="number">3.14f64</span>;</span><br><span class="line"><span class="keyword">let</span> <span class="variable">name</span> = <span class="string">&quot;string&quot;</span>;</span><br><span class="line"><span class="keyword">let</span> <span class="variable">name</span> = name; <span class="comment">// &quot;string&quot; again, this definition shadows all the others</span></span><br></pre></td></tr></table></figure><p>至少从我的 C++ 背景来说，第一次看到这个特性着实有点吓人。</p><p>这个特性来自哪里？它提供了什么好处？</p><h3 id="回答"><a href="#回答" class="headerlink" title="回答"></a>回答</h3><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6956030e8f734c3a81e5598aa9123dd7~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>变量隐藏与仿射类型（affine types）具有很多协同作用。例如：</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="variable">foo</span> = foo.<span class="title function_ invoke__">unwrap</span>();</span><br></pre></td></tr></table></figure><p>你在其中重新绑定 <code>foo</code> 的同时引用 <code>unwrap()</code> 的结果，因此旧 <code>foo</code> 由于该原因而变得不可访问。——变量指向了新的内存空间。</p><hr><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e9b00e7afe1946ed84b10d7085f0a320~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>好处是使用一个单词来表示共同的变量，例如 <code>c</code> 用于绘制上下文。变量可以被改变类型，<a href="https://github.com/PistonDevelopers/graphics">Rust-Graphics</a> 大量地依赖该特性。每一个方法返回一个新的类型，并且当上下文提供足够的信息去绘制时，你只能调用<code>.draw(gl)</code>。</p><hr><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/699da31543d44ae99d95e0d371e9fce8~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>我经常使用的单个字母是：</p><ul><li><code>a</code>、<code>b</code> 用作二进制操作的参数</li><li><code>c</code>、<code>d</code>、<code>g</code> 用作图形绘制</li><li><code>i</code>、<code>j</code>、<code>k</code> 用作索引</li><li><code>n</code>、<code>m</code> 用作长度</li><li><code>x</code>、<code>y</code>、<code>z</code>、<code>t</code> 作为空间坐标系</li></ul><p>有时我也使用<code>p</code>、<code>q</code>或者<code>v</code>、<code>u</code> 作复杂的数学运算。</p><hr><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/666f1cfe0c0e4a7f97779b7cc95dedf7~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>我认为变量隐藏的最佳论据是阻止对象可变性的能力。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">vec</span> = <span class="type">Vec</span>::<span class="title function_ invoke__">new</span>();</span><br><span class="line">vec.<span class="title function_ invoke__">push</span>(<span class="number">1</span>i);</span><br><span class="line">vec.<span class="title function_ invoke__">push</span>(<span class="number">2</span>i);</span><br><span class="line"><span class="keyword">let</span> <span class="variable">vec</span> = vec;</span><br></pre></td></tr></table></figure><p>但是，对于中间变量，我也很喜欢。有时，通过<code>let</code>绑定将长方法链分成两行只是更漂亮。您只需两次使用变量名来代替使用 <code>let temp</code>,这样，临时变量不会使函数名称空间的其余部分杂乱无章。</p><hr><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f2a0e9fa316c4ad8a95d655d76b9295b~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>我不确定，但我认为此功能可将可变性降至最低。您可以使用新值重新声明它，而不用突变一个变量。</p><p>无论如何，它并没有看起来那么糟糕。类型系统禁止使用<code>int</code>代替浮点数或字符串。</p><p>确实，此功能可能会导致错误，但也可以防止它们。没有隐藏，您常常必须为变量（相同类型）使用不同的名称，这使得使用错误的变量成为可能。</p><hr><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/551b748ead0b4ae89a1797d0032b79f5~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>我昨天也注意到了这一点。我宁愿没有它，因为它引入了难以发现的错误。</p><p>我自己的C ++代码中有与隐藏相关的错误（就在上周）。此外，由于隐藏局部变量，Gecko中也存在bug，因此显然这很危险。</p><p>我知道Rust是一种旨在防止错误的语言（即使它降低了便利性），所以我想知道为什么允许这样做。值得冒着出BUG的风险吗？</p><p>也有人评论说lint有一个选项可以警告可变阴影。会有这样的选择吗？但愿如此。</p><p>顺便提一句。该线程还有一些用例，人们如何使用隐藏，因此对此线程感兴趣的人应该通读它。</p><ul><li><a href="https://mail.mozilla.org/pipermail/rust-dev/2013-May/004306.html">[rust-dev] Variables with the same name in the same scope</a></li><li><a href="https://mail.mozilla.org/pipermail/rust-dev/2013-May/004298.html">[rust-dev] Variables with the same name in the same scope</a></li></ul><hr><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/07fc8946bff243ddaf9abac9db16b02d~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>我经常使用隐藏来防止再次引用另一个变量。这是我从OCaml那里习得的一种习惯，在该习惯下，隐藏是允许的并且是惯用的。</p><p>比如，在 <code>trans</code> 中我经常隐藏来终止块变量，因为再次使用它们是一个错误。这样，隐藏可以成为防止错误的强大工具。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>这个问答中，整体的意见比较一致，就是隐藏特性乍看起来很不爽，但是用多了就会惊讶于它的安全性、便捷性。凡事有捧就有踩，还是有老哥说出来隐藏用多了，很难调试，由于老哥过于啰嗦，懒得翻译原文如下：</p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c15812c59a3248c99c77eeed7838fabe~tplv-k3u1fbpfcp-zoom-1.image" style="zoom:50%;" /><p>个人觉得这有点杠了，所有设计一旦被滥用就会变得不对劲。总体来说隐藏带来的好处是大于益处的。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/2e8fd8fce8fc/</id>
    <link href="https://blog.zisheng.pro/posts/2e8fd8fce8fc/"/>
    <published>2023-04-19T10:08:20.000Z</published>
    <summary>在 Rust 的世界中，变量可以被一个同名的变量覆盖掉的现象就叫做隐藏。这意味着我们随后使用这个名称时，他指向的将会是第二个变量。</summary>
    <title>为什么 Rust 需要局部变量隐藏</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React-Native/"/>
    <category term="跨端开发" scheme="https://blog.zisheng.pro/tags/%E8%B7%A8%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    <category term="Flutter" scheme="https://blog.zisheng.pro/tags/Flutter/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/tags/React-Native/"/>
    <category term="RN" scheme="https://blog.zisheng.pro/tags/RN/"/>
    <category term="Weex" scheme="https://blog.zisheng.pro/tags/Weex/"/>
    <content>
      <![CDATA[<h2 id="缘起"><a href="#缘起" class="headerlink" title="缘起"></a>缘起</h2><p>那是 18 年的夏天，公司 6 月底去成都团建回郑州后，小弟、肖老师和当时绰号小萌的我（以下简称三剑客）开始了使用 React Native 开发 App 的工作。当时经验尚浅的我也只是有小程序和公众号的开发经验，唯一的亮点就是封装了 <a href="https://github.com/youngjuning/wx-promise-pro">wx-promise-pro</a> 并独立完成了一个小程序的全栈开发，而另外两位同事一个后端大佬经验丰富，一个 ios、vue、android、C#、Java 等等技能栈点满。</p><p>怎么说呢，当时也就中等焦虑吧。也就赶鸭子上架各种不适，也就睡不好吃不好担心末位淘汰，也就担心没工作被我爸唠叨。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c327b38e8ca3410ea7f74d67d8080ccb~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>人们常说压不倒你的，最终都能成就你。我就是顶着巨大心里压力一头扎进了跨端开发这个领域，一晃便是两年多过去了。接着掘金的活动，和大家聊一聊 RN 的一些事。</p><h2 id="聊聊-React-Native-入门"><a href="#聊聊-React-Native-入门" class="headerlink" title="聊聊 React Native 入门"></a>聊聊 React Native 入门</h2><p>找到紫升询问 React Native 最多的问题便是 React Native 环境的搭建，我都会耐心地帮助大家排查（有时也会让仔细看官网）。这个时候我一般都会和他们聊一下我们三剑客初次搭建环境最快的也是用了一整个工作日，最慢的因为电脑环境混乱多花了一个周末排查问题的故事。</p><p>万事开头难这句话用在 RN 和 Flutter 上尤其合适，跨端对于原生转和前端上手都是不太友好的，所以不要再问前端转合不合适，原生转不比你舒服到哪里去。</p><p>这里提醒大家关于环境的几点：</p><ul><li>Java jdk 一定要安装 <strong>1.8</strong> 系列</li><li>尽量使用安卓真机测试</li><li>如果用 Android Studio 打开项目，不要跟着提示随便升级 gradle</li><li>RN 0.60 之后 ios 使用 cocoapod 管理项目依赖，这玩意如果网速不好，会卡住。建议别折腾，买个 ** 也饿不死人的</li></ul><h2 id="聊聊跨端开发"><a href="#聊聊跨端开发" class="headerlink" title="聊聊跨端开发"></a>聊聊跨端开发</h2><p>跨端开发也叫 Hybrid Development，React Native、Flutter、Weex 都属于这个范畴。任何事物的出现必然是有其客观需求存在，跨端开发相较于传统的原生开发有其迭代周期快、可以在发布应用后热更新代码、甚至代码直接通过服务动态下发的优点，相较于 Codava、H5 打包这种解决了 60 帧问题，性能更好更接近原生。</p><p>目前前两者在各大厂 App 中都有业务落地，众所周知的闲鱼使用 Flutter 写了不少业务，前文提到的我们三剑客是完全用 React Native 搭建的项目，而现在我所在的涂鸦智能的 App 是使用原生搭建项目，嵌入我们的核心面板业务。我们不可能把所有的设备控制面板都写在 App 中，ios、android 的平台也不能动态下发代码，而 React Native 可以实现开发面板 -&gt; 打包 -&gt; 注册到云端 -&gt; 客户端根据从服务端动态加载相应的业务代码这个流程。顺便提一下隔壁小组也有在做基于 Flutter 的一码多端方案，加入涂鸦，无论你是 RN 开发者还是 Flutter 开发者都能发挥所长。</p><h2 id="从事-RN-开发需要会什么？"><a href="#从事-RN-开发需要会什么？" class="headerlink" title="从事 RN 开发需要会什么？"></a>从事 RN 开发需要会什么？</h2><p>以我的经验来看，只要你有编程经验，快速从事 RN 业务开发难度并不大。写页面用到的无非是 React 语法、React Native 内置组件的使用和第三放组件、API 的使用。</p><p>但是如果是要掌控整个 App 的架构和开发，那要求会多一些。但也无非是 React 开发同样需要掌握的全局状态管理、路由管理、组件封装等能力。</p><p>你可能会问不会原生开发可以从事 RN 开发吗？答案是肯定的，跨端方案的出现对于前端开发者的一大利好便是可以使用前端技术开发移动应用。React Native 不管是官方还是社区都有大量的封装好的 SDK 可供使用，比如 <a href="https://github.com/react-native-svg/react-native-svg">react-native-svg</a>、<a href="https://github.com/react-native-camera/react-native-camera">react-native-camera</a>、<a href="https://github.com/react-native-video/react-native-video">react-native-video</a>、<a href="https://github.com/zmxv/react-native-sound">react-native-sound</a>、<a href="https://github.com/zoontek/react-native-permissions">react-native-permissions</a> 等等这些优秀的三方库。当然你可能还有疑问，如果社区没有现成的，或者需要自定义原生的功能才能完成业务怎么办？这个就没那么简单了，但是别担心，现代工业社会是讲究协作和精细化分工的，在涂鸦，我们有专业的原生开发团队为我们提供底层 SDK 业务编写，但是我还建议大家 ios、android 的桥接代码还是要会写、原生的配置要懂，有助于提升个人能力和稀缺性。相信我这并不复杂，你遇到的问题搜一下也能解决。紫升的经验也仅限于掌握原生配置、对原生 SDK 进行封装暴露接口给 JS 层调用，ios 课程买了有一年了，也没看一点（实在惭愧）。对原生 SDK 桥接感兴趣的可以看下<a href="https://reactnative.cn/docs/native-modules-intro">原生模块简介</a>和<a href="https://github.com/brodybits/create-react-native-module">create-react-native-module</a>。</p><h2 id="聊聊我为社区做的一些事"><a href="#聊聊我为社区做的一些事" class="headerlink" title="聊聊我为社区做的一些事"></a>聊聊我为社区做的一些事</h2><blockquote><p>你可能会在各种 react native 仓库的 issue 中看到我的身影，这里也推荐大家遇到什么问题多去 github 看看 issues，一个成熟的仓库基本都能在 issue 中找到解决办法。</p></blockquote><ul><li><a href="https://github.com/youngjuning/umi-plugin-react-native">umi-plugin-react-native</a>：Show React Native Demo with dumi powered by react-native-web</li><li><a href="https://github.com/youngjuning/react-native-quill-editor">react-native-quill-editor</a>：React Native Quill Rich Text Editor Wrapper</li><li><a href="https://github.com/sishuguojixuefu/react-native-form">react-native-form</a>：私塾React Native动态表单项目，这个项目后来只在内部更新了，离职一年多了，大概率被同事重构了已经，我一直说要用 hooks 实现以下，也没得机会。</li><li><a href="https://github.com/sishuguojixuefu/react-native-screenshot">react-native-screenshot</a>：简单易用的 React Native 截屏、监听系统截屏事件组件（ios、android）</li><li><a href="https://github.com/sishuguojixuefu/react-native-office-viewer">react-native-office-viewer</a>：Office Preview Plugin for React Native（support png,pdf,doc,xls,txt…）</li><li><a href="https://github.com/sishuguojixuefu/react-native-template-sishu">react-native-template-sishu</a>：👾 Clean and minimalist React Native template for a quick start with TypeScript.</li><li><a href="https://github.com/sishuguojixuefu/react-native-qiniu">react-native-qiniu</a>：Qiniu React Native SDK ,A pure javascript implementation</li><li><a href="https://github.com/sishuguojixuefu/react-native-float-ball">react-native-float-ball</a>：🏀 floatBall with PanResponder 🏈</li><li><a href="https://juejin.cn/post/6844903843155689486">优雅地使用TypeScript开发React Native应用</a></li><li><a href="https://juejin.cn/post/6844904069937496071">react-navigation 5.x 最佳实践</a></li><li><a href="https://juejin.cn/post/6844904071137247240">React Native 项目架构面面观</a></li><li><a href="https://juejin.cn/post/6844904073309716494">React Native 集成 CodePush 指南</a></li><li><a href="https://juejin.cn/post/6844904083468337160">【译】在 React Native TypeScript 项目中使用 defaultProps</a></li><li><a href="https://juejin.cn/post/6859039959299391496">【译】在 React Native TypeScript 项目中使用 defaultProps</a></li><li><a href="https://juejin.cn/post/6867945949788897288">手摸手带你封装 React Native 富文本编辑器</a></li><li><a href="https://juejin.cn/post/6928412126587387917">React Native 调试最佳实践|牛气冲天新年征文</a></li></ul><h2 id="聊聊岗位待遇"><a href="#聊聊岗位待遇" class="headerlink" title="聊聊岗位待遇"></a>聊聊岗位待遇</h2><p>有一说一，React Native 包括 Flutter 在内，岗位比 Web 前端开发是要少很多的，但是这并不意味着不值得投资。相较于 Web 开发，跨端开发需要掌握更多的技能，入门更难一些。但是一般掌握了它，对应的薪资待遇也是很诱人的。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/2affeeb3c632/</id>
    <link href="https://blog.zisheng.pro/posts/2affeeb3c632/"/>
    <published>2023-04-19T10:01:42.000Z</published>
    <summary>那是 18 年的夏天，公司 6 月底去成都团建回郑州后，小弟、肖老师和当时绰号小萌的我（以下简称三剑客）开始了使用 React Native 开发 App 的工作。当时经验尚浅的我也只是有小程序和公众号的开发经验，唯一的亮点就是封装了 wx-promise-pro 并独立完成了一个小程序的全栈开发，而另外两位同事一个后端大佬经验丰富，一个 ios、vue、android、C#、Java 等等技能栈点满。</summary>
    <title>当我们在聊 React Native 时，我们在聊什么</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="Rust" scheme="https://blog.zisheng.pro/categories/Rust/"/>
    <category term="Rust" scheme="https://blog.zisheng.pro/tags/Rust/"/>
    <category term="Ownership" scheme="https://blog.zisheng.pro/tags/Ownership/"/>
    <category term="Borrowing" scheme="https://blog.zisheng.pro/tags/Borrowing/"/>
    <category term="Lifetimes" scheme="https://blog.zisheng.pro/tags/Lifetimes/"/>
    <category term="所有权" scheme="https://blog.zisheng.pro/tags/%E6%89%80%E6%9C%89%E6%9D%83/"/>
    <category term="借用" scheme="https://blog.zisheng.pro/tags/%E5%80%9F%E7%94%A8/"/>
    <category term="生命周期" scheme="https://blog.zisheng.pro/tags/%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/"/>
    <content>
      <![CDATA[<blockquote><ul><li>原文地址：<a href="https://medium.com/@bugaevc/understanding-rust-ownership-borrowing-lifetimes-ff9ee9f79a9c">Understanding Rust: ownership, borrowing, lifetimes</a></li><li>原文作者：<a href="hhttps://medium.com/@bugaevc">bugaevc</a></li><li>译文出自：<a href="https://github.com/xitu/gold-miner">掘金翻译计划</a></li><li>本文永久链接：<a href="https://github.com/xitu/gold-miner/blob/master/article/2021/understanding-rust-ownership-borrowing-lifetimes.md">https://github.com/xitu/gold-miner/blob/master/article/2021/understanding-rust-ownership-borrowing-lifetimes.md</a></li><li>译者：<a href="https://blog.zisheng.pro/">紫升</a></li><li>校对者：<a href="https://github.com/PassionPenguin">霜羽 Hoarfroster</a>、<a href="https://github.com/zenblo">Zz招锦</a></li></ul></blockquote><p>我对这些概念的理解是，你一旦掌握了它，所有这些语法都会看起来自然且优雅。</p><p>我不会从零开始展开教学，也不会机械地重复官方文档的内容（虽然说有时会 🙈）—— 如果你还不了解这些概念，那么你现在应该读一下<a href="https://kaisery.github.io/trpl-zh-cn/ch04-00-understanding-ownership.html">对应章节</a>的内容，因为本文是对书上内容的补充，而不是要替代它。</p><p>另外，我也建议你读一下<a href="http://blog.skylight.io/rust-means-never-having-to-close-a-socket/">这篇</a>出色的文章。它实际上是在讲述相近的话题，但关注点不一样，也值得一读。</p><p>让我们先来谈谈资源是什么。资源是宝贵的、“沉重的”、可以获取和释放（或销毁）的东西，比如一个套接字，一个打开的文件，一个信号量，一个锁，一个堆内存区域。按照传统，所有这些事情都是通过调用一个函数来创建的，该函数返回对资源本身的某种引用（一个内存指针或一个文件描述符），当程序认为自己已完成对资源的处理时，需要程序员 👨🏻‍💻 显式关闭该文件。</p><p>这种方法存在着问题。人非圣贤，孰能无过。通常我们很容易忘记释放某些资源，从而导致发生所谓的<strong>内存泄漏</strong>。更糟糕的是，人们可能会尝试访问已经释放的资源（即在释放之后使用）。如果运气好，他们会收到一条报错消息，这可能会帮助他们识别和修复错误，也可能不会。反之，它们所具有的引用（尽管就逻辑而言是无效的）可能仍是引用某个“内存位置”，而该“内存位置”已经被其他资源占用。例如说已存储其他内容的内存，其它打开的文件所使用的文件描述符等。试图通过无效的引用访问旧资源可能会破坏其他资源或使得程序完全崩溃。</p><p>我们讨论这些问题并不是杞人忧天，因为它们无时无刻伴随着我们。比如，在 <a href="http://googlechromereleases.blogspot.ru/search/label/Stable%20updates">Google Chrome 发布博客</a>中就存在着大量因为使用了被释放的资源引发的漏洞和崩溃的修复记录 —— 这也极大的浪费了人力物力，去识别和修复它们。</p><p>并不是说开发人员是愚蠢和健忘的，因为逻辑流程本身就容易出错：它需要你显示释放资源，但是并不强制你做这些。此外，我们通常不会注意到资源被忘记释放，因为这个问题很少会有着什么明显的影响。</p><p>有时要实现简单的目地就需要发明复杂的解决方案，而这些解决方案会带来更复杂的逻辑。我们很难避免在庞大的代码库中迷失，并且 Bug 总是在这里或那里突然冒出来，我们最终也见怪不怪了。其中大多数的问题都很容易被发现，但是与资源相关的错误却很难被发现。因此，一旦如果资源被野指针利用，便会非常危险。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d66aa3a26a7842d7b2102f3a3ef2b6db~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>当然，像 Rust 这样的新语言无法为你解决 Bug，但是，它可以成功地影响你的思维方式，将一些架构带入你的思想，从而使这类错误的发生几率大大降低。</p><p>Rust 为你提供了一种安全清晰的方法来管理资源。而且，它不允许你以其他任何方式对其进行管理。这是非常严格的，但这不正是我们的目的吗？</p><p>这些限制之所以很棒，有几个原因:</p><ul><li>它们能让你以正确的方式思考。在有了一些 Rust 开发经验后，即使在其他语言的语法中没有内置这些概念时，你也经常会发现自己尝试应用相似的概念。</li><li>它们能让你编写的代码更安全。除了几个很稀有的<a href="https://doc.rust-lang.org/nomicon/meet-safe-and-unsafe.html">极端案例</a>，Rust 基本上可以保证你所有的代码都不会涉及我们正在谈论的错误。</li><li>虽然如果有垃圾收集机制，Rust 就会像高级语言一样令人愉悦（我可没说 JavaScript 是令人愉悦的！），但是 Rust 与其他低级编译语言一样快且接近底层。</li></ul><p>考虑到这一点，让我们来看一下 Rust 的一些优点。</p><h2 id="所有权"><a href="#所有权" class="headerlink" title="所有权"></a>所有权</h2><p>在 Rust 中，关于资源属于哪块代码有很明确的规则。在最简单的情况下，是代码块创建了代表资源的对象。在代码块的末尾，对象被销毁且资源被释放。这里重要的区别是对象不是某种容易忘记的“弱引用”。在内部，该对象只是用于完全相同引用的包装器，而从外部看，它似乎是它表示的资源。当到达拥有资源的代码块的末尾时，资源将会自动且可预测地释放。</p><p>当编译到拥有该内存的代码的尾部，程序会自动且安全地释放资源。妈妈再也不用担心忘记释放资源了！因为该行为是全自动且可预测的，它完全会按照你的预期来完成。</p><p>这时你可能会问，为什么我要描述这些琐碎而明显的事情，而不是仅仅告诉你聪明人称之为 <a href="https://zh.wikipedia.org/wiki/RAII">RAII</a> 的概念？ 好吧，让我们继续聊一下。</p><p>这个概念适用于临时对象。比如以下操作：<code>将一些文本写入文件</code> -&gt; <code>专用代码块（例如，一个函数）将打开一个文件</code>（结果是得到一个文件对象（包装文件描述符））-&gt; <code>然后对其进行一些处理</code> -&gt; <code>然后在该块的末尾将得到文件对象</code> -&gt; <code>最后删除并且文件描述符关闭</code>。</p><p>但是在很多场景中这个概念并不管用。你可能希望将资源传递给其他人，在几个“用户”之间甚至在线程之间共享它。</p><p>让我们来看看这些。首先，你可能希望将资源传递给其他人（转移所有权），被转移的人便会拥有资源，可以对资源进行任何操作，甚至更重要的是负责释放资源。Rust 很好的支持了这一点，实际上，当你将资源提供给其他人时，默认便会发生这种情况。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">print_sum</span>(v: <span class="type">Vec</span>&lt;<span class="type">i32</span>&gt;) &#123;</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;&#123;&#125;&quot;</span>, v[<span class="number">0</span>] + v[<span class="number">1</span>]);</span><br><span class="line">    <span class="comment">// v 被移除随后被释放</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">v</span> = <span class="type">Vec</span>::<span class="title function_ invoke__">new</span>(); <span class="comment">// 资源在这里被创建</span></span><br><span class="line">    <span class="keyword">for</span> <span class="variable">i</span> <span class="keyword">in</span> <span class="number">1</span>..<span class="number">1000</span> &#123;</span><br><span class="line">        v.<span class="title function_ invoke__">push</span>(i);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 在这里， 可变变量 v 被使用</span></span><br><span class="line">    <span class="comment">// 不少于 4000 字节的内存</span></span><br><span class="line">    <span class="comment">// -------------------</span></span><br><span class="line">    <span class="comment">// 转移所有权给 print_sum 函数</span></span><br><span class="line">    <span class="title function_ invoke__">print_sum</span>(v);</span><br><span class="line">    <span class="comment">// 我们不拥有并且不能以任何方式控制变量 v</span></span><br><span class="line">    <span class="comment">// 在这里尝试访问 v 将引发编译时错误</span></span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;We&#x27;re done&quot;</span>);</span><br><span class="line">    <span class="comment">// 这里并不会发生任何释放动作</span></span><br><span class="line">    <span class="comment">// 因为 print_sum 此时负责可变变量 v 的一切</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>所有权转移的过程也称为<strong>移动</strong>，因为资源是从旧位置（例如，局部变量）被移动到了新位置（例如，一个函数参数）的。从性能角度来看，这只是“弱引用”被移动，因此这个过程很快。但是对于代码来说，好像我们实际上将整个资源都移到了新地方。</p><p>移动和复制是有区别的。广义来说，它们都意味着复制数据（如果 Rust 允许复制资源的话，这种情况下将是“弱引用”），但移动后，原始变量的内容将被视为不再有效或不再重要。Rust 实际上会将该变量视为“ <a href="https://doc.rust-lang.org/nomicon/checked-uninit.html">逻辑上未初始化</a>”，也就是说，充满了一些垃圾，例如刚刚创建的那些变量。这类变量是被禁止使用的（除非你使用新值重新初始化它），此时也不会发生资源的重新分配：现在拥有资源的人有责任在完成后进行清理。</p><p>移动不仅限于传递参数。你可以移动给一个变量。你还可以移至返回值。为此，你可以从返回值、变量、函数参数移动。基本上到处都是隐式和显示的分配。</p><p>尽管移动语法是处理资源的完全合理的方式，我将在稍后演示对于普通的旧原始数字类型变量来说，这将是一场灾难（设想无法复制一个 int 类型变量的值给另一个变量）。幸运的是，Rust 有 <a href="https://doc.rust-lang.org/std/marker/trait.Copy.html">Copy 特征</a>。实现它的类型（所有原始类型都使用）在分配时使用复制语法，所有其他类型都使用移动语法。这非常容实现，如果你希望自己的类型是可以被复制的，则只需要可选地实现 <code>Copy</code> 特征。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">print_sum</span>(a: <span class="type">i32</span>, b: <span class="type">i32</span>) &#123;</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;&#123;&#125;&quot;</span>, a + b);</span><br><span class="line">    <span class="comment">// 被复制的 a 和 b 变量在这里被移除和释放</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">a</span> = <span class="number">35</span>;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">b</span> = <span class="number">42</span>;</span><br><span class="line">    <span class="comment">// 复制和传递值</span></span><br><span class="line">    <span class="comment">// 被复制的值传递的所有权传递给 print_sum：</span></span><br><span class="line">    <span class="title function_ invoke__">print_sum</span>(a, b);</span><br><span class="line">    <span class="comment">// 我们仍然保留对原始a和b变量的完全控制权</span></span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;We still have &#123;&#125; and &#123;&#125;&quot;</span>, a, b);</span><br><span class="line">    <span class="comment">// 原始的 a 和 b 被移除并随后被释放</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>现在，我们来探讨下为什么移动语法会有用呢？如果没有他们，一切都显得那么完美。好吧，也不完全是。有时候，这是最合乎逻辑的事情。比如 <a href="https://doc.rust-lang.org/std/string/struct.String.html#method.with_capacity">with_capacity</a> 函数会分配一个字符串缓冲区，然后将其返回给调用方。所有权被转移了，并且该函数不再关心缓冲区的生死。而调用者可以完全控制缓冲区，包括负责缓冲区的释放。</p><p>在 C 语言中是一样的。诸如 <code>strdup</code> 之类的功能将分配内存，将其内存管理交给你，并期望你进行管理并最终对其进行分配。区别在于它只是一个指针，它们所能做的就是在完成后要求或提醒你使用 <code>free()</code>。上面所说的移动特性几乎无法做到，而在 Rust 中，这是该语言不可分割的一部分。</p><p>另一个示例是迭代器适配器，比如 <a href="https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.count">count</a> 这种无论如何之后都没有访问迭代器的意义。</p><p>相反的问题是，在什么情况下，我们需要对同一资源有多个引用。最明显的用例是进行多线程处理的场景。否则，如果所有操作都按顺序执行，则移动语法可能总是起作用的。尽管如此，一直来回移动东西还是很不方便的。</p><p>有时，尽管代码严格按顺序运行，但仍然感觉好像同时发生了几件事。想象一下在 vector（可变数组）上进行迭代。循环完成后，迭代器可以将你对相关 vector 的所有权转移给你，但你将无法在循环内获得对 vector 的任何访问权限。也就是说，除非你每次迭代都在你的代码和迭代器之间拥有所有权，否则那将是一团糟。似乎也无法在不破坏堆栈的情况下遍历一棵树，然后重新构造并备给以后做其他事情时用。</p><p>同时，我们将无法执行多线程，这就很不方便甚至让人厌烦。值得庆幸的是，还有一个很酷的 Rust 概念可以为我们提供帮助。那就是借用！</p><h2 id="借用"><a href="#借用" class="headerlink" title="借用"></a>借用</h2><blockquote><p>当一个函数使用引用而不是值本身作为参数时，我们便不需要为了归还所有权而特意去返回值，毕竟在这种情况下，我们根本没有取得所有权。这种通过引用传递参数给函数的方法也被成为借用。——《Rust 权威指南》</p></blockquote><p>我们有多种角度解读借用：</p><ul><li>它使我们在拥有资源的多个引用的同时仍坚持“单一所有者，单一责任”的概念。</li><li>引用类似于 C 语言中的指针。</li><li>引用也是一个对象。可变引用被移动，不可变引用被复制。删除引用后，借用将终止（取决于生命周期规则，请参见下一节）。</li><li>在最简单的情况下，引用的行为就像在没有明确地进行所有权操作的情况下来回移动所有权。</li></ul><p>下面这段代码就是最后一条的意思：</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 没有借用发生</span></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">print_sum1</span>(v: <span class="type">Vec</span>&lt;<span class="type">i32</span>&gt;) <span class="punctuation">-&gt;</span> <span class="type">Vec</span>&lt;<span class="type">i32</span>&gt; &#123;</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;&#123;&#125;&quot;</span>, v[<span class="number">0</span>] + v[<span class="number">1</span>]);</span><br><span class="line">    <span class="comment">// 返回 v 把所有权返回</span></span><br><span class="line">    <span class="comment">// 顺便一提，由于 Rust 是基于表达式的，所有这里不需要使用 return 关键字便可返回值</span></span><br><span class="line">    v</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 有借用，明确的引用</span></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">print_sum2</span>(vr: &amp;<span class="type">Vec</span>&lt;<span class="type">i32</span>&gt;) &#123;</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;&#123;&#125;&quot;</span>, (*vr)[<span class="number">0</span>] + (*vr)[<span class="number">1</span>]);</span><br><span class="line">    <span class="comment">// vr 是一个引用，在这里被移除，因为借用结束了</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 这就是你应该做的</span></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">print_sum3</span>(v: &amp;<span class="type">Vec</span>&lt;<span class="type">i32</span>&gt;) &#123;</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;&#123;&#125;&quot;</span>, v[<span class="number">0</span>] + v[<span class="number">1</span>]);</span><br><span class="line">    <span class="comment">// 同 print_sum2</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">v</span> = <span class="type">Vec</span>::<span class="title function_ invoke__">new</span>(); <span class="comment">// 创建可变数组</span></span><br><span class="line">    <span class="keyword">for</span> <span class="variable">i</span> <span class="keyword">in</span> <span class="number">1</span>..<span class="number">1000</span> &#123;</span><br><span class="line">        v.<span class="title function_ invoke__">push</span>(i);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 此时， v 被使用</span></span><br><span class="line">    <span class="comment">// 不超过 4000 字节的内存</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 传递 v 的所有权给 print_sum 并在执行结束后反会 v</span></span><br><span class="line">    v = <span class="title function_ invoke__">print_sum1</span>(v);</span><br><span class="line">    <span class="comment">// 现在，我们重新取得了 v 的所有权</span></span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;(1) We still have v: &#123;&#125;, &#123;&#125;, ...&quot;</span>, v[<span class="number">0</span>], v[<span class="number">1</span>]);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 取 v 的引用传递给 print_sum2（借用它）</span></span><br><span class="line">    <span class="title function_ invoke__">print_sum2</span>(&amp;v);</span><br><span class="line">    <span class="comment">// v 现在仍然可以被使用</span></span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;(2) We still have v: &#123;&#125;, &#123;&#125;, ...&quot;</span>, v[<span class="number">0</span>], v[<span class="number">1</span>]);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 此时仍可以</span></span><br><span class="line">    <span class="title function_ invoke__">print_sum3</span>(&amp;v);</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">&quot;(3) We still have v: &#123;&#125;, &#123;&#125;, ...&quot;</span>, v[<span class="number">0</span>], v[<span class="number">1</span>]);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// v 被移除并在此处被释放</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>让我们看看这里发生了什么。第一个函数中，我们可以始终转移所有权，但是我们已经确信有时这并不是我们想要的。</p><p>第二个函数中，我们对 vector 进行引用，然后将其传递给函数。和 C 语言很像，我们通过解引用来获取对象。由于没有复杂的生命周期，因此一旦删除引用，借用便会终止。虽然它看起来像第一个示例，但是有一个重要的区别。<code>main</code> 函数拥有 vector 的所有权，在借用 vector 时只能对它做些限制。在这个示例中，<code>main</code> 函数在借用 vector 时甚至没有机会观察向量，因此这没什么大不了的。</p><p>第三个函数结合了第一个函数不需要解引用和第二个函数不弄乱所有权的优点。这之所以可行是因为 Rust 的<a href="http://stackoverflow.com/questions/28519997/what-are-rusts-exact-auto-dereferencing-rules">自动解除引用规则</a>。这些有点复杂，但是在大多数情况下，它们可以使你几乎就像使用引用指向的对象一样编写代码，这和 C++ 的引用很相似。</p><p>这里是另一个示例：</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 通过不可变引用获取 v</span></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">count_occurences</span>(v: &amp;<span class="type">Vec</span>&lt;<span class="type">i32</span>&gt;, val: <span class="type">i32</span>) <span class="punctuation">-&gt;</span> <span class="type">usize</span> &#123;</span><br><span class="line">    v.<span class="title function_ invoke__">into_iter</span>().<span class="title function_ invoke__">filter</span>(|&amp;&amp;x| x == val).<span class="title function_ invoke__">count</span>()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">v</span> = <span class="built_in">vec!</span>[<span class="number">2</span>, <span class="number">9</span>, <span class="number">3</span>, <span class="number">1</span>, <span class="number">3</span>, <span class="number">2</span>, <span class="number">5</span>, <span class="number">5</span>, <span class="number">2</span>];</span><br><span class="line">    <span class="comment">// 为迭代借用 v</span></span><br><span class="line">    <span class="keyword">for</span> &amp;item <span class="keyword">in</span> &amp;v &#123;</span><br><span class="line">        <span class="comment">// the first borrow is still active</span></span><br><span class="line">        <span class="comment">// 第一个借用仍生效</span></span><br><span class="line">        <span class="comment">// 我们在这里第二次借用</span></span><br><span class="line">        <span class="keyword">let</span> <span class="variable">res</span> = <span class="title function_ invoke__">count_occurences</span>(&amp;v, item);</span><br><span class="line">        <span class="built_in">println!</span>(<span class="string">&quot;&#123;&#125; is repeated &#123;&#125; times&quot;</span>, item, res);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>你无需关心 <code>count_occurrences</code> 函数内部发生的事情，只需要知道它借用了 vector 即可（再次提醒，没有移动它）。循环也借用了 vector，因此我们有两个借用处于同时活动状态。循环结束后，<code>main</code> 函数将删除 vector。</p><p>哈哈，我会有点不地道了。我前面提到多线程是需要引用的主要原因，但是我展示的所有示例都是单线程的。如果你真的有兴趣，可以在 Rust 中获得有关多线程的一些<a href="https://doc.rust-lang.org/book/concurrency.html">详细信息</a>。</p><p>获取和删除引用似乎很有效，好像涉及到垃圾回收一样。但实际并不是这样的。这一切都在编译时完成。为此，Rust 需要另一个神奇的概念。让我们看下以下示例代码：</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">middle_name</span>(full_name: &amp;<span class="type">str</span>) <span class="punctuation">-&gt;</span> &amp;<span class="type">str</span> &#123;</span><br><span class="line">    full_name.<span class="title function_ invoke__">split_whitespace</span>().<span class="title function_ invoke__">nth</span>(<span class="number">1</span>).<span class="title function_ invoke__">unwrap</span>()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">name</span> = <span class="type">String</span>::<span class="title function_ invoke__">from</span>(<span class="string">&quot;Harry James Potter&quot;</span>);</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">res</span> = <span class="title function_ invoke__">middle_name</span>(&amp;name);</span><br><span class="line">    <span class="built_in">assert_eq!</span>(res, <span class="string">&quot;James&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这是可以被成功编译的，但下面的代码是无法被编译的:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">middle_name</span>(full_name: &amp;<span class="type">str</span>) <span class="punctuation">-&gt;</span> &amp;<span class="type">str</span> &#123;</span><br><span class="line">    full_name.<span class="title function_ invoke__">split_whitespace</span>().<span class="title function_ invoke__">nth</span>(<span class="number">1</span>).<span class="title function_ invoke__">unwrap</span>()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">res</span>;</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">let</span> <span class="variable">name</span> = <span class="type">String</span>::<span class="title function_ invoke__">from</span>(<span class="string">&quot;Harry James Potter&quot;</span>);</span><br><span class="line">        res = <span class="title function_ invoke__">middle_name</span>(&amp;name);</span><br><span class="line">        <span class="comment">// `name` 在这里被移除并随后被释放</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">assert_eq!</span>(res, <span class="string">&quot;James&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>首先，让我们解释下 <a href="http://doc.rust-lang.org/book/strings.html"><code>string</code> 类型</a>。<code>String</code> 拥有字符串缓冲区，一个 <code>&amp;str</code>（字符串切片）是 <code>String</code> 类型的一段或其他内存的一段（在这里并不重要）。</p><p>为了解释地更加明显，我用 C 语言编写类似的内容：</p><blockquote><p>顺便一提：在 C 语言中，你不能获取字符串的中间部分，因为标记字符串的结尾将需要更改字符串，因此我们仅限于在此处查找姓氏。</p></blockquote><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;string.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">const</span> <span class="type">char</span> *<span class="title function_">last_name</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *full_name)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">strrchr</span>(full_name, <span class="string">&#x27; &#x27;</span>) + <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">const</span> <span class="type">char</span> *buffer = <span class="built_in">strcpy</span>(<span class="built_in">malloc</span>(<span class="number">80</span>), <span class="string">&quot;Harry Potter&quot;</span>);</span><br><span class="line">    <span class="type">const</span> <span class="type">char</span> *res = last_name(buffer);</span><br><span class="line">    <span class="built_in">free</span>(buffer);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;%s\n&quot;</span>, res);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>你现在明白了吗？在使用结果之前，将删除缓冲区并重新分配缓冲区。这是一个在释放后使用资源的特殊例子。 如果 <code>printf</code> 的实现不会立即将内存用于其他用途，则此 C 代码可以编译并运行良好。不过，在一个不那么特殊的示例中，它仍然是崩溃、错误和安全漏洞的来源。正是在介绍所有权之前我们所说的。</p><p>你甚至无法在 Rust 中进行编译（我的意思是上面的 Rust 代码）。这种静态分析机制已内置在语言中，并且在整个生命周期可用。</p><h2 id="生命周期"><a href="#生命周期" class="headerlink" title="生命周期"></a>生命周期</h2><p>资源在 Rust 中是有生命周期的。他们从被创造的那一刻起一直存在到被移除的那一刻。生命周期通常被认为是作用域或代码块，但这实际上并不是一个准确的表述，因为资源可以在代码块之间移动，正如我们已经看到的那样。我们无法引用尚未创建或已删除的对象，我们很快就会看到这个要求是如何被强制执行。否则，这一切看起来都与所有权概念并没有什么不同。</p><p>这是比较难理解的一部分。引用以及其他对象也具有生存期，并且这些生存期可能与它们的借用的生存期不同（所谓的关联生命周期）。</p><p>让我们来改写下代码。借用的持续时间可能长于它所控制的引用的时间。这通常是因为可以使用另一个引用，该引用取决于借用是否处于活动状态——可以借用相同的对象或只借用其一部分，例如上例中的字符串切片。</p><p>实际上，每个引用都会记住它所代表的借用期限，也就是说，每一个引用都有一个生命周期。像所有与“借用检查”相关的事情一样，这是在编译时完成的，并且不占用任何运行时开销。与其他事物不同，你有时必须明确指定生命周期详细信息。</p><p>综上所述，让我们用代码深入探讨下：</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">middle_name</span>&lt;<span class="symbol">&#x27;a</span>&gt;(full_name: &amp;<span class="symbol">&#x27;a</span> <span class="type">str</span>) <span class="punctuation">-&gt;</span> &amp;<span class="symbol">&#x27;a</span> <span class="type">str</span> &#123;</span><br><span class="line">    full_name.<span class="title function_ invoke__">split_whitespace</span>().<span class="title function_ invoke__">nth</span>(<span class="number">1</span>).<span class="title function_ invoke__">unwrap</span>()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">name</span> = <span class="type">String</span>::<span class="title function_ invoke__">from</span>(<span class="string">&quot;Harry James Potter&quot;</span>);</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">res</span> = <span class="title function_ invoke__">middle_name</span>(&amp;name);</span><br><span class="line">    <span class="built_in">assert_eq!</span>(res, <span class="string">&quot;James&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 不会被编译:</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">    let res;</span></span><br><span class="line"><span class="comment">    &#123;</span></span><br><span class="line"><span class="comment">        let name = String::from(&quot;Harry James Potter&quot;);</span></span><br><span class="line"><span class="comment">        res = middle_name(&amp;name);</span></span><br><span class="line"><span class="comment">    &#125;</span></span><br><span class="line"><span class="comment">    assert_eq!(res, &quot;James&quot;);</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在前面的示例中，我们不必明确地指出生命周期，因为生命周期的细致程度足以让 Rust 编译器自动找出来（请参阅[lifetime elision](<a href="https://doc.rust-lang.org/book/lifetimes.html">https://doc.rust-lang.org/book/lifetimes.html</a> ＃lifetime-elision)）。无论如何，我们已经在这里演示了它们的工作原理。</p><p><code>&lt;&gt;</code> 表示该函数在整个生命周期内都是通用的，我们称其为 <code>a</code>。也就是说，对于具有关联生命周期的任何引用传入，它将返回具有相同关联生命周期的另一个引用。友情提示，关联的生命周期是指借用的生命周期，而不是引用的生命周期。</p><p>在实践中，它的含义可能不是显而易见的，所以让我们从相反的角度来看它。返回的引用被存储在 <code>res</code> 变量中，该变量在 <code>main()</code> 的整个范围内都有效。那是引用的生命周期，因此借用（相关的生命周期）至少存在了很长的时间。这意味着函数传入参数的关联生命周期必须相同，因此我们可以得出结论，必须为整个函数借用 <code>name</code> 变量。</p><p>在释放后使用的示例中（此处已注释），<code>res</code> 的生命周期仍然是整个函数，而 <code>name</code> 的生存周期没有足够长的时间，以至于借用不能在整个函数中有效。如果你尝试编译此代码，毫无疑问会触发编译错误。</p><p>Rust 编译器尝试使借用的生命周期尽可能短，理想情况下，一旦引用被移除就结束了（这是我在<strong>借用</strong>部分开始时所说的“最简单的情况”）。“借用应有尽可能长的生命周期” 的约束却是以另一种相反的方式运作的，比如从 <code>result</code> 到原始借用的生命周期会延伸地很长。只要满足所有约束条件，此过程就会停止，如果无法实现，则会出错。</p><p>你无法欺骗 Rust 让函数的返回的借用的值与生命周期完全无关，因为那样的话，在函数中你将得到相同的 <code>does not live long enough</code> 报错信息，因为不相关的生命周期可能比传入的生命周期长很多。</p><p>让我们来看下这个示例：</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">search</span>&lt;<span class="symbol">&#x27;a</span>, <span class="symbol">&#x27;b</span>&gt;(needle: &amp;<span class="symbol">&#x27;a</span> <span class="type">str</span>, haystack: &amp;<span class="symbol">&#x27;b</span> <span class="type">str</span>) <span class="punctuation">-&gt;</span> <span class="type">Option</span>&lt;&amp;<span class="symbol">&#x27;b</span> <span class="type">str</span>&gt; &#123;</span><br><span class="line">    <span class="comment">// 想象这里有一些聪明的算法</span></span><br><span class="line">    <span class="comment">// 返回了一个原始字符串的切片</span></span><br><span class="line">    <span class="keyword">let</span> <span class="variable">len</span> = needle.<span class="title function_ invoke__">len</span>();</span><br><span class="line">    <span class="keyword">if</span> haystack.<span class="title function_ invoke__">chars</span>().<span class="title function_ invoke__">nth</span>(<span class="number">0</span>) == needle.<span class="title function_ invoke__">chars</span>().<span class="title function_ invoke__">nth</span>(<span class="number">0</span>) &#123;</span><br><span class="line">        <span class="title function_ invoke__">Some</span>(&amp;haystack[..len])</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> haystack.<span class="title function_ invoke__">chars</span>().<span class="title function_ invoke__">nth</span>(<span class="number">1</span>) == needle.<span class="title function_ invoke__">chars</span>().<span class="title function_ invoke__">nth</span>(<span class="number">0</span>) &#123;</span><br><span class="line">        <span class="title function_ invoke__">Some</span>(&amp;haystack[<span class="number">1</span>..len+<span class="number">1</span>])</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="literal">None</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">haystack</span> = <span class="string">&quot;hello little girl&quot;</span>;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">res</span>;</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">let</span> <span class="variable">needle</span> = <span class="type">String</span>::<span class="title function_ invoke__">from</span>(<span class="string">&quot;ello&quot;</span>);</span><br><span class="line">        res = <span class="title function_ invoke__">search</span>(&amp;needle, haystack);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">match</span> res &#123;</span><br><span class="line">        <span class="title function_ invoke__">Some</span>(x) =&gt; <span class="built_in">println!</span>(<span class="string">&quot;found &#123;&#125;&quot;</span>, x),</span><br><span class="line">        <span class="literal">None</span> =&gt; <span class="built_in">println!</span>(<span class="string">&quot;nothing found&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 输出 &quot;found ello&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>search</code> 函数接受两个引用，这些引用具有完全不相关的生命周期。尽管 <code>haystack</code> 受到限制，但关于 <code>needle</code> 的唯一要求是在函数本身执行时借用必须有效。完成后，借用立即结束，我们可以安全地重新分配关联的内存，同时仍然保持函数结果不变。</p><p><code>haystack</code>是用字符串字面量初始化的。这些是 <code>&amp;’static str</code> 类型的字符串切片（一个始终有效的借用）。因此我们可以在需要时将 <code>res</code> 变量保持在有效范围内。这是借用期限尽可能短规则的例外。你可以将其视为对“借用字符串”的另一个限制：字符串字面量借用必须持续整个程序的整个执行时间。</p><p>最后，我们返回的不是引用本身，而是一个内部的复合对象。这是完全支持的并且不会影响我们的一生逻辑。</p><p>因此，在此示例中，该函数接受两个参数，并且在两个生存期内都是通用的。让我们看看如果我们将生命周期设置为相同，会发生什么情况：</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">the_longest</span>&lt;<span class="symbol">&#x27;a</span>&gt;(s1: &amp;<span class="symbol">&#x27;a</span> <span class="type">str</span>, s2: &amp;<span class="symbol">&#x27;a</span> <span class="type">str</span>) <span class="punctuation">-&gt;</span> &amp;<span class="symbol">&#x27;a</span> <span class="type">str</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> s1.<span class="title function_ invoke__">len</span>() &gt; s2.<span class="title function_ invoke__">len</span>() &#123; s1 &#125; <span class="keyword">else</span> &#123; s2 &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">s1</span> = <span class="type">String</span>::<span class="title function_ invoke__">from</span>(<span class="string">&quot;Python&quot;</span>);</span><br><span class="line">    <span class="comment">// 明确借用以确保借入的持续时间长于s2</span></span><br><span class="line">    <span class="keyword">let</span> <span class="variable">s1_b</span> = &amp;s1;</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">let</span> <span class="variable">s2</span> = <span class="type">String</span>::<span class="title function_ invoke__">from</span>(<span class="string">&quot;C&quot;</span>);</span><br><span class="line">        <span class="keyword">let</span> <span class="variable">res</span> = <span class="title function_ invoke__">the_longest</span>(s1_b, &amp;s2);</span><br><span class="line">        <span class="built_in">println!</span>(<span class="string">&quot;&#123;&#125; is the longest if you judge by name&quot;</span>, res);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我在内部代码块之外进行了明确的借用，因此借用会在 <code>main()</code> 的其余部分都有效。这明显和 <code>&amp;s2</code> 的生命周期不一样。如果仅接受两个具有相同生命周期的参数，那么这里为什么可以调用该函数？</p><p>事实证明，相关的生命周期会受到 <a href="https://en.wikipedia.org/wiki/Type_conversion">类型强制</a> 的约束。与大多数语言（至少是我所熟知的那些语言）不同，Rust 中的原始（整数）值不会强制转换，为此你必须始终明确地强制转换它们。你可以在一些不太明显的地方找到强制转换，例如这些关联的生命周期和 <a href="http://doc.rust-lang.org/book/trait-objects.html#dynamic-dispatch">dynamic dispatch with type erasure</a>。</p><p>我们用 C++ 代码进行比较：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">A</span> &#123;</span><br><span class="line">    <span class="type">int</span> x;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">B</span>: A &#123;</span><br><span class="line">    <span class="type">int</span> y;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">C</span>: B &#123;</span><br><span class="line">    <span class="type">int</span> z;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function">B <span class="title">func</span><span class="params">(B arg)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> arg;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    A a;</span><br><span class="line">    B b;</span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">     * 这很好用：B值是有效的A值</span></span><br><span class="line"><span class="comment">     * 换句话说，只要期望A值，就可以使用B值</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    a = b;</span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">     * 另一方面，这将是一个错误</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// b = a;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 这能很好地工作</span></span><br><span class="line">    C arg;</span><br><span class="line">    A res = <span class="built_in">func</span>(arg);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>派生类型强制为其基本类型。 当我们传递 <code>C</code> 的实例时，它强制转换为 <code>B</code>，然后返回，强制转换为 <code>A</code>，然后存储在 <code>res</code> 变量中。</p><p>同样，在 Rust 中，更长的借用可以被强制缩短。它不会影响借用本身，而只会在需要较短借用的地方起作用。因此，你可以为函数传递寿命比预期更长的借用（它将被强制执行），并且可以强制将返回的借用的生命周期缩短。</p><p>再考虑一下这个示例：</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">middle_name</span>&lt;<span class="symbol">&#x27;a</span>&gt;(full_name: &amp;<span class="symbol">&#x27;a</span> <span class="type">str</span>) <span class="punctuation">-&gt;</span> &amp;<span class="symbol">&#x27;a</span> <span class="type">str</span> &#123;</span><br><span class="line">    full_name.<span class="title function_ invoke__">split_whitespace</span>().<span class="title function_ invoke__">nth</span>(<span class="number">1</span>).<span class="title function_ invoke__">unwrap</span>()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">name</span> = <span class="type">String</span>::<span class="title function_ invoke__">from</span>(<span class="string">&quot;Harry James Potter&quot;</span>);</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">res</span> = <span class="title function_ invoke__">middle_name</span>(&amp;name);</span><br><span class="line">    <span class="built_in">assert_eq!</span>(res, <span class="string">&quot;James&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 不会被编译：</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">    let res;</span></span><br><span class="line"><span class="comment">    &#123;</span></span><br><span class="line"><span class="comment">        let name = String::from(&quot;Harry James Potter&quot;);</span></span><br><span class="line"><span class="comment">        res = middle_name(&amp;name);</span></span><br><span class="line"><span class="comment">    &#125;</span></span><br><span class="line"><span class="comment">    assert_eq!(res, &quot;James&quot;);</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>人们通常会想知道这样的函数声明是否意味着参数的关联生命周期必须（至少）与返回值一样长，反之亦然。</p><p>答案现在应该很明显。对函数来说，两个生命周期完全相同。但是由于可以强制，你可以将其借用更长的时间，甚至可以在获得结果之后缩短结果的关联生命周期。因此正确的答案是参数必须至少与返回值一样长。</p><p>而且，如果你创建一个通过引用接受多个参数的函数，并声明它们必须具有相等的关联生命周期（如在我们之前的示例中一样），则该函数的实际参数将被强制为其中最短的生命周期。这只是意味着结果不能超过任何借用的参数。</p><p>这与我们之前讨论的反向约束规则可以很好地配合。被调用者并不关心这些-它只是获得并返回相同生命周期的借用。</p><p>另一方面，调用者确保参数的关联生命周期永远不会比结果的生命周期短，可以通过扩展它们来实现。</p><h2 id="小技巧"><a href="#小技巧" class="headerlink" title="小技巧"></a>小技巧</h2><ul><li>你不能移走借用的值，因为在借用结束后该值必须保持有效。即使你在下一行中移回某些内容，也无法将其移出。但是 <code>[mem::replace](https://doc.rust-lang.org/std/mem/fn.replace.html)</code> 特征可以让你同时做这两件事。</li><li>如果你想拥有一个像 C++ 中的 <code>unique_ptr</code> 一样的指针，可以使用 <code>[Box](https://doc.rust-lang.org/std/boxed/index.html)</code> 类型。</li><li>如果你想进行一些基本的引用计数-例如 C ++ 中的 <code>shared_ptr</code> 和 <code>weak_ptr</code>，可以使用 <a href="https://doc.rust-lang.org/std/rc/index.html">这些标准模块</a></li><li>如果你确实需要摆脱 Rust 所施加的限制，则可以随时求助于 <a href="https://doc.rust-lang.org/nomicon/meet-safe-and-unsafe.html">unsafe code</a></li></ul><blockquote><p>如果发现译文存在错误或其他需要改进的地方，欢迎到 <a href="https://github.com/xitu/gold-miner">掘金翻译计划</a> 对译文进行修改并 PR，也可获得相应奖励积分。文章开头的 <strong>本文永久链接</strong> 即为本文在 GitHub 上的 MarkDown 链接。</p></blockquote><hr><blockquote><p><a href="https://github.com/xitu/gold-miner">掘金翻译计划</a> 是一个翻译优质互联网技术文章的社区，文章来源为 <a href="https://juejin.im/">掘金</a> 上的英文分享文章。内容覆盖 <a href="https://github.com/xitu/gold-miner#android">Android</a>、<a href="https://github.com/xitu/gold-miner#ios">iOS</a>、<a href="https://github.com/xitu/gold-miner#%E5%89%8D%E7%AB%AF">前端</a>、<a href="https://github.com/xitu/gold-miner#%E5%90%8E%E7%AB%AF">后端</a>、<a href="https://github.com/xitu/gold-miner#%E5%8C%BA%E5%9D%97%E9%93%BE">区块链</a>、<a href="https://github.com/xitu/gold-miner#%E4%BA%A7%E5%93%81">产品</a>、<a href="https://github.com/xitu/gold-miner#%E8%AE%BE%E8%AE%A1">设计</a>、<a href="https://github.com/xitu/gold-miner#%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD">人工智能</a>等领域，想要查看更多优质译文请持续关注 <a href="https://github.com/xitu/gold-miner">掘金翻译计划</a>、<a href="http://weibo.com/juejinfanyi">官方微博</a>、<a href="https://zhuanlan.zhihu.com/juejinfanyi">知乎专栏</a>。</p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/c6ffca3ef902/</id>
    <link href="https://blog.zisheng.pro/posts/c6ffca3ef902/"/>
    <published>2023-04-19T09:59:17.000Z</published>
    <summary>本文由浅入深，深入浅出地讲解了 Rust 中的所有权、借用、生命周期，希望能帮助大家更好地理解 Rust 中的所有权、借用、生命周期。</summary>
    <title>深入理解 Rust 所有权、借用、生命周期</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React-Native/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/tags/React-Native/"/>
    <category term="Jest" scheme="https://blog.zisheng.pro/tags/Jest/"/>
    <category term="Enzyme" scheme="https://blog.zisheng.pro/tags/Enzyme/"/>
    <category term="单元测试" scheme="https://blog.zisheng.pro/tags/%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95/"/>
    <category term="测试覆盖率" scheme="https://blog.zisheng.pro/tags/%E6%B5%8B%E8%AF%95%E8%A6%86%E7%9B%96%E7%8E%87/"/>
    <category term="TDD" scheme="https://blog.zisheng.pro/tags/TDD/"/>
    <content>
      <![CDATA[<h2 id="单元测试是什么-🧐"><a href="#单元测试是什么-🧐" class="headerlink" title="单元测试是什么 🧐"></a>单元测试是什么 🧐</h2><p>单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。程序单元是应用的最小可测试部件，在 React 编程中，最小单元通常是组件、函数。如果你熟悉“测试驱动开发”（TDD：Test-Driven Development），单元测试也不会陌生，狭义来说就是单测驱动开发。</p><p>通常来说，程序员每修改一次程序就会进行最少一次单元测试，在编写程序的过程中前后很可能要进行多次单元测试，以证实程序达到软件规格书要求的工作目标，没有程序错误。在 TDD 中，甚至是先根据设计编写单元测试，然后根据单元测试写代码。</p><p>万丈高楼平地起，单元测试和文档一样，是保障程序最小单元质量的重要一环。试想一下，一块砖可能不需要使用说明书就可以量产使用，但是一块砖不经质检测验就投入使用带来的后果可能是恐怖的。从这个角度来看，单测可能是比文档更重要的存在。当然我们也不提倡为了单测而单测，单测是为了防范于未然。</p><h2 id="其他测试"><a href="#其他测试" class="headerlink" title="其他测试"></a>其他测试</h2><p>前端测试常见的测试类型有单元测试(Unit testing)、集成测试(Integration testing)、端到端(E2E testing)测试，一般我们投入的测试资源排序如下：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/411210ad2c1b46a69444efca8b5f9c35~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>集成测试是在单元测试的基础上，集成多个模块进行测试，确保模块之间互动行为正确无误的工作。有时，单一的模块完全通过单元测试，单独使用也没有问题，但是当与其他模块配合使用时，可能就出现问题了，下图是未通过集成测试的例子：</p><p><img src="https://user-images.githubusercontent.com/88981/52933895-c0d47600-338f-11e9-9034-11e1ad0c42f1.gif" alt="紫升"></p><p>端到端测试是站在用户角度出发（一端）到真实运行环境（另一端）进行测试。一般我们会使用 Cypress、puppeteer 这些工具进行自动化测试以替代人肉测试。下图是未通过端到端测试的例子：</p><p><img src="https://imgur.com/download/Po1unSh" alt="紫升"></p><h2 id="测试覆盖率"><a href="#测试覆盖率" class="headerlink" title="测试覆盖率"></a>测试覆盖率</h2><p>我们在测试的时候，会经常关心我们的代码是否都测试到了，以及哪些代码没有测试到。jest 内置了 Istanbul 测试覆盖率工具，我们可以通过四个维度的覆盖率来了解代码测试覆盖率情况：</p><ul><li>Statements（stmts）：表达式覆盖率，是不是每个表达式都执行了？</li><li>Branches（Branch）：分支覆盖率，是不是每个 if 代码块都执行了？</li><li>Functions（Funcs）：函数覆盖率，是不是每个函数都调用了？</li><li>Lines（Lines）：行覆盖率，是不是每一行都执行了？</li></ul><p>下图是执行 <code>jest --coverage</code> 之后生成的命令行输出：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/304867f9cd894882b110a3f36eb491f4~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>下图是生成的精美的测试覆盖率报告：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0f9eaa5afd7c48a8a0c6e354075790db~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>点击 App.js 可以查看单个文件的测试覆盖率情况：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9c6240f0c86b497999fe854b20533b1a~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>点开每个也没你，你会看到页面是五颜六色的，别担心，这些颜色都是有明确的意义：</p><ul><li>粉紅色的代码: 尚未被执行的 statement 或 function</li><li>黄色的代码: 沒被覆盖到的 branch</li><li>I: 代表 if-else 的 if 没有被执行<blockquote><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/530b087960784ddd821a4fe86d06525e~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p></blockquote></li><li>E: 代表 if-else 的 if 没有被执行<blockquote><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2215ef958ea5409aad116f07f42543d8~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p></blockquote></li><li>Nx: 代表代码块被执行到的次数，可以作为代码性能的参考依据</li></ul><h2 id="安装依赖"><a href="#安装依赖" class="headerlink" title="安装依赖"></a>安装依赖</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">yarn add jest -D</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">babel</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">yarn add babel-jest -D</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">enzyme</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">yarn add enzyme jest-enzyme enzyme-adapter-react-16 enzyme-to-json -D</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">react-native-mock-render</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">yarn add react-native-mock-render -D</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">types</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">yarn add @types/enzyme @types/jest @types/react @types/react-native -D</span></span><br></pre></td></tr></table></figure><p>工具介绍：</p><ul><li>jest: Jest 是一个令人愉快的 JavaScript 测试框架，专注于简洁明快。</li><li>enzyme: Enzyme 是用于 React 的 JavaScript 测试实用程序，可以更轻松地测试 React 组件的输出。您还可以根据给定的输出进行操作，遍历并以某种方式模拟运行时。</li><li>jest-enzyme: 针对 enzyme 的 Jest 断言</li><li>enzyme-adapter-react-16: React Native 测试所需的桥接器</li><li>enzyme-to-json: 将 Enzyme wrappers 转换成符合 Jest 快照测试的 JSON 格式。</li><li>react-native-mock-render: A fully mocked and test-friendly version of react native</li></ul><h2 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h2><h3 id="jest-config-js"><a href="#jest-config-js" class="headerlink" title="jest.config.js"></a>jest.config.js</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">preset</span>: <span class="string">&#x27;react-native&#x27;</span>,</span><br><span class="line">  <span class="attr">verbose</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="attr">collectCoverage</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="attr">moduleNameMapper</span>: &#123;</span><br><span class="line">    <span class="comment">// for https://github.com/facebook/jest/issues/919</span></span><br><span class="line">    <span class="string">&#x27;^image![a-zA-Z0-9$_-]+$&#x27;</span>: <span class="string">&#x27;GlobalImageStub&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;^[@./a-zA-Z0-9$_-]+\\.(png|gif)$&#x27;</span>: <span class="string">&#x27;RelativeImageStub&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">setupFilesAfterEnv</span>: [<span class="string">&#x27;&lt;rootDir&gt;/jest.setup.js&#x27;</span>],</span><br><span class="line">  <span class="attr">snapshotSerializers</span>: [<span class="string">&#x27;enzyme-to-json/serializer&#x27;</span>],</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><ul><li>collectCoverage: 生成测试覆盖率报告</li><li>setupFilesAfterEnv: 使用 Jest 运行安装文件以配置 Enzyme 和适配器（如下文<code>jest.setup.js</code>中所示），之前是<code>setupTestFrameworkScriptFile</code>，也可以使用<code>setupFiles</code></li><li>snapshotSerializers：推荐使用序列化程序使用<code>enzyme-to-json</code>，它的安装和使用非常简单，并允许您编写简洁的快照测试。</li></ul><blockquote><p>注意：Jest 在 <a href="https://stackoverflow.com/questions/55752673/option-setuptestframeworkscriptfile-was-replaced-by-configuration-setupfilesa">24.1.0</a> 之后只能使用 <code>setupFilesAfterEnv</code></p></blockquote><h3 id="jest-setup-js"><a href="#jest-setup-js" class="headerlink" title="jest.setup.js"></a>jest.setup.js</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">&#x27;react-native&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">&#x27;react-native-mock-render/mock&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">&#x27;react-native/Libraries/Animated/src/bezier&#x27;</span>; <span class="comment">// for https://github.com/facebook/jest/issues/4710</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Enzyme</span> <span class="keyword">from</span> <span class="string">&#x27;enzyme&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Adapter</span> <span class="keyword">from</span> <span class="string">&#x27;enzyme-adapter-react-16&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Enzyme</span>.<span class="title function_">configure</span>(&#123; <span class="attr">adapter</span>: <span class="keyword">new</span> <span class="title class_">Adapter</span>() &#125;);</span><br></pre></td></tr></table></figure><h2 id="enzyme-入门"><a href="#enzyme-入门" class="headerlink" title="enzyme 入门"></a>enzyme 入门</h2><p>enzyme 是 Airbnb 开源的 react 测试类库，提供了一套简洁强大的 API，并通过 jquery 风格的方式进行 dom 处理，开发体验十分友好. 它提供三种测试方法.</p><h3 id="shallow"><a href="#shallow" class="headerlink" title="shallow"></a>shallow</h3><p>shallow 返回组件的浅渲染，对官方 shallow rendering 进行封装。浅渲染 作用就是：它仅仅会渲染至虚拟 dom，不会返回真实的 dom 节点，这个对测试性能有极大的提升。shallow 只渲染当前组件，只能能对当前组件做断言</p><h3 id="mount"><a href="#mount" class="headerlink" title="mount"></a>mount</h3><p>mount 方法用于将 React 组件加载为真实 DOM 节点。mount 会渲染当前组件以及所有子组件。多数情况下，shallow 方法就能满足我们的需求了。ref 测试则旨在 mount 模式下生效。</p><h3 id="render"><a href="#render" class="headerlink" title="render"></a>render</h3><p>render 采用的是第三方库 Cheerio 的渲染，渲染结果是普通的 html 结构，对于 snapshot 使用 render 比较合适。</p><h2 id="组件测试"><a href="#组件测试" class="headerlink" title="组件测试"></a>组件测试</h2><h3 id="组件快照测试"><a href="#组件快照测试" class="headerlink" title="组件快照测试"></a>组件快照测试</h3><p>当我们要确保 UI 不会意外更改时，快照测试都是非常有用的工具。通过 <code>toMatchSnapshot</code> 即可完成。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">describe</span>(<span class="string">&#x27;Button Component&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;basic render&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> component = renderer.<span class="title function_">create</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Button</span> /&gt;</span></span>).<span class="title function_">toJSON</span>();</span><br><span class="line">    <span class="title function_">expect</span>(component).<span class="title function_">toMatchSnapshot</span>();</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="生命周期测试"><a href="#生命周期测试" class="headerlink" title="生命周期测试"></a>生命周期测试</h3><h4 id="componentDidMount"><a href="#componentDidMount" class="headerlink" title="componentDidMount"></a>componentDidMount</h4><p>通过调用 <code>shallow</code> 和 <code>mount</code> 方法，可以触发 componentDidMount 生命周期：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; shallow &#125; <span class="keyword">from</span> <span class="string">&#x27;enzyme&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">setup</span>(<span class="params">props = &#123;&#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> wrapper = <span class="title function_">shallow</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">CarouselComponent</span> /&gt;</span></span>);</span><br><span class="line">  <span class="keyword">const</span> instance = wrapper.<span class="title function_">instance</span>();</span><br><span class="line">  <span class="keyword">return</span> &#123; wrapper, instance &#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">describe</span>(<span class="string">&#x27;Carousel Component&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;renders correctly&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">setup</span>();</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>也可以通过 <code>wrapper.setState</code> 方法进行触发：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; shallow &#125; <span class="keyword">from</span> <span class="string">&#x27;enzyme&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">setup</span>(<span class="params">props = &#123;&#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> wrapper = <span class="title function_">shallow</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Component</span> &#123;<span class="attr">...props</span>&#125; /&gt;</span></span>);</span><br><span class="line">  <span class="keyword">const</span> instance = wrapper.<span class="title function_">instance</span>();</span><br><span class="line">  <span class="keyword">return</span> &#123; wrapper, instance &#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">describe</span>(<span class="string">&#x27;Component&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;renders correctly&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; wrapper &#125; = <span class="title function_">setup</span>();</span><br><span class="line">    wrapper.<span class="title function_">setState</span>(&#123;</span><br><span class="line">      <span class="attr">enable</span>: <span class="literal">true</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h4 id="componentWillUnMont"><a href="#componentWillUnMont" class="headerlink" title="componentWillUnMont"></a>componentWillUnMont</h4><p>通过调用 <code>wrapper.unmount()</code> 可以触发 componentWillUnMont 生命周期：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; shallow &#125; <span class="keyword">from</span> <span class="string">&#x27;enzyme&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">setup</span>(<span class="params">props = &#123;&#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> wrapper = <span class="title function_">shallow</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Component</span> /&gt;</span></span>);</span><br><span class="line">  <span class="keyword">const</span> instance = wrapper.<span class="title function_">instance</span>();</span><br><span class="line">  <span class="keyword">return</span> &#123; wrapper, instance &#125;;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">describe</span>(<span class="string">&#x27;Carousel Component&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;renders correctly&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; wrapper &#125; = <span class="title function_">setup</span>();</span><br><span class="line">    <span class="title function_">expect</span>(wrapper).<span class="title function_">toMatchSnapshot</span>();</span><br><span class="line">    wrapper.<span class="title function_">unmount</span>();</span><br><span class="line">    <span class="title function_">expect</span>(wrapper).<span class="title function_">toMatchSnapshot</span>();</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h4 id="componentWillReceiveProps"><a href="#componentWillReceiveProps" class="headerlink" title="componentWillReceiveProps"></a>componentWillReceiveProps</h4><p>可以通过 <code>wrapper.setProps</code> 方法触发：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; shallow &#125; <span class="keyword">from</span> <span class="string">&#x27;enzyme&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">setup</span>(<span class="params">props = &#123;&#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> wrapper = <span class="title function_">shallow</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Component</span> &#123;<span class="attr">...props</span>&#125; /&gt;</span></span>);</span><br><span class="line">  <span class="keyword">const</span> instance = wrapper.<span class="title function_">instance</span>();</span><br><span class="line">  <span class="keyword">return</span> &#123; wrapper, instance &#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">it</span>(<span class="string">&#x27;componentWillReceiveProps&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; wrapper, instance &#125; = <span class="title function_">setup</span>(&#123;</span><br><span class="line">    <span class="attr">autoplay</span>: <span class="literal">true</span>,</span><br><span class="line">  &#125;);</span><br><span class="line">  wrapper.<span class="title function_">setProps</span>(&#123; <span class="attr">autoplay</span>: <span class="literal">false</span> &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="定时器模拟（Timer-Mocks）"><a href="#定时器模拟（Timer-Mocks）" class="headerlink" title="定时器模拟（Timer Mocks）"></a>定时器模拟（Timer Mocks）</h3><p>原生定时器功能(即 setTimeout，setInterval，clearTimeout，clearInterval)对于测试环境来说不太理想，因为它们依赖于实时时间。<br>Jest 可以将定时器换成允许我们自己控制时间的功能。</p><p>这里我们通过调用 <code>jest.useFakeTimers()</code> 来启用假定时器。然后在需要的时候执行 <code>jest.runOnlyPendingTimers()</code> 来触发定时器：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; shallow &#125; <span class="keyword">from</span> <span class="string">&#x27;enzyme&#x27;</span>;</span><br><span class="line"></span><br><span class="line">jest.<span class="title function_">useFakeTimers</span>();</span><br><span class="line"></span><br><span class="line"><span class="title function_">it</span>(<span class="string">&#x27;autoplay methods with count(2) and os(ios)&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; wrapper, instance &#125; = <span class="title function_">setup</span>(&#123;</span><br><span class="line">    <span class="attr">autoplay</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">loop</span>: <span class="literal">false</span>,</span><br><span class="line">  &#125;);</span><br><span class="line">  wrapper.<span class="title function_">setState</span>(&#123; <span class="attr">isScrolling</span>: <span class="literal">true</span> &#125;, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    jest.<span class="title function_">runOnlyPendingTimers</span>();</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="FAQ"><a href="#FAQ" class="headerlink" title="FAQ"></a>FAQ</h2><h3 id="如何忽略某一块代码"><a href="#如何忽略某一块代码" class="headerlink" title="如何忽略某一块代码"></a>如何忽略某一块代码</h3><p>添加以下格式的注释到要忽略的代码块前即可：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* istanbul ignore next */</span></span><br></pre></td></tr></table></figure><h3 id="使用-mount-时，忽略-React-Native-的警告"><a href="#使用-mount-时，忽略-React-Native-的警告" class="headerlink" title="使用 mount 时，忽略 React Native 的警告"></a>使用 mount 时，忽略 React Native 的警告</h3><ul><li>参考自：<a href="https://github.com/enzymejs/enzyme/issues/831#issuecomment-352934963">Remove warnings when rendering react-native components</a></li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">describe</span>(<span class="string">&#x27;mounting&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> origConsole = <span class="variable language_">console</span>.<span class="property">error</span>;</span><br><span class="line">    <span class="title function_">beforeEach</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="property">error</span> = <span class="function">() =&gt;</span> &#123;&#125;;</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="title function_">afterEach</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="property">error</span> = origConsole;</span><br><span class="line">    &#125;);</span><br><span class="line">    it ......</span><br><span class="line">       mount....</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="常见-issues"><a href="#常见-issues" class="headerlink" title="常见 issues"></a>常见 issues</h3><ul><li><p>enzyme</p><ul><li><a href="https://github.com/enzymejs/enzyme/issues/1436">Create Adapter for React Native &amp; React 16</a></li><li><a href="https://github.com/enzymejs/enzyme/issues/991">Can’t simulate press event in react-native</a></li><li><a href="https://github.com/enzymejs/enzyme/issues/1636">Shallow with New React Context API. Consumer not getting context</a></li><li><a href="https://stackoverflow.com/questions/40776121/enzyme-is-not-finding-component-by-props">Enzyme is not finding component by props</a></li></ul></li><li><p>jest</p><ul><li><a href="https://github.com/facebook/jest/issues/4710">TypeError: Cannot read property ‘Object.<anonymous>‘ of null</a></li><li><a href="https://stackoverflow.com/questions/46252396/jest-how-to-test-if-a-component-does-not-exist">Jest - how to test if a component does not exist?</a></li><li><a href="https://github.com/enzymejs/enzyme/issues/316">Refs not working in component being shallow rendered</a></li><li><a href="https://github.com/facebook/jest/issues/6434">ReferenceError: You are trying to <code>import</code> a file after the Jest environment has been torn down.</a></li></ul></li><li><p>react-native</p><ul><li><a href="https://github.com/facebook/jest/issues/919">requiring image in react-native</a></li><li><a href="https://github.com/facebook/jest/issues/3822">Cannot find module ‘setupDevtools’ from ‘setup.js’</a></li><li><a href="https://github.com/react-navigation/react-navigation/issues/7950#issuecomment-615220412">Unable to resolve module <code>./views/assets/back-icon.png</code></a></li></ul></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/5e817da7587c/</id>
    <link href="https://blog.zisheng.pro/posts/5e817da7587c/"/>
    <published>2023-04-19T09:48:29.000Z</published>
    <summary>通常来说，程序员每修改一次程序就会进行最少一次单元测试，在编写程序的过程中前后很可能要进行多次单元测试，以证实程序达到软件规格书要求的工作目标，没有程序错误。在 TDD 中，甚至是先根据设计编写单元测试，然后根据单元测试写代码。</summary>
    <title>使用 Jest 和 Enzyme 进行 React Native 单元测试</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="Golang" scheme="https://blog.zisheng.pro/categories/Golang/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/tags/Golang/"/>
    <category term="Cobra" scheme="https://blog.zisheng.pro/tags/Cobra/"/>
    <category term="CLI" scheme="https://blog.zisheng.pro/tags/CLI/"/>
    <category term="Go" scheme="https://blog.zisheng.pro/tags/Go/"/>
    <content>
      <![CDATA[<h2 id="项目简介"><a href="#项目简介" class="headerlink" title="项目简介"></a>项目简介</h2><p><a href="https://github.com/tuya/tuya-panel-cli">tuya-panel-cli</a> 是一款基于 Go 语言的服务于涂鸦智能面板开发者的开发工具。</p><p>主要功能是基于 <a href="https://github.com/tuya/tuya-panel-demo">tuya-panel-demo</a> 仓库初始化一个面板项目、打包开发完成的项目以及工具后台自动检查更新。</p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7eeb936770f44767ba14ce60a170737b~tplv-k3u1fbpfcp-zoom-1.image" style="zoom: 33%;" /><p>项目基于流行的 Go CLI 框架 Cobra 项目开发，基本按预期实现了需求和设计目标，为之后进一步完善开发者生态找到了抓手，为形成开发闭环添加了重要一环（皮一下）。</p><p>我在该项目中负责需求承接、技术选型（后边会介绍）、方案设计、方案落地以及项目交付整个流程的工作。当然这并没什么可吹嘘的，如您所见也并不是什么复杂的项目。我之所以很想复盘，是因为这个项目是由我从 0 到 1 再到 1.1 完全把控的一个项目。</p><h2 id="项目背景"><a href="#项目背景" class="headerlink" title="项目背景"></a>项目背景</h2><p>涂鸦智能是一家全球化物联网 AI + IOT 领军企业，紫升所在的 <a href="https://www.tuya.com/cn/product/product-development/IoTOS-Development">IoTOS</a> 部门主要是赋能开发者使用涂鸦 IoTOS, 自主选择不同芯片进行智能产品开发。</p><p>随着公司内部和公司外部开发者不断增多，维护两套开发工具已不再适应趋势。为了更好地服务广大开发者。我们需要搭建统一的开发者平台。开发者工具（CLI）无疑是很好的开端。</p><h2 id="技术选型"><a href="#技术选型" class="headerlink" title="技术选型"></a>技术选型</h2><p>在真正开始开发前，紫升分别对 Node、Deno 和 Go 的方案进行了调研。之所以没有直接使用 Node 进行开发，一是因为 Boss 要求源代码不开放，二是因为我们的开发者很多是搞嵌入式的。为了最大程度方便开发者（无需安装运行环境）我们们需要一个可以将 CLI 打包成二进制的方案。</p><h3 id="Node"><a href="#Node" class="headerlink" title="Node"></a><strong>Node</strong></h3><p>众所周知，Node 开发的 CLI 是需要通过 NPM 安装的，开发者需要在本地事先安装 Node 环境。这一点作为前端开发者可能习以为常，但是对于非前端开发人员或非程序员，运行你的脚本需要额外配置环境就会显得很突兀。</p><p>那 Node 有没有二进制打包方案呢？</p><p>答案是肯定的，紫升在调研中发现了<a href="https://github.com/oclif/oclif">oclif</a> 和 <a href="https://github.com/vercel/pkg">pkg</a>，两者都可以将开发好的程序进行跨平台打包。区别在于 pkg 只是一个简单地将程序打包成二进制可执行文件的工具，而 oclif 是一个完善的支持插件系统的 CLI 框架。</p><p>目前为止，oclif 看起来就是我们想要的，但是很遗憾，它由于将 Node 环境打包进安装程序造成安装包过大以及直接暴露源代码落选了（pkg 则是由于太过简陋的功能直接 pass）。</p><h3 id="Deno"><a href="#Deno" class="headerlink" title="Deno"></a>Deno</h3><p>紫升是一个 Deno 爱好者，<a href="https://juejin.cn/post/6854573220432248839">Deno从入门到跑路 | 🏆 技术专题第一期征文</a> 证明了我写过 Hello World。由于语法和 API 上都很接近 Node，所以成为了第二个考虑的方案。</p><p>Deno 在 1.7.0 加入了对 Windows 平台的编译支持后，完整支持了跨平台编译。<a href="https://github.com/youngjuning/seve">seve</a> 是我写的一个简单的 CLI Demo。</p><p>没有选择 Deno 的原因生态不够完善需要自己造的轮子有点多。比如没有成熟的跨平台编译、Flag 解析、问卷插件、彩色文字、loading、进度条插件。至于类似于 Node 的 Commander 和 cac 的 CLI 框架就更没有了。seve 就是我挖的一个大坑，本意是打造一款 Deno 版的 Commander，在接触 cac 之后改了主意。</p><h3 id="Go"><a href="#Go" class="headerlink" title="Go"></a>Go</h3><p>紫升也是一个 Go 的爱好者，同样的 <a href="https://juejin.cn/post/6860522117423857678">极速入门Go并爬取掘金专栏 | 🏆 技术专题第二期</a> 证明我写过 Hello World。之所以最后才考虑 Go，是因为作为前端开发者，Node 和 Deno 给我的心理压力很小或者几乎没有，而 Go 对我来说是既熟悉又陌生。如果不是前两者都无法符合要求，恐怕我是不会选择 Go 的。</p><p><strong>跨平台编译</strong></p><p>Go 本身就支持编译命令，下面便是使用 go 自带 <code>build</code> 命令编写的打包脚本：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">#</span><span class="language-bash">!/bin/bash</span></span><br><span class="line">name=&quot;tuya-panel-cli&quot;</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">Windows</span></span><br><span class="line">CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o $&#123;name&#125;.exe main.go</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">Linux</span></span><br><span class="line">CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o $&#123;name&#125; main.go</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">MacOS</span></span><br><span class="line">CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o $&#123;name&#125; main.go</span><br></pre></td></tr></table></figure><p>你可能会觉得以上脚本很繁琐，Go 也有第三方的编译插件可以使用，比如 <a href="https://github.com/mitchellh/gox">gox</a> 和 <a href="https://github.com/karalabe/xgo">xgo</a>。</p><p><strong>CLI 框架</strong></p><p><a href="https://github.com/urfave/cli">urfave</a> 比较简单易上手，如果是小型的或者单命令的程序推荐选择，我选择的是 <a href="https://github.com/spf13/cobra">cobra</a>。调研 Cobra 的过程中，顺手整理翻译了<a href="https://juejin.cn/post/6924541628031959047">Cobra 中文文档</a>，有需要的掘友可以直接白嫖。我们看下 Cobra 的特性：</p><ul><li><p>简单的基于子命令的 CLIs：<code>app server</code>、<code>app fetch</code> 等；</p></li><li><p>完全兼容 <a href="https://zh.wikipedia.org/wiki/%E5%8F%AF%E7%A7%BB%E6%A4%8D%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E6%8E%A5%E5%8F%A3">POSIX（可移植操作系统接口）</a> 的标志（包括短版和长版）</p></li><li><p>嵌套子命令</p></li><li><p>全局、局部和级联的标志</p></li><li><p>使用 <code>cobra init appname</code> 和 <code>cobra add cmdname</code> 轻松生成应用程序和命令</p></li><li><p>智能提示（<code>app srver</code> …did you mean <code>app server</code>）</p></li><li><p>自动生成命令和标志的帮助</p></li><li><p>自动识别 <code>-h</code>、<code>--help</code> 等帮助标识</p></li><li><p>自动为你的应用程序生成的 bash 自动完成</p></li><li><p>自动为你的应用程序生成 man 手册</p></li><li><p>命令别名，以便你可以更改内容而不会破坏它们</p></li><li><p>定义自己的帮助，用法等的灵活性。</p></li><li><p>可选与 <a href="https://github.com/spf13/viper">viper</a> 紧密集成，可用于 <a href="https://12factor.net/zh_cn/">12factor</a> 应用程序</p></li></ul><blockquote><p>从特性便可以看出来 Cobra 既可以为我们提供丰富的特性，又可以让我们的方案落地。</p></blockquote><h2 id="实践过程"><a href="#实践过程" class="headerlink" title="实践过程"></a>实践过程</h2><p>实践过程中遇到的问题，以及解决方式？<br>最后达成了什么效果，或者什么样的优化和提升？</p><p>技术选型之后就是落地实践，项目框架搭建掘友可直接阅读前文提到的中文文档，这里不再赘述。下面我将按照 tuya-panel-cli 实现的命令分别讲述实践过程。</p><h3 id="init-初始化模板"><a href="#init-初始化模板" class="headerlink" title="init 初始化模板"></a>init 初始化模板</h3><blockquote><p>需求：<a href="https://github.com/tuya/tuya-panel-demo">tuya-panel-demo</a> 项目包含了所有的模板，我们需要实现提示用户选择对应模板并初始化到当前目录</p></blockquote><p>1、发布模板：<a href="https://github.com/tuya/tuya-panel-demo/blob/master/.github/workflows/release.yml">GitHub CI</a> 监听到有新的 git tag 被 push 到仓库，则将所有模板打包并发布一个草稿版的 release，管理员手动编写发行说明后正式发布 release。这个过程和 Deno 本身的版本管理是一致的。关于这部分内容可以阅读<a href="https://juejin.cn/post/6926899307735957511">叮，一份 Deno GitHub Action 源码解析请查收</a>。</p><p>2、选择模板：第一个思路是维护一个 <code>txt</code> 文件，通过 http 获取内容，这有点蠢，每次有新的模板或删除模板都需要手动维护；第二个思路是通过爬虫爬取 <code>https://github.com/tuya/tuya-panel-demo/releases/latest</code> 并取出模板列表。我用的爬虫插件是 <a href="github.com/gocolly/colly">colly</a>，选择提示插件是 <a href="github.com/manifoldco/promptui">promptui</a>。</p><p>3、下载模板：我是使用 <code>net/http</code> 手撸了一个文件下载函数，然后使用 <a href="github.com/mholt/archiver">archiver</a> 对压缩包解压到缓存目录，最后使用 <a href="github.com/otiai10/copy">copy</a> 复制到当前目录。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ee160384538a4fba8882ccf08bed0cb0~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><h3 id="upgrade-升级命令"><a href="#upgrade-升级命令" class="headerlink" title="upgrade 升级命令"></a>upgrade 升级命令</h3><p><a href="https://juejin.cn/post/6924465443704930318">向 Deno 学习优秀的脚本管理</a> 讲述了如何管理我们的程序，<a href="https://juejin.cn/post/6925201316264443918">基于 Go 实现 Deno upgrade</a> 讲述了如何站在巨人的肩膀上实现了自己的 upgrade 命令。这里简要概述一下。</p><p>1、借鉴 Deno 的脚本管理方式，我们通过 github release 管理版本（Go 语言的插件管理也是这个套路）</p><p>2、阅读 Deno upgrade 命令源码并用 Go 实现（由此接触并喜欢上了 Rust）</p><p>3、在模仿的基础上实现了后台检查更新的功能</p><p>4、封装了 <a href="https://github.com/youngjuning/go-release">go-release</a> 插件并开源。</p><h3 id="package-打包"><a href="#package-打包" class="headerlink" title="package 打包"></a>package 打包</h3><p>这个命令是从内部项目迁移出来的，学到一个知识是文件顶部加 <code>// +build windows</code> 代表 windows 平台编译使用，<code>// +build linux darwin</code> 代表 linux 和 mac 下编译使用。</p><h3 id="homebrew"><a href="#homebrew" class="headerlink" title="homebrew"></a>homebrew</h3><p>为了进一步方便 Homebrew 用户，紫升将 tuya-panel-cli 发布到了 <a href="https://github.com/youngjuning/homebrew-tap">youngjuning&#x2F;homebrew-tap</a>，具体的内容请参考当时我写的文章<a href="https://juejin.cn/post/6922347045692899336">不会吧？不会吧！还有人不会发 Homebrew 包？</a></p><h2 id="总结思考"><a href="#总结思考" class="headerlink" title="总结思考"></a>总结思考</h2><ul><li>技术选型要实际写 demo，看上去合适的不一定真的合适</li><li>技术选型要考虑时间、生态，作为职业程序员不应该畏惧舒适区以外的世界</li><li>项目能够如期交付，一方面是 cobra 这个框架的强大和易用，二是由于先前储备的 Go 基础知识。不过这也没什么值得夸耀的，用到的语法都足够简单。</li><li>项目发布后，进行了文档的补充和提示的优化。暂未发现使用上的问题。</li><li>暂未和 viper 联动，后续有需求可以加上。</li><li>未来需要系统的学习 Go 语言，以便更好地优化项目。</li></ul><blockquote><p>最最重要的一点感悟是，一点要及时记录自己的学习和工作情况，就像记账一样。</p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/e238033719e7/</id>
    <link href="https://blog.zisheng.pro/posts/e238033719e7/"/>
    <published>2023-04-19T09:46:01.000Z</published>
    <summary>项目基于流行的 Go CLI 框架 Cobra 项目开发，基本按预期实现了需求和设计目标，为之后进一步完善开发者生态找到了抓手，为形成开发闭环添加了重要一环（皮一下）。</summary>
    <title>从零使用 Go 语言构建 CLI 项目</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/categories/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="掘金翻译计划" scheme="https://blog.zisheng.pro/categories/%E6%8E%98%E9%87%91%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="Python" scheme="https://blog.zisheng.pro/tags/Python/"/>
    <category term="JSON" scheme="https://blog.zisheng.pro/tags/JSON/"/>
    <category term="CPython" scheme="https://blog.zisheng.pro/tags/CPython/"/>
    <category term="simplejson" scheme="https://blog.zisheng.pro/tags/simplejson/"/>
    <category term="ujson" scheme="https://blog.zisheng.pro/tags/ujson/"/>
    <category term="pysimdjson" scheme="https://blog.zisheng.pro/tags/pysimdjson/"/>
    <category term="python-rapidjson" scheme="https://blog.zisheng.pro/tags/python-rapidjson/"/>
    <category term="orjson" scheme="https://blog.zisheng.pro/tags/orjson/"/>
    <content>
      <![CDATA[<blockquote><ul><li>原文地址：<a href="https://levelup.gitconnected.com/json-encoding-decoding-with-python-62a2cae63a6a">JSON encoding&#x2F;decoding with Python</a></li><li>原文作者：<a href="https://medium.com/@martinthoma">Martin Thoma</a></li><li>译文出自：<a href="https://github.com/xitu/gold-miner">掘金翻译计划</a></li><li>本文永久链接：<a href="https://github.com/xitu/gold-miner/blob/master/article/2021/json-encoding-decoding-with-python.md">https://github.com/xitu/gold-miner/blob/master/article/2021/json-encoding-decoding-with-python.md</a></li><li>译者：<a href="https://github.com/youngjuning">紫升</a>、<a href="https://github.com/a651092254">雪刺</a></li><li>校对者：<a href="https://github.com/husiyu">Zoe</a></li></ul></blockquote><p>REST API 是 在全世界使用标准化的消息格式。JSON 是互联网上数据交换的基石，作为 JavaScript 的一个子集，它从一开始就获得了巨大的推广。它特别清晰易读的语法也有利于推广。</p><p>据我所知各种语言都有 JSON 库用于序列化和反序列化。实际上在 Python 中就有很多种 JSON 库。在下文中，我将为大家比较它们的异同。</p><h2 id="引用库"><a href="#引用库" class="headerlink" title="引用库"></a>引用库</h2><p><strong>CPython</strong> 本身具有一个 <a href="https://docs.python.org/3/library/json.html">json</a> 模块。它最初是由 Bob Ippolito 作为 simplejson 开发的，并被合并到 Python 2.4 中（<a href="https://docs.python.org/3/whatsnew/2.6.html#the-json-module-javascript-object-notation">源代码</a>）。CPython 遵循 Python 软件基金会（Python Software Foundation）许可协议。</p><p><strong>simplejson</strong> 仍然作为一个单独的库存在，你可以通过 pip 安装它。它是带有可选 C 扩展的纯 Python 库。 Simplejson 遵循 MIT 和 Academic Free License（AFL）许可协议。</p><p><a href="https://pypi.org/project/ujson/">ujson</a> 是对 C 语言库 Ultra JSON 的绑定。<a href="https://github.com/ultrajson/ultrajson">Ultra JSON</a> 由 ESN（<a href="https://techcrunch.com/2012/09/26/electronic-arts-buys-online-gaming-development-studio-esn/">一家电子艺术工作室公司</a>）开发，并获得了 <a href="https://tldrlegal.com/license/bsd-3-clause-license-(revised)">3条款BSD许可</a>。 Ultra JSON 在 Github 上拥有 3k 星，305 个 fork，50 个贡献者，最近一次提交的日期只有 12 天，而最后一次提交是在 5 天之前发布的。我听说它处于“维护模式”（<a href="https://github.com/ultrajson/ultrajson/issues/428#issuecomment-699456053">来源</a>），表明没有新的进展。</p><p><strong>pysimdjson</strong> 是对 C ++ 库 <a href="https://github.com/simdjson/simdjson">simdjson</a> 的绑定。 SIMDjson 从加拿大获得资助。simdjson 在 Github 上有 12.2k 颗星，611 个分支，63 个贡献者，最后一次提交是 11 小时前，而最后一个 issue 是 2 小时前创建的。</p><p><strong>python-rapidjson</strong> 是对 C ++ 库 <a href="https://github.com/Tencent/rapidjson">RapidJSON</a> 的绑定。 RapidJSON 由 <a href="https://en.wikipedia.org/wiki/Tencent">腾讯</a> 开发。 RapidJSON 在 GitHub 上有 9.8k 个星，2.7k 个 fork，150 个贡献者，最近一次提交大约在 2 个月前，而最后一个 issue 是 17 天前创建的。</p><p><a href="https://pypi.org/project/orjson/">orjson</a> 是一个 Python 软件包，依靠 Rust 来完成繁重的工作。</p><h2 id="成熟度和操作安全性"><a href="#成熟度和操作安全性" class="headerlink" title="成熟度和操作安全性"></a>成熟度和操作安全性</h2><p>上面所有提到的库都可以毫无问题地用作 benchmark 示例，切换 JSON 模块也不是什么大问题，但我仍然想确定相关模块是否支持。</p><p>CPython，simplejson，ujson 和 orjson 都认为他们自己已经可以投产了。</p><p>python-rapidjson 将自身标记为 alpha，但是一位维护人员说这是一个错误，并将很快得到修复（<a href="https://github.com/python-rapidjson/python-rapidjson/issues/140#issuecomment-699475354">资源</a>）。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7c990d034af342a8ba240e887ae86270~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><p>判断一个库的问题是否能够被顺利解决，一个直接的方式是直接去它的仓库创建 issue，并观察后续的跟进反馈：</p><ul><li><a href="https://github.com/simplejson/simplejson/issues/267">SimpleJSON</a>：第二天我得到了答复，回答很明确，易于理解，友善。 <a href="https://github.com/xitu/gold-miner/blob/master/article/2021/undefined">Bob Ippolito</a> 回答了我。他是最初开发这个库的人，并且在 JSON 模块的 Python 文档中也提到了他！</li><li><a href="https://github.com/ultrajson/ultrajson/issues/428">uJSON</a>：30分钟内，我得到了一个清晰，友好，易于遵循的答案。 @hugovank</li><li><a href="https://github.com/ijl/orjson/issues/127">ORJSON</a>：10天没有反应，然后关闭，没有任何评论。</li><li><a href="https://github.com/TkTech/pysimdjson/issues/54">[PySIMDJSON]</a>：15天后无人答复。</li><li><a href="https://github.com/python-rapidjson/python-rapidjson/issues/140">Python-RapidJSON</a>：在30分钟内，我得到了一个清晰，友好，易于遵循的答案。十天后合并了一个<a href="https://github.com/python-rapidjson/python-rapidjson/pull/143">简单的PR</a>。</li></ul><p>通过以上操作我得出一个答案，它们基本上没有相互关系。</p><h2 id="基准测试（Benchmark"><a href="#基准测试（Benchmark" class="headerlink" title="基准测试（Benchmark)"></a>基准测试（Benchmark)</h2><p>为了正确地对不同的库进行基准测试，我考虑了以下情况：</p><ul><li><strong>API</strong>：交换信息的 Web 服务。它可能包含 Unicode 并具有嵌套结构。 Twitter API 的 JSON 文件听起来不错，可以对此进行测试。</li><li><strong>API JSON错误</strong>：我很好奇如果 JSON API 格式有错误，性能会如何变化。因此，我在中间删除了一个大括号。</li><li><strong>GeoJSON</strong>：我首先通过一个开源街道地图导出器 <a href="https://overpass-turbo.eu/">Overpass Turbo</a> 得到了 <a href="https://en.wikipedia.org/wiki/GeoJSON">GeoJSON</a> 格式的 JSON 文件。你将获得疯狂多的 JSON 文件，这些文件大多具有坐标，而且还很嵌套。</li><li><strong>机器学习</strong>：只是大量的浮点数列表。这些可能是神经网络层的权重。</li><li><strong>JSON行</strong>：结构化日志在行业中大量使用。如果分析这些日志，可能需要遍历千兆字节的数据。它们都是带有日期时间对象、消息、记录器、日志状态等信息的简单字集。</li></ul><h4 id="反序列化速度"><a href="#反序列化速度" class="headerlink" title="反序列化速度"></a>反序列化速度</h4><p>我将我的硬盘驱动器的读取速度设置了一个较低的上限，在以下3个图表中将以它作为基准。</p><p><img src="https://i.loli.net/2021/03/26/ypSPNaJseM24WZL.png" alt="紫升"></p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e8f138e2304c45c886374ac04f8fff52~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a681aacfe9e34a6a9fbe808f1b9a8c94~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fa658530b4224ef5909984ad9a818632~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ec272cb068f54f67b2233426d83e29fd~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>由此得出的结论是：</p><ul><li>Rapidjson 速度很慢，但是对于像 twitter.json 这样的小型 JSON，你不会注意到有什么不同。可以通过结构化日志看到这一点。</li><li>simdjson，orjson 和 ujson 都快得惊人。</li><li>对于大多数库而言，读取包含结构错误的 JSON 文件的速度相同。一个值得注意的例外是 Rapidjson。我猜一旦发现错误，它将中止读取文件。</li></ul><h4 id="序列化速度"><a href="#序列化速度" class="headerlink" title="序列化速度"></a>序列化速度</h4><p>在这里，我预先创建了 JSON 字符串,并以写入磁盘的时间作为基线测量了所需的时间。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a6c6caa82b8b454caf79534eacc0f4f3~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5e3665d926b5418eaa07f45893f5bb74~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8873d330493747f7906649e5af6c6c6f~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fcc5c29d33cd43939ec6d8ae860f26ee~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>我由此得出的结论是：</p><ul><li>orjson 非常快，超级接近我的硬盘驱动器写入速度。 ujson 也非常接近。</li><li>Rapidjson 也很快，但与 orjson 或 ujson 不在同一级别。</li><li>simdjson 很慢。</li></ul><h2 id="专业的-JSON-工作流"><a href="#专业的-JSON-工作流" class="headerlink" title="专业的 JSON 工作流"></a>专业的 JSON 工作流</h2><p>最后总结一下，我想指出一些我之前看到并记录下来的问题：</p><ul><li><p>调用变量 <code>foo_json</code>：JSON 是一种字符串格式。如果不是字符串，则不是 JSON。<br>如果使用 <code>bar = json.loads(foo)</code> 反序列化 JSON，则 bar 不是 JSON。</p><p>你可以将 bar 序列化为与 JSON <code>foo</code> 等效的 JSON，但 bar 不是 JSON,这是一个 Python 对象，很像一个字典对象，就将它当作 <code>foo_json</code>。</p></li><li><p>属性会在各处进行检查：如果你收到 JSON 数据，很轻松就可以转换为 Python 对象（例如字典）并使用它。这对于概念验证代码或很小的 JSON 字符串来说是很好的选择。如果你不将其转换为 <a href="https://docs.python.org/3/library/dataclasses.html">dataclass</a> 之类的，它将一团糟。</p></li></ul><p><a href="https://github.com/samuelcolvin/pydantic">pydantic</a> 是一个超级有用的验证库。你可以使用自己喜欢的 JSON 库将 JSON 字符串解析为带有 字典&#x2F;列表&#x2F;字符串&#x2F;数字&#x2F;布尔值的 Python 基本表示形式，然后再使用 Pydantic 对其进行解析。这样做的好处是你知道以后要处理的内容。不再只是将 <code>Dict[str, Any]</code> 用作 <a href="https://medium.com/analytics-vidhya/type-annotations-in-python-3-8-3b401384403d">type annotation</a>,不再用没用的的编辑器自动完成，不再检查属性是否在整个代码中都存在。</p><p>要引入除默认 <code>json</code> 以外的其他 json 包，我建议使用此模式</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> ujson <span class="keyword">as</span> json</span><br></pre></td></tr></table></figure><p>对于 Flask，你可以使用其他 <a href="https://flask.palletsprojects.com/en/1.1.x/api/#flask.json.JSONEncoder">编码器</a>&#x2F;<a href="https://flask.palletsprojects.com/en/1.1.x/api/#flask.json.JSONDecoder">解码器</a>，如下所示：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> simplejson <span class="keyword">import</span> JSONEncoder, JSONDecoder</span><br><span class="line"></span><br><span class="line">app.json_encoder = JSONEncoder</span><br><span class="line">app.json_decoder = JSONDecoder</span><br></pre></td></tr></table></figure><h2 id="还可以看看"><a href="#还可以看看" class="headerlink" title="还可以看看"></a>还可以看看</h2><ul><li><a href="https://github.com/xitu/gold-miner/blob/master/article/2021/undefined">Daniel Lemire</a>: <a href="https://www.youtube.com/watch?v=wlvKAT7SZIQ">Parsing JSON Really Quickly: Lessons Learned</a></li><li><a href="https://github.com/xitu/gold-miner/blob/master/article/2021/undefined">Ng Wai Foong</a>: <a href="https://levelup.gitconnected.com/introduction-to-orjson-3d06dde79208">Introduction to orjson</a></li><li><a href="https://github.com/xitu/gold-miner/blob/master/article/2021/undefined">Nicolas Seriot</a>: <a href="http://seriot.ch/parsing_json.php">Parsing JSON is a Minefield</a></li></ul><blockquote><p>如果发现译文存在错误或其他需要改进的地方，欢迎到 <a href="https://github.com/xitu/gold-miner">掘金翻译计划</a> 对译文进行修改并 PR，也可获得相应奖励积分。文章开头的 <strong>本文永久链接</strong> 即为本文在 GitHub 上的 MarkDown 链接。</p></blockquote><hr><blockquote><p><a href="https://github.com/xitu/gold-miner">掘金翻译计划</a> 是一个翻译优质互联网技术文章的社区，文章来源为 <a href="https://juejin.im/">掘金</a> 上的英文分享文章。内容覆盖 <a href="https://github.com/xitu/gold-miner#android">Android</a>、<a href="https://github.com/xitu/gold-miner#ios">iOS</a>、<a href="https://github.com/xitu/gold-miner#%E5%89%8D%E7%AB%AF">前端</a>、<a href="https://github.com/xitu/gold-miner#%E5%90%8E%E7%AB%AF">后端</a>、<a href="https://github.com/xitu/gold-miner#%E5%8C%BA%E5%9D%97%E9%93%BE">区块链</a>、<a href="https://github.com/xitu/gold-miner#%E4%BA%A7%E5%93%81">产品</a>、<a href="https://github.com/xitu/gold-miner#%E8%AE%BE%E8%AE%A1">设计</a>、<a href="https://github.com/xitu/gold-miner#%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD">人工智能</a>等领域，想要查看更多优质译文请持续关注 <a href="https://github.com/xitu/gold-miner">掘金翻译计划</a>、<a href="http://weibo.com/juejinfanyi">官方微博</a>、<a href="https://zhuanlan.zhihu.com/juejinfanyi">知乎专栏</a>。</p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/802686c84f7e/</id>
    <link href="https://blog.zisheng.pro/posts/802686c84f7e/"/>
    <published>2023-04-19T09:38:34.000Z</published>
    <summary>REST API 是 在全世界使用标准化的消息格式。JSON 是互联网上数据交换的基石，作为 JavaScript 的一个子集，它从一开始就获得了巨大的推广。它特别清晰易读的语法也有利于推广。</summary>
    <title>Python 中的 JSON 库：按速度、成熟度和操作安全性进行比较</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="漫谈" scheme="https://blog.zisheng.pro/categories/%E6%BC%AB%E8%B0%88/"/>
    <category term="紫升" scheme="https://blog.zisheng.pro/tags/%E7%B4%AB%E5%8D%87/"/>
    <category term="跨端" scheme="https://blog.zisheng.pro/tags/%E8%B7%A8%E7%AB%AF/"/>
    <category term="掘金" scheme="https://blog.zisheng.pro/tags/%E6%8E%98%E9%87%91/"/>
    <category term="宗心" scheme="https://blog.zisheng.pro/tags/%E5%AE%97%E5%BF%83/"/>
    <content>
      <![CDATA[<h2 id="感谢掘金-Zoe"><a href="#感谢掘金-Zoe" class="headerlink" title="感谢掘金&amp;Zoe"></a>感谢掘金&amp;Zoe</h2><p>首先感谢掘金这个帮助开发者成长的社区，从 2020 年开始写博客至今，不仅沉淀（水）了近 80 篇博客，还完成了职业生涯的小突破。最重要的在优弧大大发掘我之后，借由掘金这个平台认识了很多好朋友和大佬。其中大圣和言哥对我的影响最多，在我多次迷茫或者瞎搞的时候给出建议或者直接敲打我。</p><p>话说回此次由 Zoe 同学主办的 <a href="https://juejin.cn/post/6933147477399109640">3 月闯关活动开启，等你来战！</a> 活动，一开始我是拒绝的，毕竟我没有换工作的打算。Zoe 同学告诉我不求职，总是有升职加薪的追求的吧，顺手就把 <a href="https://juejin.cn/post/6933147477399109640#heading-7">闯关路线二：升职加薪</a> 推荐给我，甚至拿出我欠优弧文章的事情来压我。嗯！我就是在 Zoe 同学不断约稿和不断催稿的情况下完成了闯关活动，直到活动快结束的时候我才知道还有导师职业规划指导的环节(p≧w≦q)。向我们勤劳又负责的 Zoe 同学致敬，您辛苦啦！以前是我错了，现在我想做个好人。</p><h2 id="活动历程"><a href="#活动历程" class="headerlink" title="活动历程"></a>活动历程</h2><p><strong>技术点评</strong></p><ul><li><a href="https://juejin.cn/post/6937983077209604127">使用 Jest 和 Enzyme 进行 React Native 单元测试|技术点评</a></li><li><a href="https://juejin.cn/post/6937124684030967845">「译」2021 Flutter 和 React Native 如何选|技术点评</a></li><li><a href="https://juejin.cn/post/6936391941806358535">当我们在聊 RN 时，我们在聊什么 | 技术点评</a></li></ul><p>其中第三篇获得了 <a href="https://juejin.cn/post/6939854159831105549#heading-8">优秀文章奖</a>，奖品是掘金周边大礼包！</p><p><strong>项目复盘</strong></p><ul><li><a href="https://juejin.cn/post/6940296964089053221">从零使用 Go 语言构建 CLI 项目 | 项目复盘</a></li></ul><p>由于在临安买房的事耽搁，这个阶段只输出了一篇文章，很荣幸再次获得 <a href="https://juejin.cn/post/6942744127461130277#heading-2">优秀项目复盘文章奖</a>，奖品还是掘金周边大礼包！我对象说我都可以卖掘金周边了，看我这三个缸子，一个泡茶，一个泡咖啡，一个白开水，还有两个在家躺尸：</p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f0033f65290149cf947e41a6735fd914~tplv-k3u1fbpfcp-watermark.image" alt="WechatIMG101.jpeg" style="zoom:33%;" /><p><strong>成长计划</strong></p><ul><li><a href="https://juejin.cn/pin/6942466001271259172">在跨端领域继续深耕，一年内达到公司技术专家级别</a></li><li><a href="https://juejin.cn/pin/6942686885214945288">目前处于广泛涉猎技术，构建个人知识和技能体系的阶段。垂直领域的精深和知识体系的架…</a></li><li><a href="https://juejin.cn/pin/6943014460898410504">不断调整优化自己的目标，并严格要求自己：#网页链接#。虚心听取别人的建议和批评。</a></li><li><a href="https://juejin.cn/pin/6943271666751373326">肖老师，经验丰富，一个字稳</a></li><li><a href="https://juejin.cn/pin/6943864731392278542">不管什么事，我都有整理和记录的习惯。所以我经手的东西都有详细文档。离职的时候，领…</a></li></ul><p>肖老师是谁？肖老师是我老东家一起奋斗过的兄弟，一起写过代码，一起泡过澡，现在是晞格玛云技术总监，有合适的小伙伴我可以帮忙投递简历。</p><h2 id="感谢宗心大佬"><a href="#感谢宗心大佬" class="headerlink" title="感谢宗心大佬"></a>感谢宗心大佬</h2><p>当我知道还有导师指导职业规划这个事之后，我就拜托 Zoe 同学帮忙联系宗心大佬选我，一是很早就关注了闲鱼在 Flutter 方面的实践，二是宗心大佬带领的团队正是我所从事的跨端方向（具体聊了之后发现跨端只是闲鱼的一部分，后面会展开说）。感谢宗心大佬给了我这次机会，下面是宗心大佬的简介：</p><p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4ce993a4e5634984bc746356585ea242~tplv-k3u1fbpfcp-watermark.image" alt="紫升"></p><h2 id="跨端问与答"><a href="#跨端问与答" class="headerlink" title="跨端问与答"></a>跨端问与答</h2><p>问题是我提前准备的，都是现阶段令我焦虑和困惑的点。宗心大佬人真的很随和很 nice，也极其耐心地针对我的问题给出了答疑，真的就是拨云见日，令我茅塞顿开（此处非玩笑话）。可惜的是我这个憨憨虽然录了屏，但是没有声音，记忆它不完整，也不能完全复述大佬的意思！希望大家见谅，放上视频合照以纪念此次指导：</p><table><thead><tr><th>大佬侧颜</th><th>大佬正脸</th></tr></thead><tbody><tr><td><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6bce94fe51be45b68415f42ea977ee78~tplv-k3u1fbpfcp-watermark.image" style="zoom:33%;" /></td><td><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7243bd2796204aafb62a75d59d62ee31~tplv-k3u1fbpfcp-watermark.image" style="zoom:33%;" /></td></tr></tbody></table><h3 id="Flutter-真的会干掉-React-Native-吗？"><a href="#Flutter-真的会干掉-React-Native-吗？" class="headerlink" title="Flutter 真的会干掉 React Native 吗？"></a>Flutter 真的会干掉 React Native 吗？</h3><h4 id="问题背景"><a href="#问题背景" class="headerlink" title="问题背景"></a>问题背景</h4><p>前一段 Flutter 2.0 出来的时候，一片唱衰 React Native，Flutter 完爆 RN 的声音让我很焦虑。在我的认知里，这两者并不存在你死我活的问题呀，而且早起两个同队都给出声明说不存在直接竞争关系，那么作为国内最早且使用 Flutter 最多的团队，宗心大佬是怎么思考这两者的呢？</p><h4 id="大佬解答"><a href="#大佬解答" class="headerlink" title="大佬解答"></a>大佬解答</h4><p>Flutter 和 React Native 所适用的业务场景是不一样的，Flutter 更多地可以替代原生开发作为 App 开发的新模式，闲鱼目前的基础链路就在用 Flutter 做归一的架构。而 React Native 可以做动态化的事情，比如手淘的活动引导页这类需要高度动态化的场景。</p><p>React Native 更多的是让前端可以写客户端，对前端更友好。而 Flutter 偏向于原生，对原生同学比较友好。</p><h4 id="我的感悟"><a href="#我的感悟" class="headerlink" title="我的感悟"></a>我的感悟</h4><p>技术存在即合理，站在宗心大佬的高度，一切技术的选型和架构一定是跟随业务场景走的。脱离业务去谈技术和架构，就是流氓行为，不负责任的，纯属制造焦虑的。</p><h3 id="国内有没有可能出现比肩-Flutter-和-React-Native-的框架？"><a href="#国内有没有可能出现比肩-Flutter-和-React-Native-的框架？" class="headerlink" title="国内有没有可能出现比肩 Flutter 和 React Native 的框架？"></a>国内有没有可能出现比肩 Flutter 和 React Native 的框架？</h3><h4 id="问题背景-1"><a href="#问题背景-1" class="headerlink" title="问题背景"></a>问题背景</h4><p>作为国内一线大厂，阿里是否有野心做出比肩甚至超越 Flutter 和 React Native 的框架呢?</p><h4 id="大佬解答-1"><a href="#大佬解答-1" class="headerlink" title="大佬解答"></a>大佬解答</h4><p>大佬对 Flutter 和 React Native 的理解很透彻，把问题一分为二。首先是 React Native，列举了 Weex 这个框架，RN的架构比较好实现。而 Flutter 的实现就是一个庞大的工程，需要对操作系统和浏览器引擎有深厚研究的团队才有希望做出。大佬列举了 UC、鸿蒙这类公司。</p><h4 id="我的感悟-1"><a href="#我的感悟-1" class="headerlink" title="我的感悟"></a>我的感悟</h4><p>看似是单一的框架，但背后是整个生态。难实现的不是框架，而是大环境和生态的建设。相信国家的精英们迟早会有属于我们自己的操作系统，自己的生态，自己的跨端框架。</p><h3 id="闲鱼是否放弃-Flutter？"><a href="#闲鱼是否放弃-Flutter？" class="headerlink" title="闲鱼是否放弃 Flutter？"></a>闲鱼是否放弃 Flutter？</h3><h4 id="问题背景-2"><a href="#问题背景-2" class="headerlink" title="问题背景"></a>问题背景</h4><p>因为上份工作是有机会在阿里园区呆过，听闻 Weex 已经被放弃了，所以问了一下宗心大佬进展。由于不是一个团队，大佬不方便代表 Weex 团队，但是对大家比较关心的闲鱼是否放弃 Flutter 做出了解答。</p><h4 id="大佬解答-2"><a href="#大佬解答-2" class="headerlink" title="大佬解答"></a>大佬解答</h4><p>前面我们提到，宗心大佬是闲鱼基础链路负责人，大佬也解答了 Flutter 的应用场景是可以替代原生开发的一些事情。那么闲鱼是否会放弃 Flutter 呢？答案是否定的，宗心大佬描述了闲鱼基础链路架构的方向：Flutter 作为基础链路的核心，辅以原生、H5 等容器，最终做到基础链路的归一。也就是说闲鱼并不是要放弃 Flutter，而是基于 Flutter 在做更大更深的事情。</p><p>目前跨端处于比较混乱的局面，RN 容器、H5 容器、原生壳、各厂封闭生态的小程序以及 Flutter 共存于一个 App 的情况屡见不鲜。这带来的不只是管理成本，还有性能问题。此时急需一个能够统一基础链路的技术，闲鱼基于多年的经验选择了 Flutter。</p><p>宗心大佬也透露了内部十分活跃的 Flutter 开源团队建设，相信不久就会为 Flutter 生态输入优质的开源项目。希望彼时在座的各位有机会能参与进去。</p><h4 id="我的感悟-2"><a href="#我的感悟-2" class="headerlink" title="我的感悟"></a>我的感悟</h4><p>其实，在涂鸦也有相应的小组在做同样的事情，在我们的沟通里叫一码多端。也是基于 Flutter 实现一套代码多端运行。这在跨端领域可以说是炙手可热的话题了。</p><h3 id="在跨端领域达到专家水平需要具备哪些指标？"><a href="#在跨端领域达到专家水平需要具备哪些指标？" class="headerlink" title="在跨端领域达到专家水平需要具备哪些指标？"></a>在跨端领域达到专家水平需要具备哪些指标？</h3><h4 id="问题背景-3"><a href="#问题背景-3" class="headerlink" title="问题背景"></a>问题背景</h4><p>嗯，技术专家是紫升目前努力的方向，也制定了一系列的目标和计划。但是我并不知道这些目标和计划是否正确！</p><h4 id="大佬解答-3"><a href="#大佬解答-3" class="headerlink" title="大佬解答"></a>大佬解答</h4><p>大佬将专家分为技术向专家和业务向专家。技术向专家比较少见，需要对某一个领域或某一个问题有很专精的研究，比如 Flutter 渲染引擎的优化、音视频方向的编码研究等等。而更常见的是业务向专家，这里的业务并不是具体的 ERP 或者电商。而是要有产品意识，需要在缺少产品的紧急情况下能顶上去的能力。</p><p>到 P7 有的是解决了行业解决不了的问题；有的是首创了一个架构并可以长期沿用；有的是在原有架构上做了创新性工作；有的是在某一个技术领域达到专家级别；有的是对某一类业务问题的技术实现非常精通。有的是凭借自身对技术整体把控。</p><p>大佬也表示了对方法论的肯定，建议我不要太过于执着于技术追求。以自己的经验告戒我还年轻，早些读一读经管类、管理类、心理类方面的书籍开阔思路，为以后的发展做准备。</p><p>宗心大佬最后还是劝诫我不要只在意技术方面的进步，能走的更远需要的不只是你的技术能力，更多的是综合能力，甚至是平衡生活和家庭的能力。</p><h4 id="我的感悟-3"><a href="#我的感悟-3" class="headerlink" title="我的感悟"></a>我的感悟</h4><p>每个人选择和适合的是不一样的，这受限于个人学识、经历、经验、精力以及家庭因素。我们无法通过简单地模仿他人达到目的。我们要做的是学习大佬的习惯、学习方法、对于技术框架的架构以及技术的前瞻性。</p><h3 id="您对于全栈开发的理解是什么样呢？"><a href="#您对于全栈开发的理解是什么样呢？" class="headerlink" title="您对于全栈开发的理解是什么样呢？"></a>您对于全栈开发的理解是什么样呢？</h3><h4 id="问题背景-4"><a href="#问题背景-4" class="headerlink" title="问题背景"></a>问题背景</h4><p>去年年底由于焦虑和迷茫，疯狂入手各种后端语言，其中包括 Deno、Go、甚至 Rust。大圣老师曾告诫我多看看前端框架源码，我却在广度上疯狂拓展。直到把自己搞的很疲惫，达到极限才开始反思我所认为的全栈是否是一个错误的概念。</p><h4 id="大佬解答-4"><a href="#大佬解答-4" class="headerlink" title="大佬解答"></a>大佬解答</h4><p>每个人的职业发展道路是不一样的，每个人的立身之本也不一样。紫升你思考一下你作为一个前端真的需要精通后端吗？你需要的不是精通那么多后端细枝末节，而是培养自己解决问题的通用能力。你精通了前端的加载优化、分包、异步编程、性能优化、引擎运行原理与调优等等这些深度问题。同样的经验放在后端是一样通用的。</p><p>大佬还以自己作为客户端起家最后能带领服务端的经验教导我，紫升你有一天能带服务端的时候，不是说你就要后端代码写的很溜。但是你要有大局观，遇到问题知道问题是什么还不够，还需要知道解决的方向以及安排可以解决这个问题的人去做。</p><p>我问大佬这些能力我不实际去学习如何获取？大佬说 Hello World 对你来说不难吧？整体的对一个技术的了解不难吧。在你不需要实际在工作中用到时这些就够了。你要做的首先还是把你安身立命的东西做到足够好，前后端只是职责划分，工程化和底层的本质是一样的。</p><p>现在云原生技术发展的这么迅速，作为前端，你也可以更多地关注云开发。这些也能让你自己做出一个项目出来。没必要说精通后端自己接口这样子。</p><h4 id="我的感悟-4"><a href="#我的感悟-4" class="headerlink" title="我的感悟"></a>我的感悟</h4><p>宗心大佬一番话点醒了梦中人，我的计划中放入过多的后端技能学习。这样并没有让我缓解焦虑，反倒是越来越焦虑。虽然还是很不舍，但我不得不逼自己暂时放弃 Deno、Rust 的学习。之所以要坚持 Go 的学习是因为我需要一个编译语言来解 JS 的毒，而 Go 足够简单不会对我造成太大的负担。</p><h3 id="我是在大前端方向突破还是专精跨端领域呢？"><a href="#我是在大前端方向突破还是专精跨端领域呢？" class="headerlink" title="我是在大前端方向突破还是专精跨端领域呢？"></a>我是在大前端方向突破还是专精跨端领域呢？</h3><h4 id="问题背景-5"><a href="#问题背景-5" class="headerlink" title="问题背景"></a>问题背景</h4><p>我从 18 年开始做 React Native 开发，积淀了很多业务开发知识和架构工程化方面的能力。一直在我心里过不去的坎是原生开发能力的欠缺。我很迷惑我到底是往大前端方向努力，还是闷头在跨端领域深挖。我指的不是 RN 和 Flutter，我指的是要不要把原生开发学一学。</p><h4 id="大佬解答-5"><a href="#大佬解答-5" class="headerlink" title="大佬解答"></a>大佬解答</h4><p>我建议你去学一下 iOS 和 安卓开发，起 Demo 玩一玩知道怎么运行的，如何开发的。大佬 12 年进阿里实习的时候也是不懂客户端开发的，当年资料更少。但是也就用一两周上手开始做项目。</p><p>但是宗心大佬还是告诫我一定要知道自己安身立命的东西是什么，不要忘记初心。不管是就业形势还是职业发展我更希望你能做一个懂客户端的大前端，而不是放弃前端往客户端突破。</p><h4 id="我的感悟-5"><a href="#我的感悟-5" class="headerlink" title="我的感悟"></a>我的感悟</h4><p>这和上一个全栈的问题类似，也是导致我越来越焦虑的问题之一。听了大佬的教导，我更加坚定了接下来的道路。原生开发我是要懂，而不是要什么都会。跨端和客户端还是不一样的。我的优势是在前端的积累，为什么要跟客户端转前端的同学抢饭碗呢？</p><h2 id="感悟"><a href="#感悟" class="headerlink" title="感悟"></a>感悟</h2><p>聊完之后我最大的感受是掘金的活动质量真的太高了，之前对宗心大佬不够了解，这一番聊下来，简直就是偶像级别的。以上所有的问题在没有聊之前几乎都没有自己的答案的，全部是迷茫的点。大佬不仅耐心的解惑，还以自身的实际经历为我的职业发展给出了清晰和明确的建议。再次感谢掘金和宗心大佬。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/c3f1c12d5d33/</id>
    <link href="https://blog.zisheng.pro/posts/c3f1c12d5d33/"/>
    <published>2023-04-19T09:34:00.000Z</published>
    <summary>首先感谢掘金这个帮助开发者成长的社区，从 2020 年开始写博客至今，不仅沉淀（水）了近 80 篇博客，还完成了职业生涯的小突破。最重要的在优弧大大发掘我之后，借由掘金这个平台认识了很多好朋友和大佬。其中大圣和言哥对我的影响最多，在我多次迷茫或者瞎搞的时候给出建议或者直接敲打我。</summary>
    <title>闲鱼宗心大佬聊跨端职业发展</title>
    <updated>2026-07-12T09:21:36.911Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/categories/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="掘金翻译计划" scheme="https://blog.zisheng.pro/categories/%E6%8E%98%E9%87%91%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/tags/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="掘金翻译计划" scheme="https://blog.zisheng.pro/tags/%E6%8E%98%E9%87%91%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="微软" scheme="https://blog.zisheng.pro/tags/%E5%BE%AE%E8%BD%AF/"/>
    <category term="谷歌" scheme="https://blog.zisheng.pro/tags/%E8%B0%B7%E6%AD%8C/"/>
    <content>
      <![CDATA[<blockquote><ul><li>原文地址：<a href="https://www.theverge.com/2021/3/12/22327306/google-microsoft-attack-open-web-online-news-australia-laws">Google slams Microsoft for trying ‘to break the way the open web works’</a></li><li>原文作者：Tom Warren</li><li>译文出自：<a href="https://github.com/xitu/gold-miner">掘金翻译计划</a></li><li>本文永久链接：<a href="https://github.com/xitu/gold-miner/blob/master/article/2021/google-microsoft-attack-open-web-online-news-australia-laws.md">https://github.com/xitu/gold-miner/blob/master/article/2021/google-microsoft-attack-open-web-online-news-australia-laws.md</a></li><li>译者：<a href="https://github.com/youngjuning">紫升</a></li><li>校对者：<a href="https://github.com/zenblo">zenblo</a></li></ul></blockquote><p>谷歌和微软工程师可能会在 Chromium 浏览器项目开发上进行合作，但这并没有消除双方之间的企业政治斗争。今天谷歌对微软发起了严厉的攻击，指责它试图 “打破开放 web 的运作方式，以此来削弱竞争对手”。</p><p>谷歌对微软破坏其支持新闻业和出版商努力的攻击感到不满。今年一月，谷歌威胁要将其搜索引擎从澳大利亚移走以回应一项法律，该法律一旦实行将迫使谷歌为新闻出版商的内容付费。就在谷歌屈服并与新闻集团和其他出版商达成协议，确保其服务继续在澳大利亚提供之后几天，澳大利亚 <a href="https://www.theverge.com/2021/2/24/22283777/australia-new-media-bargaining-code-facebook-google-paying-news">2 月通过了这项法律</a>。另一方面，Facebook 确实取消了用户和出版商在澳内分享新闻内容的功能，这 <a href="https://www.theverge.com/2021/2/23/22296778/facebook-ends-australian-news-ban-bargaining-code-google">赢得了澳大利亚政府的一些让步</a>。</p><p>在这中间，<a href="https://news.microsoft.com/en-au/2021/02/03/microsoft-supports-australian-government-proposal-addressing-news-media-and-digital-platforms/">微软非常公开</a> 地表示支持澳大利亚的新法律，它甚至 <a href="https://www.reuters.com/article/us-australia-media-facebook-microsoft/microsoft-teams-up-with-eu-publishers-amid-facebooks-australian-spat-idUSKBN2AM1MB">与欧洲出版商联手</a> 呼吁网络平台达成协议，为新闻机构的内容付费。谷歌对微软参与其中并不满意，这是自 <a href="https://www.theverge.com/2013/11/22/5135626/scroogled-microsoft-ads-attack-google">Scroogled 时代</a> 以来，我们看到的第一次大规模的公开争辩。</p><p>谷歌全球事务主管肯特-沃克（Kent Walker）<a href="https://blog.google/products/news/google-commitment-supporting-journalism/">在一篇博客文章中</a> 说：”他们现在提出了自私的要求，甚至不惜打破开放 web 的工作方式，以削弱竞争对手的实力”。”这次最新的攻击标志着微软惯用手法的回归。沃克在文中放上了维基百科上的”恐惧、不确定性和怀疑”（FUD）条目的链接，并指责微软混淆视听，以转移对最近安全问题的注意力。</p><p>沃克说：“微软这次的攻击并非巧合，而是有备而来。这发生在 SolarWinds 攻击之后，在他们允许他们的数万名客户被黑客通过微软的主要漏洞攻击的时刻。” 沃克接着说：“微软被警告他们系统中存在的漏洞，我们知道这些漏洞正在被黑客利用，微软也在做止损工作。与此同时，他们的客户正在被称为 “电子邮件大劫案” 的事件中中收拾残局。所以对他们转移视线的老把戏我们不必大惊小怪”</p><p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/00f4fd6fed7545018f48651d4f2bf549~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p>众议院司法委员会</p><p>谷歌的这一不同寻常的攻击也正值众议院司法委员会 <a href="https://judiciary.house.gov/">（House Judiciary Committee）</a> 研究自由和多元化新闻竞争的反垄断和商业方面的问题。谷歌认为它并没有从谷歌新闻中赚钱，但微软认为这要复杂得多，涉及到谷歌搜索广告、广告技术业务、广告交换、广告技术工具以及谷歌的整体消费者数据集。</p><p>微软总裁布拉德-史密斯在 <a href="https://docs.house.gov/meetings/JU/JU05/20210312/111315/HHRG-117-JU05-Wstate-SmithB-20210312.pdf">这篇文章</a> 说：“新闻机构有广告库存要卖，但他们不能再直接卖给那些想投放广告的人。相反，出于客观原因，他们必须使用谷歌的工具，在谷歌的广告交易所上运营，为谷歌的运营贡献数据，并向谷歌支付资金。所有这一切都影响了新闻机构甚至从自己网站上的广告中获得经济利益的能力。”</p><p>在出版商是否应该对由搜索巨头和 Facebook 主导的数字广告行业拥有更多控制权的核心论点上，谷歌和微软显然存在分歧。微软希望国会推进《新闻竞争与保护法》来让新闻机构能够与在线内容发行商进行集体谈判。</p><p>而谷歌则认为，其试图与新闻行业合作的 <a href="https://newsinitiative.withgoogle.com/">谷歌新闻计划</a> 足以帮助新闻机构。在微软和谷歌唇枪舌战的同时，<a href="https://judiciary.house.gov/calendar/eventsingle.aspx?EventID=4440">众议院司法委员会今天召开会议</a>，就数字时代新闻业的未来听取不那么尖锐的争论。</p><blockquote><p>如果发现译文存在错误或其他需要改进的地方，欢迎到 <a href="https://github.com/xitu/gold-miner">掘金翻译计划</a> 对译文进行修改并 PR，也可获得相应奖励积分。文章开头的 <strong>本文永久链接</strong> 即为本文在 GitHub 上的 MarkDown 链接。</p></blockquote><hr><blockquote><p><a href="https://github.com/xitu/gold-miner">掘金翻译计划</a> 是一个翻译优质互联网技术文章的社区，文章来源为 <a href="https://juejin.im/">掘金</a> 上的英文分享文章。内容覆盖 <a href="https://github.com/xitu/gold-miner#android">Android</a>、<a href="https://github.com/xitu/gold-miner#ios">iOS</a>、<a href="https://github.com/xitu/gold-miner#%E5%89%8D%E7%AB%AF">前端</a>、<a href="https://github.com/xitu/gold-miner#%E5%90%8E%E7%AB%AF">后端</a>、<a href="https://github.com/xitu/gold-miner#%E5%8C%BA%E5%9D%97%E9%93%BE">区块链</a>、<a href="https://github.com/xitu/gold-miner#%E4%BA%A7%E5%93%81">产品</a>、<a href="https://github.com/xitu/gold-miner#%E8%AE%BE%E8%AE%A1">设计</a>、<a href="https://github.com/xitu/gold-miner#%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD">人工智能</a>等领域，想要查看更多优质译文请持续关注 <a href="https://github.com/xitu/gold-miner">掘金翻译计划</a>、<a href="http://weibo.com/juejinfanyi">官方微博</a>、<a href="https://zhuanlan.zhihu.com/juejinfanyi">知乎专栏</a>。</p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/70420a4b1330/</id>
    <link href="https://blog.zisheng.pro/posts/70420a4b1330/"/>
    <published>2023-04-19T09:31:35.000Z</published>
    <summary>谷歌和微软工程师可能会在 Chromium 浏览器项目开发上进行合作，但这并没有消除双方之间的企业政治斗争。今天谷歌对微软发起了严厉的攻击，指责它试图 “打破开放 web 的运作方式，以此来削弱竞争对手&quot;。</summary>
    <title>谷歌抨击微软“试图破坏开放 web 的运作方式”</title>
    <updated>2026-07-12T09:21:36.911Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/categories/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="掘金翻译计划" scheme="https://blog.zisheng.pro/categories/%E6%8E%98%E9%87%91%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="架构图" scheme="https://blog.zisheng.pro/tags/%E6%9E%B6%E6%9E%84%E5%9B%BE/"/>
    <category term="draw.io" scheme="https://blog.zisheng.pro/tags/draw-io/"/>
    <content>
      <![CDATA[<blockquote><ul><li>原文地址：<a href="https://betterprogramming.pub/solutions-architect-tips-the-5-types-of-architecture-diagrams-eb0c11996f9e">Solutions Architect Tips — The 5 Types of Architecture Diagrams</a></li><li>原文作者：<a href="https://medium.com/@allenheltondev">Allen Helton</a></li><li>译文出自：<a href="https://github.com/xitu/gold-miner">掘金翻译计划</a></li><li>本文永久链接：<a href="https://github.com/xitu/gold-miner/blob/master/article/2021/solutions-architect-tips-the-5-types-of-architecture-diagrams.md">https://github.com/xitu/gold-miner/blob/master/article/2021/solutions-architect-tips-the-5-types-of-architecture-diagrams.md</a></li><li>译者：<a href="https://github.com/youngjuning">紫升</a></li><li>校对者：<a href="https://github.com/PassionPenguin">PassionPenguin</a>、<a href="https://github.com/zenblo">Zz招锦</a></li></ul></blockquote><p>你是否曾经在会议中，有人试图解释一个软件系统如何工作？</p><p>我与一位解决方案架构师新手进行了一次对话。他试图描述他们提出的一个大约有八个不同组件的系统，而这些组件都以多种方式相互作用。</p><p>他们用手势和大量的 “这块和这块之间通过…… ” 来解释解决方案。</p><p>我能听懂他们嘴里说的每个字，但是把这些字连起来我什么都没弄明白。</p><p>在解释复杂的架构概念时，口头表述显得苍白无力。我一边跟着思路走，一边试图建立一个心理模型。我需要一个画面感。</p><p>我需要<strong>一张图表</strong>。</p><p>但不是说一张图就可以走遍天下。架构图并不是一个“一刀切”的解决方案。</p><p>我们最近讨论过，<a href="https://betterprogramming.pub/how-to-switch-from-software-developer-to-solutions-architect-5e0c12bdc4b1">作为一个解决方案架构师</a> 的一个重要能力是将你的想法有效地传达给技术和非技术受众。</p><p>你的图表必须考虑到这一点。如果你想把你的想法传达给不同的人群，你必须制作多个版本的图。</p><p>今天，我们将讨论你应该根据五个不同的受众制作的五种不同类型的图表。</p><p>我们将讨论一个业务是假想的但 API 是真实的例子：<a href="https://github.com/allenheltondev/gopher-holes-unlimited">无限地鼠洞</a>，我们在系统中添加一个新的地鼠进行追踪。</p><h2 id="1-流程图"><a href="#1-流程图" class="headerlink" title="1. 流程图"></a>1. 流程图</h2><p>你可以制作的最通用、一般来说影响最广的图是流程图。它是一个中高层的图，可以展示工作流的所有部分。</p><p>该图说明了业务流程中的流动部分。</p><p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6cc634aefcc5437a86311d1a5ffca3f2~tplv-k3u1fbpfcp-watermark.image" alt="0_ZUyEi9v9becFhGfp.png"></p><h3 id="受众"><a href="#受众" class="headerlink" title="受众"></a>受众</h3><p>这种类型的图的受众通常是技术人员。它可以用来向架构委员会介绍一个想法，或者向开发人员描述一个业务流程如何工作。</p><h3 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h3><p><strong>架构流程图</strong>的主要内容是包含所有流动的部分。在 <a href="https://betterprogramming.pub/serverless-you-keep-using-that-word-i-do-not-think-it-means-what-you-think-it-means-c7d5516a5ecc">无服务器 AWS 环境</a> 的案例中，我们标注了每个托管服务以及服务之间的通信。</p><p>虽然没有描述这些部件之间如何交互的细节，但是图中确实显示了这些连接。它显示了数据如何在系统中流动。</p><h2 id="2-服务图"><a href="#2-服务图" class="headerlink" title="2. 服务图"></a>2. 服务图</h2><p>服务图从高层次上说明了连通性。它并不显示工作流或服务如何工作的任何细节，而是显示了发挥作用的主要部分。这是一个旨在显示应用程序中使用的内部与外部服务的图。</p><p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/44d93cf3319b4515873197760ac20127~tplv-k3u1fbpfcp-watermark.image" alt="0_hgdP8Dhm9taCxsec.png"></p><h3 id="受众-1"><a href="#受众-1" class="headerlink" title="受众"></a>受众</h3><p>IT 和网络工程师往往对这种类型的图最感兴趣。他们关心你与外部服务的任何连接。另外，他们需要知道是否有任何内部连接需要被监控。</p><p>我经常用这种图来向高管们描述系统的工作原理。他们想知道主要应用程序之间的连接，没有什么比服务图更好地表示这些连接了。</p><h3 id="注意事项-1"><a href="#注意事项-1" class="headerlink" title="注意事项"></a>注意事项</h3><p>在构建<strong>架构服务图</strong>时，最好列出构成你的应用或生态系统的所有微服务。标明哪些服务之间相互通信，并确保区分你公司拥有的服务和外部的服务。</p><p>关于服务如何工作的细节对于这个高级图来说是不必要的。这是所有关于使应用程序运行的服务。</p><h2 id="3-角色图"><a href="#3-角色图" class="headerlink" title="3. 角色图"></a>3. 角色图</h2><p>重要的是要表明你的架构需要解决的业务问题。角色图描述了一个按时间顺序排列的视图和特定工作流中的角色。这是证明你在构建解决方案时已经考虑了业务因素的最佳工具。</p><p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8a04a6009c3d465ca053d2bcc70ede78~tplv-k3u1fbpfcp-watermark.image" alt="0_YS49oSULHmzUvx2L.png"></p><h3 id="受众-2"><a href="#受众-2" class="headerlink" title="受众"></a>受众</h3><p>面向企业的个人和产品所有者是这类图的目标受众。他们关注的是角色以及他们如何与系统互动。向他们展示一张<strong>谁做了什么</strong>和<strong>在什么时间做</strong>的图，它将完美地描述你的系统正在做什么。</p><h3 id="注意事项-2"><a href="#注意事项-2" class="headerlink" title="注意事项"></a>注意事项</h3><p><strong>架构角色图</strong>对 <a href="https://www.bpmn.org/">BPMN 模型</a> 进行了一些尝试。利用泳道来显示工作流中的不同角色。这种类型的图往往是偏底层的，因为它比其它图包含更多的细节。一定要标明角色、工作流，以及关于业务流程如何从一个步骤到另一个步骤的所有假设。</p><p>这些图还可以帮助那些刚接触一个领域的开发人员，并为他们将要构建的东西提供深刻的上下文。</p><h2 id="4-基础设施图"><a href="#4-基础设施图" class="headerlink" title="4. 基础设施图"></a>4. 基础设施图</h2><p>基础设施图是一个“所见即所得”的模型。它代表了已经实现的一切。它是一个偏底层的图，旨在包含服务、应用、生态系统中存在的一切。</p><p>这个图的目的是显示已经建立的东西和系统当前的工作方式。可以把它看作是你所构建的应用程序的<strong>蓝图</strong>。</p><p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/df11db95ab9948ffa796ca1ae0e52a4e~tplv-k3u1fbpfcp-watermark.image" alt="0_fHIrAhbsy8vjXtrC.png"></p><h3 id="受众-3"><a href="#受众-3" class="headerlink" title="受众"></a>受众</h3><p>基础设施图的受众不同。它可以用来向开发人员展示他们在特定微服务中必须使用的东西。它也可以用来向客户展示你的公司为完成一项任务而使用的所有资源。</p><p>技术人员将是你的基础设施图的主要受众。由于你提供的是一个清单，而不是传达想法或业务流程，所以这个图的预期用途仅限于信息。这是为那些喜欢 “细枝末节” 细节的人准备的。</p><h3 id="注意事项-3"><a href="#注意事项-3" class="headerlink" title="注意事项"></a>注意事项</h3><p>在构建<strong>基础设施架构图</strong>时，不要漏掉任何一块。这种类型的图的目标是显示你的应用程序中的所有内容以及它们如何连接。你不需要在<strong>如何</strong>上做得太过详细，而是专注于让你的应用的所有部分都包含在图中。</p><h2 id="5-开发者图"><a href="#5-开发者图" class="headerlink" title="5. 开发者图"></a>5. 开发者图</h2><p>当你需要开始着手处理问题时，开发者图将是你最好的选择。它包括了开发人员为了构建解决方案所需要的一切。</p><p>我们的目标是回答任何可能通过查看<strong>流程图</strong>出现的问题，并在设计中包含它们。这是一堆图中最底层的图，目的是为了在你不在场的情况下传达想法。</p><p>有人应该能够读懂这张图，并确切地知道该怎么做。</p><p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cb9ebee9c83342449840372366222293~tplv-k3u1fbpfcp-watermark.image" alt="0_Ph0P0a9JRdHqDDlN.png"></p><h3 id="受众-4"><a href="#受众-4" class="headerlink" title="受众"></a>受众</h3><p>实现落地方案的开发人员是该类型图的受众。图中所包含的详细程度对于你团队以外的人来说是不必要的。有时，对于不需要细节的受众来说，太多细节可能是件坏事。</p><p>向开发团队以外的人提供实施细节就是一个过于详细的例子。它导致分散注意力，并掩盖了你试图传达的其他信息。</p><h3 id="注意事项-4"><a href="#注意事项-4" class="headerlink" title="注意事项"></a>注意事项</h3><p>开发者架构图本质上是补充了细节的<strong>流程图</strong>。用你能想到的任何具体的实现细节来标注每一块，并且一定要标注重要的转换。</p><p>这种类型的图并不能取代用户故事，但它确实有助于增强用户故事，增加整个开发团队的理解。当你可以使用它们的时候，因为当实现完成后，你将有一个有用的在未来可以参考的材料。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>架构图有很多类型。每一种都有独特的目的，很多都服务于不同的受众。作为一个解决方案架构师，你必须能够在推销你的想法时，向正确的人提供正确类型的图表。</p><p>通常情况下，一个版本的图是不够的。当我开始一个新的设计时，我总是从<strong>流程图</strong>开始。我把所有的想法都写下来，然后把它推荐给其他系统架构师。一旦我们就解决方案达成一致，我就会拿着这个图，把它变成一个<strong>角色图</strong>，然后拿给业务人员。</p><p>当我得到业务人员的签收后，我就可以自由地做出<strong>开发人员图</strong>和<strong>服务图</strong>。服务图是给高管的，以确保他们对我们正在做的事情有一个高层次的看法。开发者图是给将要落地解决方案的工程师的。</p><p>一旦解决方案构建完成，我们就可以更新<strong>基础设施图</strong>，以继续新的工作。</p><p>一张图片胜过千言万语，但当涉及到架构图时，它们可能胜过五千张。能够让人们快速、轻松地理解你的想法，是成为一个优秀的解决方案架构师的关键。</p><p>有了为不同的受众构建不同类型的图表的能力，你就能为自己的成功做好准备。</p><blockquote><p><strong>P.S. 我总是使用 <a href="https://draw.io/">draw.io</a> 来构建我的图表。这是一个免费的工具，为我们提供制作漂亮的各种各样的图表、模型所需的所有东西。</strong></p></blockquote><blockquote><p>如果发现译文存在错误或其他需要改进的地方，欢迎到 <a href="https://github.com/xitu/gold-miner">掘金翻译计划</a> 对译文进行修改并 PR，也可获得相应奖励积分。文章开头的 <strong>本文永久链接</strong> 即为本文在 GitHub 上的 MarkDown 链接。</p></blockquote><hr><blockquote><p><a href="https://github.com/xitu/gold-miner">掘金翻译计划</a> 是一个翻译优质互联网技术文章的社区，文章来源为 <a href="https://juejin.im/">掘金</a> 上的英文分享文章。内容覆盖 <a href="https://github.com/xitu/gold-miner#android">Android</a>、<a href="https://github.com/xitu/gold-miner#ios">iOS</a>、<a href="https://github.com/xitu/gold-miner#%E5%89%8D%E7%AB%AF">前端</a>、<a href="https://github.com/xitu/gold-miner#%E5%90%8E%E7%AB%AF">后端</a>、<a href="https://github.com/xitu/gold-miner#%E5%8C%BA%E5%9D%97%E9%93%BE">区块链</a>、<a href="https://github.com/xitu/gold-miner#%E4%BA%A7%E5%93%81">产品</a>、<a href="https://github.com/xitu/gold-miner#%E8%AE%BE%E8%AE%A1">设计</a>、<a href="https://github.com/xitu/gold-miner#%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD">人工智能</a>等领域，想要查看更多优质译文请持续关注 <a href="https://github.com/xitu/gold-miner">掘金翻译计划</a>、<a href="http://weibo.com/juejinfanyi">官方微博</a>、<a href="https://zhuanlan.zhihu.com/juejinfanyi">知乎专栏</a>。</p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/36caef603791/</id>
    <link href="https://blog.zisheng.pro/posts/36caef603791/"/>
    <published>2023-04-19T09:29:27.000Z</published>
    <summary>本文将讨论你应该根据五个不同的受众制作的五种不同类型的图表。</summary>
    <title>解决方案架构师技巧：架构图的 5 种类型</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="运维" scheme="https://blog.zisheng.pro/categories/%E8%BF%90%E7%BB%B4/"/>
    <category term="Docker" scheme="https://blog.zisheng.pro/tags/Docker/"/>
    <category term="宝塔" scheme="https://blog.zisheng.pro/tags/%E5%AE%9D%E5%A1%94/"/>
    <category term="Linux" scheme="https://blog.zisheng.pro/tags/Linux/"/>
    <category term="LAMP" scheme="https://blog.zisheng.pro/tags/LAMP/"/>
    <category term="LNMP" scheme="https://blog.zisheng.pro/tags/LNMP/"/>
    <content>
      <![CDATA[<h2 id="宝塔面板是什么？"><a href="#宝塔面板是什么？" class="headerlink" title="宝塔面板是什么？"></a>宝塔面板是什么？</h2><p>宝塔 Linux 面板是提升运维效率的服务器管理软件，支持一键 LAMP&#x2F;LNMP&#x2F;集群&#x2F;监控&#x2F;网站&#x2F;FTP&#x2F;数据库&#x2F;JAVA 等 100 多项服务器管理功能。有 30 个人的专业团队研发及维护，经过 200 多个版本的迭代，功能全，少出错且足够安全，已获得全球百万用户认可安装。运维要高效，装宝塔。</p><p><img src="https://www.bt.cn/Public/images/linux_pc_free.png" alt="紫升"></p><h2 id="为什么使用-Docker-安装？"><a href="#为什么使用-Docker-安装？" class="headerlink" title="为什么使用 Docker 安装？"></a>为什么使用 Docker 安装？</h2><ol><li>环境隔离，一个主机可以部署多套服务</li><li>宝塔对主机控制度较高，依赖关系复杂，而 Docker 可以保证宝塔服务不影响到宿主机其他服务。</li><li>由于和宿主机环境隔离，不容易被木马工具。</li><li>宝塔面板的兼容性顺序为 <code>Centos7.x &gt; Debian10 &gt; Ubuntu 20.04 &gt; Centos8.x &gt; Ubuntu 18.04 &gt; 其它系统</code>，而用户的主机五花八门，为了获取最佳的兼容性，使用 Docker 部署是不二选择。</li><li>Docker 管理成本低，方便迁移。</li><li>可随时管理容器，无需重置服务器。</li></ol><h2 id="Docker-安装"><a href="#Docker-安装" class="headerlink" title="Docker 安装"></a>Docker 安装</h2><p>推荐使用脚本安装：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">curl -fsSL https://get.docker.com -o get-docker.sh</span><br><span class="line">sudo sh get-docker.sh --mirror Aliyun</span><br></pre></td></tr></table></figure><p>安装成功后，会自动启动 Docker 服务。用户可以使用 <code>systemctl is-enabled docker</code> 来确认 Docker 服务是否是开机自启动。如果不是请使用 <code>systemctl enable docker</code> 设置自启动。</p><p><strong>代理加速</strong></p><p>执行 <code>nano /etc/docker/daemon.json</code> 中写入如下内容：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;registry-mirrors&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;https://registry.docker-cn.com&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;https://mirror.baidubce.com&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;https://hub-mirror.c.163.com&quot;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="开端口教程，不开不能用"><a href="#开端口教程，不开不能用" class="headerlink" title="开端口教程，不开不能用"></a>开端口教程，不开不能用</h2><blockquote><p>宝塔需要的端口：8888、888、20、21、80、443</p></blockquote><ul><li>腾讯云：<a href="https://www.bt.cn/bbs/thread-1229-1-1.html">https://www.bt.cn/bbs/thread-1229-1-1.html</a></li><li>阿里云：<a href="https://www.bt.cn/bbs/thread-2897-1-1.html">https://www.bt.cn/bbs/thread-2897-1-1.html</a></li><li>华为云：<a href="https://www.bt.cn/bbs/thread-3923-1-1.html">https://www.bt.cn/bbs/thread-3923-1-1.html</a></li><li>京东云：<a href="https://docs.jdcloud.com/cn/virtual-machines/configurate-inbound-rules">https://docs.jdcloud.com/cn/virtual-machines/configurate-inbound-rules</a></li></ul><h2 id="安装宝塔镜像"><a href="#安装宝塔镜像" class="headerlink" title="安装宝塔镜像"></a>安装宝塔镜像</h2><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -fsSL https://blog.zisheng.pro/shell/baota/install.sh | sh</span><br></pre></td></tr></table></figure><ul><li><code>/www/wwwroot</code> 挂载在宿主机的 <code>~/wwwroot</code> 目录</li><li><code>/www</code> 挂载在 <code>baota_www</code> volume，即 <code>/var/lib/docker/volumes/baota_www/</code></li></ul><blockquote><p>上面的脚本是为了我为了方便管理，等价于 <code>docker run -tid --name baota --net=host --privileged=true --shm-size=1g --restart always -v baota_www:/www -v ~/wwwroot:/www/wwwroot pch18/baota</code></p></blockquote><h3 id="登录方式"><a href="#登录方式" class="headerlink" title="登录方式"></a>登录方式</h3><ul><li>登陆地址：<code>http://&#123;&#123;面板ip地址&#125;&#125;:8888</code></li><li>初始账号：<code>username</code></li><li>初始密码：<code>password</code></li></ul><h3 id="宝塔面板命令行"><a href="#宝塔面板命令行" class="headerlink" title="宝塔面板命令行"></a>宝塔面板命令行</h3><p><strong>进入容器：</strong></p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker <span class="built_in">exec</span> -it baota bash</span><br></pre></td></tr></table></figure><p><strong>执行 <code>bt</code> 进行交互式操作：</strong></p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">[root@bebc0d8e35b1 wwwroot]<span class="comment"># bt</span></span><br><span class="line">===============宝塔面板命令行==================</span><br><span class="line">(1) 重启面板服务           (8) 改面板端口</span><br><span class="line">(2) 停止面板服务           (9) 清除面板缓存</span><br><span class="line">(3) 启动面板服务           (10) 清除登录限制</span><br><span class="line">(4) 重载面板服务           (11) 取消入口限制</span><br><span class="line">(5) 修改面板密码           (12) 取消域名绑定限制</span><br><span class="line">(6) 修改面板用户名         (13) 取消IP访问限制</span><br><span class="line">(7) 强制修改MySQL密码      (14) 查看面板默认信息</span><br><span class="line">(22) 显示面板错误日志      (15) 清理系统垃圾</span><br><span class="line">(23) 关闭BasicAuth认证     (16) 修复面板(检查错误并更新面板文件到最新版)</span><br><span class="line">(24) 关闭谷歌认证          (17) 设置日志切割是否压缩</span><br><span class="line">(25) 设置是否保存文件历史副本  (18) 设置是否自动备份面板</span><br><span class="line">(0) 取消</span><br><span class="line">===============================================</span><br></pre></td></tr></table></figure><h2 id="进阶配置"><a href="#进阶配置" class="headerlink" title="进阶配置"></a>进阶配置</h2><ol><li>登录面板后，请立即修改面板用户和面板密码，并建议修改面板端口和安全入口。</li><li>更新的话，可以使用面板提供的功能进行修复。</li></ol><h2 id="备份宝塔面板"><a href="#备份宝塔面板" class="headerlink" title="备份宝塔面板"></a>备份宝塔面板</h2><h3 id="备份容器"><a href="#备份容器" class="headerlink" title="备份容器"></a>备份容器</h3><p>如果我们迁移的话，不需要再重复上面的安装配置步骤，直接使用我提供的脚本：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -fsSL https://blog.zisheng.pro/shell/baota/backup.sh | sh</span><br></pre></td></tr></table></figure><p>压缩包 <code>baota.tar</code> 将备份到当前目录，上传到新的服务器之后载入容器：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker load -i baota.tar</span><br></pre></td></tr></table></figure><p>恢复容器：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -fsSL https://blog.zisheng.pro/shell/baota/restore.sh | sh</span><br></pre></td></tr></table></figure><h2 id="已知问题"><a href="#已知问题" class="headerlink" title="已知问题"></a>已知问题</h2><ol><li>部分机器初始化环境可能某些端口被占用，比如 80 端口被占用，使用 <code>netstat -lnp|grep 80</code> 查看进程 PID，然后使用 <code>kill -9 XXXX</code> 结束该进程 (xxxx 为进程的相关 pid)。</li><li>如果事先安装了 nginx，请先卸载，完全卸载请执行 <code>curl -fsSL https://blog.zisheng.pro/shell/nginx/uninstall.sh | sh</code></li></ol>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/b68410bd395b/</id>
    <link href="https://blog.zisheng.pro/posts/b68410bd395b/"/>
    <published>2023-04-18T09:19:26.000Z</published>
    <summary>宝塔 Linux 面板是提升运维效率的服务器管理软件，支持一键 LAMP/LNMP/集群/监控/网站/FTP/数据库/JAVA 等 100 多项服务器管理功能。</summary>
    <title>Docker 安装配置宝塔面板</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="笔记" scheme="https://blog.zisheng.pro/categories/%E7%AC%94%E8%AE%B0/"/>
    <category term="React.js" scheme="https://blog.zisheng.pro/tags/React-js/"/>
    <category term="Vue.js" scheme="https://blog.zisheng.pro/tags/Vue-js/"/>
    <category term="Angular.js" scheme="https://blog.zisheng.pro/tags/Angular-js/"/>
    <category term="前端框架" scheme="https://blog.zisheng.pro/tags/%E5%89%8D%E7%AB%AF%E6%A1%86%E6%9E%B6/"/>
    <category term="UI" scheme="https://blog.zisheng.pro/tags/UI/"/>
    <category term="逻辑" scheme="https://blog.zisheng.pro/tags/%E9%80%BB%E8%BE%91/"/>
    <content>
      <![CDATA[<p>前端框架旨在帮助开发人员更有效地组织和管理UI和逻辑。以下是一些常见方法，用于描述和组织UI和逻辑</p><h2 id="组件化"><a href="#组件化" class="headerlink" title="组件化"></a>组件化</h2><p>前端框架通常支持将UI划分为可重用组件。这有助于减少代码复制和提高代码的可维护性。每个组件都具有自己的状态和属性，以及与之相关联的事件处理程序。</p><p>比如 React 哲学就是在构建用户界面时，首先会把它分解成一个个组件，然后，你需要把这些组件连接在一起，使数据流经它们。</p><h2 id="模块化"><a href="#模块化" class="headerlink" title="模块化"></a>模块化</h2><p>前端框架还经常使用模块化来组织 JavaScript 逻辑。这样做可以将应用程序拆分为小块，每个块具有自己的职责和功能。这有助于改善代码的可读性和可维护性，并使团队成员能够更轻松地协作。</p><p>比如 React 通过 Hooks 来实现模块化，Hooks 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。</p><h2 id="数据绑定"><a href="#数据绑定" class="headerlink" title="数据绑定"></a>数据绑定</h2><p>前端框架通常包含数据绑定机制，允许开发人员将UI元素与模型数据绑定。这种技术使得当一个对象发生变化时，与之相关联的UI元素会自动更新，从而简化了UI的维护。</p><h2 id="路由"><a href="#路由" class="headerlink" title="路由"></a>路由</h2><p>前端框架通常支持路由机制，允许页面根据URL路径进行重新加载或刷新。这对于单页应用程序尤其有用，因为它可以在不刷新整个页面的情况下更新UI。</p><h2 id="状态管理"><a href="#状态管理" class="headerlink" title="状态管理"></a>状态管理</h2><p>一些前端框架提供了状态管理工具，例如 Redux 和 Vuex。这使得开发人员能够轻松地管理应用程序的状态，并在不同组件之间共享数据。</p><h2 id="模板语言"><a href="#模板语言" class="headerlink" title="模板语言"></a>模板语言</h2><p>前端框架通常使用模板语言来生成 UI 元素。这些语言允许开发人员将 JavaScript 逻辑嵌入到 HTML 中，并根据数据生成动态内容。比如 vue template 语法就是这样的。</p><h2 id="样式管理"><a href="#样式管理" class="headerlink" title="样式管理"></a>样式管理</h2><p>前端框架通常提供样式管理工具，例如 CSS 预处理器（如 Sass 或 Less）和 CSS 模块。这些工具使得样式更易于管理和维护，并提高了代码的可重用性。</p><h2 id="构建工具"><a href="#构建工具" class="headerlink" title="构建工具"></a>构建工具</h2><p>前端框架经常使用构建工具（例如 Webpack 或 Parcel）来自动化构建过程。这些工具可以执行任务，例如打包、压缩和转换代码，在开发和生产环境中进行分离。</p><h2 id="三大前端框架"><a href="#三大前端框架" class="headerlink" title="三大前端框架"></a>三大前端框架</h2><ul><li>Angular：由 Google 开发的 JavaScript 框架，提供完整的 MVC（模型-视图-控制器）解决方案，并包含许多内置的功能，例如表单验证、路由和 HTTP 请求管理。</li><li>React：由 Facebook 开发的 JavaScript 库，专注于 UI 层面的构建，提供虚拟 DOM（Document Object Model）和组件化架构，使得构建大规模应用程序更加容易。</li><li>Vue.js：一个轻量级的 JavaScript 框架，通过使用模板语言和指令来实现响应式数据绑定和组件化架构。</li></ul><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>尽管前端框架有很多不同的实现方式，但它们的目标都是相似的：通过提供结构、约定和工具来帮助开发人员更容易地构建复杂的Web 应用程序。如果使用得当，前端框架可以大大简化开发流程，并提高代码的可维护性和可读性。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/58a20039730b/</id>
    <link href="https://blog.zisheng.pro/posts/58a20039730b/"/>
    <published>2023-04-12T11:45:23.000Z</published>
    <summary>前端框架旨在帮助开发人员更有效地组织和管理 UI 和逻辑。本文讲解了一些常见的用于描述和组织 UI 和逻辑的方法</summary>
    <title>前端框架如何描述和组织 UI 与逻辑</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="Google" scheme="https://blog.zisheng.pro/categories/Google/"/>
    <category term="Adsense" scheme="https://blog.zisheng.pro/categories/Google/Adsense/"/>
    <category term="站点运营" scheme="https://blog.zisheng.pro/categories/%E7%AB%99%E7%82%B9%E8%BF%90%E8%90%A5/"/>
    <category term="广告" scheme="https://blog.zisheng.pro/categories/%E7%AB%99%E7%82%B9%E8%BF%90%E8%90%A5/%E5%B9%BF%E5%91%8A/"/>
    <category term="Adsense" scheme="https://blog.zisheng.pro/tags/Adsense/"/>
    <category term="Google" scheme="https://blog.zisheng.pro/tags/Google/"/>
    <category term="Google Adsense" scheme="https://blog.zisheng.pro/tags/Google-Adsense/"/>
    <category term="Google Adsense 付款方式" scheme="https://blog.zisheng.pro/tags/Google-Adsense-%E4%BB%98%E6%AC%BE%E6%96%B9%E5%BC%8F/"/>
    <category term="Google Adsense 收款方式" scheme="https://blog.zisheng.pro/tags/Google-Adsense-%E6%94%B6%E6%AC%BE%E6%96%B9%E5%BC%8F/"/>
    <category term="Google Adsense 招商银行" scheme="https://blog.zisheng.pro/tags/Google-Adsense-%E6%8B%9B%E5%95%86%E9%93%B6%E8%A1%8C/"/>
    <category term="Google Adsense 电汇" scheme="https://blog.zisheng.pro/tags/Google-Adsense-%E7%94%B5%E6%B1%87/"/>
    <content>
      <![CDATA[<blockquote><p>当账户余额达到 100 美金时，Adsense 会显示收款账户设置选项，本文讲解了如何设置有效的收款方式。</p></blockquote><p>Google Adsense 并没有人工客服服务，由于 Google 在中国大陆的退出和服务受限，对于不熟悉相关操作流程而且无法直接联系客服获取帮助的人来说可能会存在一些困难。针对 Google AdSense 的付款方式，之前西联汇款和支票兑现方式确实很常见，但是如今也可以通过银行转账、Paypal 等方式来收取付款。我建议需要使用 Google 服务的人可以多查找可靠的网上教学资源，同时多了解相关政策和操作流程，以更好地利用这些工具。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1681051625654.png" alt="Google Adsense 添加付款方式"></p><p>自 2020 年起 Google 暂停西联汇款，采用电汇方式付款，其实经过改革，Google AdSense提现变得更加便捷，加上现在银行都开发了APP，收款+结汇都可以在线完成，下文以招商银行为例介绍收款的方法。</p><p>补充：中国各大银行都支持跨境收款，但是每家银行政策不一样，例如中国银行不支持公对私，广发银行需要提供合同协议等，经过验证，目前招商银行收款非常稳定，暂时没有要求需要提供相关文件。添加付款方式后 Google AdSense 账户达到 $100，每月 21 日（中国时间 22 日）付款到指定银行账户。</p><h2 id="添加付款方式"><a href="#添加付款方式" class="headerlink" title="添加付款方式"></a>添加付款方式</h2><div class="note danger modern"><p>由于国内信用卡的限制，无法接收境外汇款，所以必须使用储蓄卡付款。</p></div><p>进入 Google AdSense 管理后台点击<code>付款</code> -&gt; <code>付款信息</code> -&gt; <code>管理付款方式</code>，添加付款方式信息。银行卡必须为一类账户，二类及以下账户没有权限收款外币，所以到银行开户需要注意账户类型。</p><ul><li>付款人ID（可选）</li><li>银行账户上的姓名：LUO ZHU（姓名拼音大写）</li><li>银行名称：<code>China Merchants Bank, H.O. Shenzhen, China</code>（不需要改）</li><li>SWIFT BIC：<code>CMBCCNBS</code> (不需要改)</li><li>账号：招商银行卡号</li><li>重新输入账号：确认招商银行卡号</li></ul><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1681053357059.png" alt="总行接受境外汇款的路径"></p><p>以上信息是通过向招商银行 App 客服机器人问出来的：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1681053178244.png" alt="总行接受境外汇款的路径"></p><ul><li>收款行(Beneficiary＇s Bank): <code>China Merchants Bank, H.O. Shenzhen, China</code></li><li>Swift Code(BIC): <code>CMBCCNBS</code>（11位的可填写：<code>CMBCCNBSXXX</code>）</li><li>收款人(一卡通或存折或对公账号)账号(Beneficiary＇s a&#x2F;c no.):  <code>************</code></li><li>收款人名称(Beneficiary): ***(注:境内个人以我行开户证件姓名的汉语拼音为准，境外个人以我行开户证件姓名为准；对公客户英文名称)</li><li>地址(address): 深圳市深南大道7088号招商银行大厦（China Merchants Bank, H.O.,CMB Tower,7088 Shennan Boulevard, Shenzhen, China）</li></ul><h2 id="Google-AdSense-付款"><a href="#Google-AdSense-付款" class="headerlink" title="Google AdSense 付款"></a>Google AdSense 付款</h2><p>账号金额达到 $100，每月 22 日打款到指定招商银行账户，大约 2 个工作日会收到银行短信提示入账一笔美金。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1681053414520.png" alt="Google AdSense 付款"></p><h2 id="结汇"><a href="#结汇" class="headerlink" title="结汇"></a>结汇</h2><p>下载招商银行一卡通APP，进入”外汇结汇“委托结汇，卖出账户现有的美元现汇即可兑现为人民币，每个人年度结汇总额度（$50000）如果超出可到银行柜台申请额度。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1681053513525.png" alt="招商银行外汇结汇"></p><p>需要注意的是结汇业务办理时间是 8:30-22:00，如果超出时间，需要在下一个工作日办理。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1681053627730.png" alt="招商银行委托结汇"></p><p>如果有额度且在业务办理时间内，结汇委托页面如下：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1681053688032.png" alt="招商银行委托结汇"></p><p>最后祝大家 Google AdSense 付款顺利！</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/5de90b19f4d9/</id>
    <link href="https://blog.zisheng.pro/posts/5de90b19f4d9/"/>
    <published>2023-04-09T14:42:37.000Z</published>
    <summary>自2020年起，Google AdSense 已暂停使用西联汇款付款方式，而采用更为便捷的电汇方式进行付款。由于技术的不断发展，现在银行也都开发了APP，用户可以在线完成收款和结汇的操作。以下是以招商银行为例介绍 Google AdSense 收款方法</summary>
    <title>2023 Google Adsense 付款方式设置教程</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="Google" scheme="https://blog.zisheng.pro/categories/Google/"/>
    <category term="Adsense" scheme="https://blog.zisheng.pro/categories/Google/Adsense/"/>
    <category term="站点运营" scheme="https://blog.zisheng.pro/categories/%E7%AB%99%E7%82%B9%E8%BF%90%E8%90%A5/"/>
    <category term="广告" scheme="https://blog.zisheng.pro/categories/%E7%AB%99%E7%82%B9%E8%BF%90%E8%90%A5/%E5%B9%BF%E5%91%8A/"/>
    <category term="Google Adsense" scheme="https://blog.zisheng.pro/tags/Google-Adsense/"/>
    <category term="Google Adsense PIN 码" scheme="https://blog.zisheng.pro/tags/Google-Adsense-PIN-%E7%A0%81/"/>
    <category term="中国大陆" scheme="https://blog.zisheng.pro/tags/%E4%B8%AD%E5%9B%BD%E5%A4%A7%E9%99%86/"/>
    <category term="谷歌地址验证" scheme="https://blog.zisheng.pro/tags/%E8%B0%B7%E6%AD%8C%E5%9C%B0%E5%9D%80%E9%AA%8C%E8%AF%81/"/>
    <category term="谷歌广告" scheme="https://blog.zisheng.pro/tags/%E8%B0%B7%E6%AD%8C%E5%B9%BF%E5%91%8A/"/>
    <category term="谷歌广告收入" scheme="https://blog.zisheng.pro/tags/%E8%B0%B7%E6%AD%8C%E5%B9%BF%E5%91%8A%E6%94%B6%E5%85%A5/"/>
    <category term="谷歌身份验证" scheme="https://blog.zisheng.pro/tags/%E8%B0%B7%E6%AD%8C%E8%BA%AB%E4%BB%BD%E9%AA%8C%E8%AF%81/"/>
    <category term="谷歌地址验证最低限额" scheme="https://blog.zisheng.pro/tags/%E8%B0%B7%E6%AD%8C%E5%9C%B0%E5%9D%80%E9%AA%8C%E8%AF%81%E6%9C%80%E4%BD%8E%E9%99%90%E9%A2%9D/"/>
    <content>
      <![CDATA[<p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1681044391871.jpeg" alt="Google Adsense PIN 码"></p><p>为确保你的帐号安全无虞，在向你发放任何付款之前，谷歌会要求你先验证地址。验证地址的 PIN 码会以国际平信的方式邮寄给你。那么 2023 年中国大陆还能收到 PIN 码吗？答案是肯定的，只不过有一些需要注意的细节。</p><h2 id="地址验证的工作原理"><a href="#地址验证的工作原理" class="headerlink" title="地址验证的工作原理"></a>地址验证的工作原理</h2><p>1、当你的收入达到<a href="https://support.google.com/adsense/answer/1709871#v">进行验证所需的最低限额时</a>（目前中国账号是 10 美金），我们会将个人识别码（PIN 码）邮寄到您的付款地址。</p><p>PIN 码通常会在 2 至 4 周内寄达，但也可能需要更长时间，具体取决于您所在的地理位置。</p><div class="note warning modern"><p>如果我们要求您<a href="https://support.google.com/adsense/answer/7568896">验证自己的身份</a>，那么在您成功完成身份验证之前，谷歌不会给您寄送 PIN 码。</p></div><p>2、收到 PIN 码后，请将其输入 AdSense 帐号以验证您的地址。</p><p>自 PIN 码生成之日算起，您可以在 4 个月内将该码输入帐号。如果您在 4 个月后还未输入该码，我们会停止在您的页面上展示广告。</p><div class="note warning modern"><p>PIN 码是指在您的帐号余额达到进行验证所需的最低限额时生成的 6 位数字。PIN 码与发布商 ID 不同。请仅在 PIN 码验证卡片中输入正确的 PIN 码。请勿输入随机数字。如果 PIN 码输错三次，您的帐号将停止展示广告。</p></div><p>但是不用担心，如果你错过了 PIN 码，你可以申请线上验证 PIN 码。</p><h2 id="我的经验"><a href="#我的经验" class="headerlink" title="我的经验"></a>我的经验</h2><p>1、由于 Google Adsense 是以国际平信的形式发的 PIN 码，而国际平信的性质决定了是没有电话信息的，解决办法是在地址里面加上电话号码，这样就可以通过电话联系到你了。</p><p>2、如果过了两周多没有收到 PIN 码，可以自己到就近的中国邮政网点找一找，他们一般有专门负责国际平邮的分拣员。</p><p>由于我没想着能收到钱，所以之前填写地址的时候没有备注上电话，而且地址还有错别字，Google Adsense 又是在额度达到 10 美金后自动寄出的 PIN 码。所以我就等了两周多，还是没有收到，就去了中国邮政网点，并加了他们负责国际平信的快递员微信。第二天快递师傅就发微信告诉我这个到了好几天了。</p><p>而且由于我第二次改正了地址并在地址中备注了电话，我第二次申请的 PIN 码很快就也寄到了：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1681050930527.png" alt="Google Adsense PIN 码"></p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/5328a4a14a49/</id>
    <link href="https://blog.zisheng.pro/posts/5328a4a14a49/"/>
    <published>2023-04-09T12:32:06.000Z</published>
    <summary>为确保你的帐号安全无虞，在向你发放任何付款之前，谷歌会要求你先验证地址。验证地址的 PIN 码会以国际平信的方式邮寄给你。那么 2023 年中国大陆还能收到 PIN 码吗？答案是肯定的，只不过有一些需要注意的细节。</summary>
    <title>2023 中国大陆可以收到 Google Adsense PIN 码吗?</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="VS Code" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/VS-Code/"/>
    <category term="VS Code" scheme="https://blog.zisheng.pro/tags/VS-Code/"/>
    <category term="Git" scheme="https://blog.zisheng.pro/tags/Git/"/>
    <category term="时间线" scheme="https://blog.zisheng.pro/tags/%E6%97%B6%E9%97%B4%E7%BA%BF/"/>
    <category term="找回文件版本" scheme="https://blog.zisheng.pro/tags/%E6%89%BE%E5%9B%9E%E6%96%87%E4%BB%B6%E7%89%88%E6%9C%AC/"/>
    <category term="撤销修改" scheme="https://blog.zisheng.pro/tags/%E6%92%A4%E9%94%80%E4%BF%AE%E6%94%B9/"/>
    <category term="撤销保存" scheme="https://blog.zisheng.pro/tags/%E6%92%A4%E9%94%80%E4%BF%9D%E5%AD%98/"/>
    <category term="撤销提交" scheme="https://blog.zisheng.pro/tags/%E6%92%A4%E9%94%80%E6%8F%90%E4%BA%A4/"/>
    <category term="Local History" scheme="https://blog.zisheng.pro/tags/Local-History/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>你是否遇到过这样的情况：你修改了本地代码文件，但需要恢复到之前保存的未提交到 Git 存储库中的版本？</p><p>如果你是 Visual Studio Code 的用户，那么你很幸运，因为我将向你展示如何做到这一点…</p><div class="img-wrap"><div class="img-bg"><img class="img" src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1680956041180.png" style="width:100px;height:100px;"/></div></div><h2 id="步骤"><a href="#步骤" class="headerlink" title="步骤"></a>步骤</h2><p>在探索 VS Code 中众多的菜单选项和面板时，我最近偶然发现了一个鲜为人知的功能，叫做“时间线”（timeline），它可以显示给定文件的运行历史。</p><p>你可以在 VS Code 中打开一个给定的文件，在“资源管理器”菜单底部注意到一个名为“时间线”的面板，它显示给定文件的运行版本历史。它将你的本地文件保存历史（未提交更改）和 Git 存储库提交整合在一个地方。这是一个非常方便但鲜为人知的 VS Code 功能。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1680957163086.png" alt="vscode 资源管理器时间线面板"></p><p>点击时间轴面板中的条目，将显示所做修改的不同对比。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1680957154278.png" alt="vscode 资源管理器时间线面板"></p><p>需要注意的是，本地文件的保存历史只有在你用 VS Code 编辑文件时才会被跟踪。因此，如果你在 VS Code 之外编辑文件，你将不会看到任何文件的版本历史。</p><h2 id="Local-History"><a href="#Local-History" class="headerlink" title="Local History"></a>Local History</h2><p>VS Code 内置的时间线功能有一个局限：它只能显示你当前打开的文件的历史，如果你的文件丢失（比如由于 git pull 导致的丢失）那就没有办法找回了。</p><p>为了解决这个问题，我发现了一个名为 Local History 的 VS Code 扩展，它可以让你在 VS Code 中查看本地文件的历史版本。</p><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>Visual Studio Code 有时会让你觉得像糖果店里的孩子……有这么多的闪光灯和旋钮要转，有时会让人有点不知所措。我经常觉得我几乎没有触及它广泛的功能集的表面。说到这里，我希望你能从这篇文章中了解到一些关于 Visual Studio Code 的新知识。</p><p>如果你有任何其他 VS Code 的技巧想分享。欢迎在下面的评论中发表，以帮助其他开发者。</p><p>祝编码愉快!</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/2a5412bc286d/</id>
    <link href="https://blog.zisheng.pro/posts/2a5412bc286d/"/>
    <published>2023-04-08T12:08:19.000Z</published>
    <summary>你是否遇到过这样的情况：你修改了本地代码文件，但需要恢复到之前保存的未提交到 Git 存储库中的版本？</summary>
    <title>如何在 VS Code 中找回以前的文件版本</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React-Native/"/>
    <category term="编程" scheme="https://blog.zisheng.pro/categories/%E7%BC%96%E7%A8%8B/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/tags/React-Native/"/>
    <category term="npm" scheme="https://blog.zisheng.pro/tags/npm/"/>
    <category term="rollup" scheme="https://blog.zisheng.pro/tags/rollup/"/>
    <category term="打包" scheme="https://blog.zisheng.pro/tags/%E6%89%93%E5%8C%85/"/>
    <category term="发布" scheme="https://blog.zisheng.pro/tags/%E5%8F%91%E5%B8%83/"/>
    <category term="React Native 插件" scheme="https://blog.zisheng.pro/tags/React-Native-%E6%8F%92%E4%BB%B6/"/>
    <content>
      <![CDATA[<h2 id="安装-rollup"><a href="#安装-rollup" class="headerlink" title="安装 rollup"></a>安装 rollup</h2><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add -D rollup</span><br></pre></td></tr></table></figure><h2 id="package-json"><a href="#package-json" class="headerlink" title="package.json"></a>package.json</h2><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;react-native-refined-components&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;0.1.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;refined react-native components&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;main&quot;</span><span class="punctuation">:</span> <span class="string">&quot;dist/cjs/index.js&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;module&quot;</span><span class="punctuation">:</span> <span class="string">&quot;dist/es/index.js&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;browser&quot;</span><span class="punctuation">:</span> <span class="string">&quot;dist/umd/index.js&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;types&quot;</span><span class="punctuation">:</span> <span class="string">&quot;dist/es/index.d.ts&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;build&quot;</span><span class="punctuation">:</span> <span class="string">&quot;rimraf dist/* &amp;&amp; rollup -c&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;dev&quot;</span><span class="punctuation">:</span> <span class="string">&quot;rollup -c -w&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="rollup配置文件"><a href="#rollup配置文件" class="headerlink" title="rollup配置文件"></a>rollup配置文件</h2><p>在根目录新建 <code>rollup.config.js</code>:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pkg <span class="keyword">from</span> <span class="string">&#x27;./package.json&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> [</span><br><span class="line">  <span class="comment">// browser-friendly UMD build</span></span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">input</span>: <span class="string">&quot;./src/index.ts&quot;</span>,</span><br><span class="line">    <span class="comment">// 输出</span></span><br><span class="line">    <span class="attr">output</span>: &#123;</span><br><span class="line">      <span class="attr">file</span>: pkg.<span class="property">browser</span>, <span class="comment">// 文件</span></span><br><span class="line">      <span class="attr">format</span>: <span class="string">&#x27;umd&#x27;</span>, <span class="comment">// 格式</span></span><br><span class="line">      <span class="attr">name</span>: <span class="string">&#x27;refined-components&#x27;</span>, <span class="comment">// 生成包名称，代表你的 iife/umd 包</span></span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="comment">// CommonJS (for Node) and ES module (for bundlers) build.</span></span><br><span class="line">  &#123;</span><br><span class="line"><span class="attr">input</span>: <span class="string">&quot;./src/index.ts&quot;</span>,</span><br><span class="line"><span class="attr">output</span>: [</span><br><span class="line">&#123; <span class="attr">file</span>: pkg.<span class="property">main</span>, <span class="attr">format</span>: <span class="string">&#x27;cjs&#x27;</span> &#125;,</span><br><span class="line">&#123; <span class="attr">file</span>: pkg.<span class="property">module</span>, <span class="attr">format</span>: <span class="string">&#x27;es&#x27;</span> &#125;</span><br><span class="line">    ],</span><br><span class="line">&#125;</span><br><span class="line">]</span><br></pre></td></tr></table></figure><h2 id="转换-json-文件为-ES6-modules"><a href="#转换-json-文件为-ES6-modules" class="headerlink" title="转换 .json 文件为 ES6 modules"></a>转换 <code>.json</code> 文件为 ES6 modules</h2><h3 id="安装-rollup-plugin-json"><a href="#安装-rollup-plugin-json" class="headerlink" title="安装 @rollup/plugin-json:"></a>安装 <code>@rollup/plugin-json</code>:</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add -D @rollup/plugin-json</span><br></pre></td></tr></table></figure><h3 id="配置-rollup-config-js"><a href="#配置-rollup-config-js" class="headerlink" title="配置 rollup.config.js"></a>配置 <code>rollup.config.js</code></h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// rollup.config.js</span></span><br><span class="line"><span class="keyword">import</span> json <span class="keyword">from</span> <span class="string">&#x27;@rollup/plugin-json&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> pkg <span class="keyword">from</span> <span class="string">&#x27;./package.json&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> [</span><br><span class="line">  <span class="comment">// browser-friendly UMD build</span></span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">input</span>: <span class="string">&quot;./src/index.ts&quot;</span>,</span><br><span class="line">    <span class="comment">// 输出</span></span><br><span class="line">    <span class="attr">output</span>: &#123;</span><br><span class="line">      <span class="attr">file</span>: pkg.<span class="property">browser</span>, <span class="comment">// 文件</span></span><br><span class="line">      <span class="attr">format</span>: <span class="string">&#x27;umd&#x27;</span>, <span class="comment">// 格式</span></span><br><span class="line">      <span class="attr">name</span>: <span class="string">&#x27;refined-components&#x27;</span>, <span class="comment">// 生成包名称，代表你的 iife/umd 包</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">plugins</span>: [</span><br><span class="line">      <span class="title function_">json</span>(),</span><br><span class="line">    ]</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="comment">// CommonJS (for Node) and ES module (for bundlers) build.</span></span><br><span class="line">  &#123;</span><br><span class="line"><span class="attr">input</span>: <span class="string">&quot;./src/index.ts&quot;</span>,</span><br><span class="line"><span class="attr">output</span>: [</span><br><span class="line">&#123; <span class="attr">file</span>: pkg.<span class="property">main</span>, <span class="attr">format</span>: <span class="string">&#x27;cjs&#x27;</span> &#125;,</span><br><span class="line">&#123; <span class="attr">file</span>: pkg.<span class="property">module</span>, <span class="attr">format</span>: <span class="string">&#x27;es&#x27;</span> &#125;</span><br><span class="line">    ],</span><br><span class="line">    <span class="attr">plugins</span>: [<span class="title function_">json</span>()]</span><br><span class="line">&#125;</span><br><span class="line">]</span><br></pre></td></tr></table></figure><h2 id="加载并解析-CommonJS-模块"><a href="#加载并解析-CommonJS-模块" class="headerlink" title="加载并解析 CommonJS 模块"></a>加载并解析 CommonJS 模块</h2><p>我们写组件库或工具库时不可避免会用到外部库，这些外部库可能是符合 CommonJS 规范的。而 Rollup 力图实现 ES 模块的规范， 因此，加载 CommonJS 模块和使用 Node 模块位置解析逻辑都被实现为可选插件，默认情况下不在 Rollup 内核中。我们需要安装并配置 <a href="https://github.com/rollup/plugins/tree/master/packages/commonjs">CommonJS</a> 和 <a href="https://github.com/rollup/plugins/tree/master/packages/node-resolve">node-resolve</a> 插件。</p><h3 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add -D @rollup/plugin-node-resolve @rollup/plugin-commonjs</span><br></pre></td></tr></table></figure><h3 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h3><p>一般我们打的 cjs 和 esm 格式文件需要把第三放包打进来</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// rollup.config.js</span></span><br><span class="line"><span class="keyword">import</span> commonjs <span class="keyword">from</span> <span class="string">&#x27;@rollup/plugin-commonjs&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; nodeResolve &#125; <span class="keyword">from</span> <span class="string">&#x27;@rollup/plugin-node-resolve&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> pkg <span class="keyword">from</span> <span class="string">&#x27;./package.json&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> [</span><br><span class="line">  <span class="comment">// browser-friendly UMD build</span></span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">input</span>: <span class="string">&quot;./src/index.ts&quot;</span>,</span><br><span class="line">    <span class="comment">// 输出</span></span><br><span class="line">    <span class="attr">output</span>: &#123;</span><br><span class="line">      <span class="attr">file</span>: <span class="string">&quot;./dist/umd/index.js&quot;</span>, <span class="comment">// 文件</span></span><br><span class="line">      <span class="attr">format</span>: <span class="string">&#x27;umd&#x27;</span>, <span class="comment">// 格式</span></span><br><span class="line">      <span class="attr">name</span>: <span class="string">&#x27;refined-components&#x27;</span>, <span class="comment">// 生成包名称，代表你的 iife/umd 包</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">plugins</span>: [</span><br><span class="line">      <span class="title function_">json</span>(),</span><br><span class="line">      <span class="title function_">commonjs</span>(), <span class="comment">// 加载 commonjs 模块</span></span><br><span class="line">      <span class="title function_">nodeResolve</span>() <span class="comment">// 将 commonjs 转换为 ES 模块</span></span><br><span class="line">    ]</span><br><span class="line">  &#125;</span><br><span class="line">]</span><br></pre></td></tr></table></figure><blockquote><p>注意：CommonJS 和 ES module 格式下我们一般不希望把第三方库打包到输出产物中，所以并不需要配置这两个插件。</p></blockquote><h2 id="忽略-warning-treating-module-as-external-dependency"><a href="#忽略-warning-treating-module-as-external-dependency" class="headerlink" title="忽略 warning-treating-module-as-external-dependency"></a>忽略 <code>warning-treating-module-as-external-dependency</code></h2><p>Rollup 默认只会解析相对路径的模块，像是 <code>import _ from &#39;lodash&#39;</code> 不会被打包进 bundle，并且打包时会有警告。如果你想忽略这些警告，你需要在 <code>external</code> 中指明这些外部模块。那么有没有更优雅的方式呢？答案是肯定的，我们只需要安装并配置 <code>rollup-plugin-node-externals</code> 插件即可。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add -D rollup-plugin-node-externals</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// rollup.config.js</span></span><br><span class="line"><span class="keyword">import</span> externals <span class="keyword">from</span> <span class="string">&#x27;rollup-plugin-node-externals&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> [</span><br><span class="line">  <span class="comment">// CommonJS (for Node) and ES module (for bundlers) build.</span></span><br><span class="line">  &#123;</span><br><span class="line"><span class="attr">output</span>: [</span><br><span class="line">&#123;</span><br><span class="line">        <span class="attr">file</span>: <span class="string">&#x27;./dist/cjs/index.js&#x27;</span>,</span><br><span class="line">        <span class="attr">format</span>: <span class="string">&#x27;cjs&#x27;</span>,</span><br><span class="line">        <span class="attr">exports</span>: <span class="string">&#x27;auto&#x27;</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">&#123;</span><br><span class="line">        <span class="attr">file</span>: <span class="string">&#x27;./dist/es/index.js&#x27;</span>,</span><br><span class="line">        <span class="attr">format</span>: <span class="string">&#x27;es&#x27;</span></span><br><span class="line">      &#125;</span><br><span class="line">    ],</span><br><span class="line">    <span class="attr">plugins</span>: [</span><br><span class="line">      <span class="title function_">externals</span>(&#123;<span class="attr">deps</span>: <span class="literal">true</span>&#125;),</span><br><span class="line">    ]</span><br><span class="line">&#125;</span><br><span class="line">]</span><br></pre></td></tr></table></figure><h2 id="打包-ts-文件"><a href="#打包-ts-文件" class="headerlink" title="打包 ts 文件"></a>打包 ts 文件</h2><h3 id="安装依赖"><a href="#安装依赖" class="headerlink" title="安装依赖"></a>安装依赖</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add -D rollup-plugin-typescript2 typescript</span><br></pre></td></tr></table></figure><h3 id="配置-1"><a href="#配置-1" class="headerlink" title="配置"></a>配置</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> typescript <span class="keyword">from</span> <span class="string">&#x27;rollup-plugin-typescript2&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> [</span><br><span class="line">  <span class="comment">// browser-friendly UMD build</span></span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">input</span>: <span class="string">&quot;./src/index.ts&quot;</span>,</span><br><span class="line">    <span class="comment">// 输出</span></span><br><span class="line">    <span class="attr">output</span>: &#123;</span><br><span class="line">      <span class="attr">file</span>: <span class="string">&#x27;./dist/umd/index.js&#x27;</span>, <span class="comment">// 文件</span></span><br><span class="line">      <span class="attr">format</span>: <span class="string">&#x27;umd&#x27;</span>, <span class="comment">// 格式</span></span><br><span class="line">      <span class="attr">name</span>: <span class="string">&#x27;refined-components&#x27;</span>, <span class="comment">// 生成包名称，代表你的 iife/umd 包</span></span><br><span class="line">      <span class="attr">globals</span>: &#123;</span><br><span class="line">        <span class="string">&#x27;react&#x27;</span>: <span class="string">&#x27;React&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;react-native&#x27;</span>: <span class="string">&#x27;reactNative&#x27;</span></span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="attr">sourcemap</span>: <span class="literal">true</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">plugins</span>: [</span><br><span class="line">      <span class="comment">// 如果用了 rollup-plugin-node-resolve， 则必须将它放在 typescript 插件前面</span></span><br><span class="line">      <span class="title function_">typescript</span>(&#123;</span><br><span class="line">        <span class="attr">tsconfigOverride</span>: &#123;</span><br><span class="line">          <span class="attr">compilerOptions</span>: &#123; <span class="attr">declaration</span>: <span class="literal">false</span> &#125;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;),</span><br><span class="line">    ],</span><br><span class="line">    <span class="attr">external</span>: [<span class="string">&quot;react&quot;</span>,<span class="string">&quot;react-native&quot;</span>],</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="comment">// CommonJS (for Node) and ES module (for bundlers) build.</span></span><br><span class="line">  &#123;</span><br><span class="line"><span class="attr">input</span>: <span class="string">&#x27;./src/index.ts&#x27;</span>,</span><br><span class="line"><span class="attr">output</span>: [</span><br><span class="line">&#123; <span class="attr">dir</span>: <span class="string">&#x27;./dist/cjs/index.js&#x27;</span>, <span class="attr">format</span>: <span class="string">&#x27;cjs&#x27;</span>, <span class="attr">exports</span>: <span class="string">&#x27;auto&#x27;</span> &#125;,</span><br><span class="line">&#123; <span class="attr">dir</span>: <span class="string">&#x27;./dist/es/index.js&#x27;</span>, <span class="attr">format</span>: <span class="string">&#x27;es&#x27;</span> &#125;</span><br><span class="line">    ],</span><br><span class="line">    <span class="attr">plugins</span>: [</span><br><span class="line">      <span class="title function_">typescript</span>(),</span><br><span class="line">    ],</span><br><span class="line">    <span class="attr">external</span>: [<span class="string">&quot;react&quot;</span>,<span class="string">&quot;react-native&quot;</span>],</span><br><span class="line">&#125;</span><br><span class="line">]</span><br></pre></td></tr></table></figure><h3 id="tsconfig-json"><a href="#tsconfig-json" class="headerlink" title="tsconfig.json"></a>tsconfig.json</h3><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;compilerOptions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;target&quot;</span><span class="punctuation">:</span> <span class="string">&quot;es5&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;module&quot;</span><span class="punctuation">:</span> <span class="string">&quot;esnext&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;declaration&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;jsx&quot;</span><span class="punctuation">:</span> <span class="string">&quot;react&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;strict&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;noImplicitAny&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;skipLibCheck&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;moduleResolution&quot;</span><span class="punctuation">:</span> <span class="string">&quot;node&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;allowSyntheticDefaultImports&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;esModuleInterop&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;resolveJsonModule&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;exclude&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;dist&quot;</span><span class="punctuation">,</span><span class="string">&quot;rollup.config.js&quot;</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="rollup-plugin-multi-input-使用"><a href="#rollup-plugin-multi-input-使用" class="headerlink" title="rollup-plugin-multi-input 使用"></a>rollup-plugin-multi-input 使用</h2><p>组件库比较大时，我们可能需要让我们的库支持 tree-shaking。那么你就不能将所有的文件都打入到一个文件中。<code>rollup-plugin-multi-input</code> 便是一个将打包产物输出到各自的文件中的插件。就像：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">src</span><br><span class="line">  - A.ts</span><br><span class="line">  - B.ts</span><br><span class="line">  - index.ts</span><br><span class="line">-&gt;</span><br><span class="line">dist</span><br><span class="line">  - A.js</span><br><span class="line">  - B.js</span><br><span class="line">  - index.js</span><br></pre></td></tr></table></figure><h3 id="安装依赖-1"><a href="#安装依赖-1" class="headerlink" title="安装依赖"></a>安装依赖</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add -D rollup-plugin-multi-input</span><br></pre></td></tr></table></figure><h3 id="配置-2"><a href="#配置-2" class="headerlink" title="配置"></a>配置</h3><blockquote><p>注意：因为产物是多文件，所以需要用 <code>dir</code> 属性指定文件夹。</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> multiInput <span class="keyword">from</span> <span class="string">&#x27;rollup-plugin-multi-input&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> [</span><br><span class="line">  ...</span><br><span class="line">  <span class="comment">// CommonJS (for Node) and ES module (for bundlers) build.</span></span><br><span class="line">  &#123;</span><br><span class="line"><span class="attr">input</span>: [<span class="string">&#x27;src/**/*.ts&#x27;</span>,<span class="string">&#x27;src/**/*.tsx&#x27;</span>],</span><br><span class="line"><span class="attr">output</span>: [</span><br><span class="line">&#123; <span class="attr">dir</span>: <span class="string">&#x27;./src&#x27;</span>, <span class="attr">format</span>: <span class="string">&#x27;cjs&#x27;</span>, <span class="attr">exports</span>: <span class="string">&#x27;auto&#x27;</span> &#125;,</span><br><span class="line">&#123; <span class="attr">dir</span>: <span class="string">&#x27;./src&#x27;</span>, <span class="attr">format</span>: <span class="string">&#x27;es&#x27;</span> &#125;</span><br><span class="line">    ],</span><br><span class="line">    <span class="attr">plugins</span>: [</span><br><span class="line">      <span class="title function_">multiInput</span>(),</span><br><span class="line">    ],</span><br><span class="line">&#125;</span><br><span class="line">]</span><br></pre></td></tr></table></figure><h2 id="其他插件"><a href="#其他插件" class="headerlink" title="其他插件"></a>其他插件</h2><ul><li>rollup-plugin-progress: 打包进度条</li><li>rollup-plugin-terser: 压缩文件</li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/74c90b834cdf/</id>
    <link href="https://blog.zisheng.pro/posts/74c90b834cdf/"/>
    <published>2023-04-08T11:28:25.000Z</published>
    <summary>本文接绍了如何使用 rollup 打包 React Native 插件并发布的流程和使用到的 rollup 插件</summary>
    <title>使用 rollup 打包 React Native 插件并发布</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/categories/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="掘金翻译计划" scheme="https://blog.zisheng.pro/categories/%E6%8E%98%E9%87%91%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="NodeJs" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/NodeJs/"/>
    <category term="Node.js" scheme="https://blog.zisheng.pro/tags/Node-js/"/>
    <category term="Hapi.js" scheme="https://blog.zisheng.pro/tags/Hapi-js/"/>
    <category term="Express.js" scheme="https://blog.zisheng.pro/tags/Express-js/"/>
    <category term="Nest.js" scheme="https://blog.zisheng.pro/tags/Nest-js/"/>
    <category term="Koa.js" scheme="https://blog.zisheng.pro/tags/Koa-js/"/>
    <category term="Socket.io" scheme="https://blog.zisheng.pro/tags/Socket-io/"/>
    <category term="Meteor.js" scheme="https://blog.zisheng.pro/tags/Meteor-js/"/>
    <category term="Adonis.js" scheme="https://blog.zisheng.pro/tags/Adonis-js/"/>
    <category term="Sails.js" scheme="https://blog.zisheng.pro/tags/Sails-js/"/>
    <content>
      <![CDATA[<blockquote><ul><li>原文地址：<a href="https://javascript.plainenglish.io/top-node-js-frameworks-to-use-in-2021-4951ee5940b8">Top Node.js Frameworks to use in 2021</a></li><li>原文作者：<a href="https://medium.com/@ronakataglowid">Ronak Patel</a></li><li>译文出自：<a href="https://github.com/xitu/gold-miner">掘金翻译计划</a></li><li>本文永久链接：<a href="https://github.com/xitu/gold-miner/blob/master/article/2021/top-node-js-frameworks-to-use-in-2021.md">https://github.com/xitu/gold-miner/blob/master/article/2021/top-node-js-frameworks-to-use-in-2021.md</a></li><li>译者：<a href="https://github.com/youngjuning">紫升</a></li><li>校对者：<a href="https://github.com/PassionPenguin">PassionPenguin</a>、<a href="https://github.com/xilihuasi">xilihuasi</a></li></ul></blockquote><p>Node.js 是最敏捷的服务端 web 应用平台，因为它为应用开发公司提供了构建可扩展的单一编程语言 web 平台的便利。它是最热门的开源的 JavaScript 运行时框架之一，具有跨平台属性，让我们可以在浏览器以外的环境运行代码。</p><h2 id="Node-js-的特别之处是什么？"><a href="#Node-js-的特别之处是什么？" class="headerlink" title="Node.js 的特别之处是什么？"></a>Node.js 的特别之处是什么？</h2><ol><li>它有一个干净简洁的代码库。</li><li>它最适合敏捷开发和原型设计。</li><li>它有一个庞大的生态系统以提供开源库。</li><li>它可以用于更快地开发企业级可扩展的应用程序。</li><li>它基于最常用的编程语言 —— JavaScript。</li></ol><h2 id="市场对-Node-js-的反应如何？"><a href="#市场对-Node-js-的反应如何？" class="headerlink" title="市场对 Node.js 的反应如何？"></a>市场对 Node.js 的反应如何？</h2><p><img src="https://cdn-images-1.medium.com/max/2366/1*4kohl3kB8tfsqcJY-f3xlA.png" alt="NPM 趋势"></p><p>图片来源：<a href="https://www.npmtrends.com/nodejs">npmtrends</a></p><p><strong>下面是一些关于 Node.js 的更多信息：</strong></p><ul><li>Amazon、Netflix、LinkedIn、eBay、PayPal 和 Reddit 使用 Node.js 作为他们的后端框架。</li><li>43% 的 Node.js 开发者使用这个框架创建企业级应用</li><li>Paypal 注意到改用 Node.js 后，平均响应时间显著缩短了 35%。</li><li>Node.js 可以将任何现有应用的性能提高 50%。</li><li>Node.js 降低了 58% 的应用开发成本。</li><li>Statista 提到，截至 2020 年初，Node 是开发者中使用最多的框架，占 51.4%。</li></ul><h2 id="使用-Node-js-框架的收益"><a href="#使用-Node-js-框架的收益" class="headerlink" title="使用 Node.js 框架的收益"></a>使用 Node.js 框架的收益</h2><ul><li>易扩展性</li><li>学习曲线低</li><li>JavaScript 全栈开发</li><li>庞大而活跃的社区</li><li>使用单一的代码库，以提高整体 web 性能。</li></ul><h2 id="2021-年最值得用的-Node-js-框架"><a href="#2021-年最值得用的-Node-js-框架" class="headerlink" title="2021 年最值得用的 Node.js 框架"></a>2021 年最值得用的 Node.js 框架</h2><p>我们已经介绍了 Node.js，并详细地了解了它的功能，现在我们可以讨论 2021 年最值得使用的 Node.js 框架啦。</p><h3 id="1-Hapi-js"><a href="#1-Hapi-js" class="headerlink" title="1. Hapi.js"></a>1. Hapi.js</h3><ul><li><strong>GitHub</strong><ul><li><strong>Stars</strong>：13.1k</li><li><strong>Forks</strong>：1.3k</li><li><strong>版本</strong>：v20.1.0</li><li><strong>贡献者</strong>：208</li><li><strong>使用人数</strong>：16.5k</li></ul></li><li><strong>NPM 周下载量</strong>：105,065</li><li><strong>License</strong>：BSD-3-Clause</li></ul><p><img src="https://cdn-images-1.medium.com/max/2316/1*bnq7cugj6hQYaLYlY2a53w.png" alt="Hapi.js 框架流行度"></p><p>Hapi.js 是众多开发者信赖的最简单、安全、可靠的框架之一。你可以使用 Hapi.js 来创建可扩展和健壮的应用程序，它具有最小的开销和开箱即用的功能。它是开发 JSON API 的顶级<a href="https://aglowiditsolutions.com/blog/hidden-cost-of-app-development/">Node.js</a>框架。</p><p><strong>Hapi.js 可以被用于：</strong></p><ul><li>网站</li><li>HTTP 代理应用</li><li>应用程序接口服务</li></ul><p><strong>Hapi.js 主要特性：</strong></p><ul><li>输入验证</li><li>日志</li><li>错误处理</li><li>代码可重用性</li><li>缓存</li><li>没有外部依赖</li><li>基于配置的功能</li><li>集成框架：在 Node 框架中提供全面的认证和授权 API 支持。</li></ul><p><strong>什么时候使用 Hapi.js：</strong></p><p>Hapi.js 是开发安全、实时、可扩展和社交媒体应用的理想选择。大多数移动应用开发者都喜欢用 Hapi.js 来创建代理和 API 服务器。</p><p><strong>谁在使用 Hapi.js：</strong></p><ul><li>Commercetools</li><li>Main Stack</li><li>Beam</li><li>Taggun</li><li>Artifakt</li></ul><h3 id="2-Express-js"><a href="#2-Express-js" class="headerlink" title="2. Express.js"></a>2. Express.js</h3><ul><li><strong>GitHub</strong><ul><li><strong>Stars</strong>：52.3k</li><li><strong>Forks</strong>：8.8k</li><li><strong>版本</strong>：v4.17.1</li><li><strong>贡献者</strong>：262</li><li><strong>使用人数</strong>：9.2m</li></ul></li><li><strong>NPM 周下载量</strong>：17,193,915</li><li><strong>License</strong>：MIT</li></ul><p><img src="https://cdn-images-1.medium.com/max/2080/1*oV-DuccRP7WvxgSabkTNqQ.png" alt="Express.js 流行度"></p><p>Express.js 是一个灵活而简约的 Node.js 应用框架。这个插件并不是围绕着特定的组件构建的，因此它并不限制你使用什么技术。这就给了开发者尝试的自由。他们还可以获得闪电般的配置和纯 JavaScript 体验，这些特性使 Express.js 成为快速原型设计和敏捷开发市场的有力竞争者。</p><p><strong>Express.js 可以被用于：</strong></p><ul><li>单页应用</li><li>多页应用</li><li>混合应用</li></ul><p><strong>Express.js 主要特性：</strong></p><ul><li>更快的服务端开发</li><li>赋能开发者更快地构建 RESTful API</li><li>Express 支持 MVC 架构，但需要开发者做一些额外工作</li><li>开箱支持 NoSQL 数据库</li></ul><p><strong>什么时候使用 Express.js：</strong></p><p>Express.js 是快速创建 Web 应用程序和服务的理想选择，因为它有现成的 API 生成工具。它是基于 JavaScript 的全栈方案 MEAN 的一部分。这意味着你可以使用 Express.js 来制作任何基于浏览器的企业级应用。</p><p><strong>谁在使用 Express.js：</strong></p><ul><li>FindHotel</li><li>Omnipresent</li><li>Okay</li><li>SiHub</li><li>TheDoe</li></ul><h3 id="3-Nest-js"><a href="#3-Nest-js" class="headerlink" title="3. Nest.js"></a>3. Nest.js</h3><ul><li><strong>Github</strong><ul><li><strong>Stars</strong>：35.5k</li><li><strong>Forks</strong>：3.4k</li><li><strong>版本</strong>：7.5.0</li><li><strong>贡献者</strong>：226</li><li><strong>使用人数</strong>：60.4k</li></ul></li><li><strong>NPM 周下载量</strong>：508,214</li><li><strong>License</strong>：MIT</li></ul><p><img src="https://cdn-images-1.medium.com/max/2000/1*uTZrckIaqRi2BolefxaVTg.png" alt="Nest.js 流行度"></p><p>Nest.js 是一个服务器端应用框架，它是为了解放开发者的生产力，让他们的生活变得更轻松而打造的。开发者通常为了更好地组织和管理代码而使用这个 Node.js 框架。</p><p><strong>Nest.js 可以被用于：</strong></p><ul><li>编写更清晰和可重用的代码。</li><li>编写具有更高层次结构的代码，如拦截器、过滤器、管道等；</li><li>编写可扩展、可测试和松散型应用程序。</li></ul><p><strong>Nest.js 主要特性</strong></p><ul><li>易于扩展：可与其他库一起使用。</li><li>允许开发人员完全使用纯 JavaScript 进行编码。</li><li>结合了函数式编程、面向对象编程和响应式编程的特点。</li><li>公开框架 API，帮助开发者使用各平台上的各种第三方模块。</li><li>它有一个详细且维护良好的文档。</li></ul><p><strong>什么时候使用 Nest.js：</strong></p><p>Nest.js 主要用于编写具有可扩展、可测试和松散耦合特点的应用。它将 Node.js 的扩展潜力提高到了一个全新的水平。它提供了结构和灵活性的适当平衡，可以高效地管理你的大型项目的代码，并且仍然有结构感可循。</p><p><strong>谁在使用 Nest.js：</strong></p><ul><li>Roche</li><li>Adidas</li><li>Decathlon</li><li>Capgemini</li></ul><h3 id="4-Koa-js"><a href="#4-Koa-js" class="headerlink" title="4. Koa.js"></a>4. Koa.js</h3><ul><li><strong>GitHub</strong><ul><li><strong>Stars</strong>：30.9k</li><li><strong>Forks</strong>：3k</li><li><strong>版本</strong>：2.13.1</li><li><strong>贡献者</strong>：219</li><li><strong>使用人数</strong>：160k</li></ul></li><li><strong>NPM 周下载量</strong>：870,944</li><li><strong>License</strong>：MIT</li></ul><p><img src="https://cdn-images-1.medium.com/max/2078/1*lKpRVNszZ6gVbTF_blVJRQ.png" alt="Koa.js 框架流行度"></p><p>Koa.js 是一个开源的 Node web 框架，由 Express.js 原班人马创建。通过 Koa，他们的目标是为 Web 应用和 API 创建一个更小、更有价值、更强大的平台。它提供了多种高效的方法，以让构建服务的过程更快速。</p><p><strong>Koa.js 可以被用于：</strong></p><ul><li>前台系统</li><li>后台系统</li><li>混合系统</li></ul><p><strong>Koa.js 主要特性：</strong></p><ul><li>代表现代和未来</li><li>与所有 Node.js 框架相比，体积更小。</li><li>有一个内置的错误捕捉器，防止网站崩溃。</li><li>使用 context 对象，该对象同时拥有请求和响应对象。</li></ul><p><strong>什么时候使用 Koa.js：</strong></p><p>Koa.js 最适合用于创建服务器、路由、处理响应和处理错误。</p><p><strong>谁在使用 Koa.js：</strong></p><ul><li>Paralect</li><li>LetzChange</li><li>BrainHub</li><li>Bulb</li></ul><h3 id="5-Socket-io"><a href="#5-Socket-io" class="headerlink" title="5. Socket.io"></a>5. Socket.io</h3><ul><li><strong>GitHub</strong><ul><li><strong>Stars</strong>：52.7k</li><li><strong>Forks</strong>：9.6k</li><li><strong>版本</strong>：4.0.0</li><li><strong>贡献者</strong>：189</li><li><strong>使用人数</strong>：2.2m</li></ul></li><li><strong>NPM 周下载量</strong>：3,617,636</li><li><strong>License</strong>：MIT</li></ul><p><img src="https://cdn-images-1.medium.com/max/2046/1*5zUKf_Dt48W00aWfkAs84g.png" alt="socket.io 流行度"></p><p>Socket.io 是用来在客户端和服务器端之间创建实时双向通信的框架。要做到这一点，客户端需要在浏览器中安装 Socket.io，服务器也要集成 Socket.io 包。这使得数据可以在数百万种形式中共享。然而，最受欢迎的方法仍然是 JSON。</p><p><strong>Socket.io 由以下两个部分组成：</strong></p><ol><li>JavaScript 服务：Node.js</li><li>JavaScript 客户端库：Node.js</li></ol><blockquote><p><strong>注意：</strong> Socket.io 还兼容许多其他语言，如 Java、C+、Swift、Dart、.Net 和 Python。</p></blockquote><p><strong>Socket.io 可以被用于：</strong></p><ul><li>各种命名空间</li><li>广播</li><li>事件处理</li><li>错误处理</li><li>日志和调试</li><li>聊天应用</li><li>内部</li></ul><p><strong>Socket.io 主要特性：</strong></p><ul><li>将信息编码为命名的 JSON 或二进制事件。</li><li>在应用程序中添加“实时”能力。</li><li>支持自动重新连接</li><li>出色的速度和可靠性</li><li>即时通讯和聊天</li></ul><p><strong>什么时候使用 Socket.io：</strong></p><p>Socket.io 是最好的基于事件的实时双向通信工具之一。任何想要在应用中添加实时分析功能的人都应该使用它。Socket.io 对于实时游戏应用也很有用。在实时游戏中使用基本的 HTTP 或 HTTPS 协议是不可行的，因为这些文件很大，建立通信需要时间。在这里，我们使用体积更小的 socket 包，几乎是实时地完成工作，以获得更流畅和更好的体验。</p><p><strong>谁在使用 Socket.io：</strong></p><ul><li>Alibaba Travels</li><li>Patreon</li><li>Trello</li><li>Justmop</li><li>Plaid</li></ul><h3 id="6-Meteor-js"><a href="#6-Meteor-js" class="headerlink" title="6. Meteor.js"></a>6. Meteor.js</h3><ul><li><strong>Github</strong><br>— <strong>Stars</strong>：42.3k<ul><li><strong>Forks</strong>：5.2k</li><li><strong>版本</strong>：4.0.0</li><li><strong>贡献者</strong>：452</li></ul></li><li>NPM：不可用</li><li><strong>License</strong>：MIT</li></ul><p>Meteor.js 是一个开源的全栈 JavaScript 平台，JavaScript 根据意图不同运行在不同的地方。JavaScript 运行在 Web 浏览器内部；然后 JavaScript 运行在 Node.js 容器内的 Meteor 服务器上，支持 HTML 片段、静态资源和 CSS 规则。</p><p><strong>Meteor.js 可以被用于：</strong></p><ul><li>移动应用程序全流程</li><li>web 应用程序全流程</li></ul><p><strong>Meteor.js 主要特性：</strong></p><ul><li>纯 JavaScript</li><li>干净、稳健的数据同步</li><li>互操作性</li><li>智能套件</li><li>代码热更新</li></ul><p><strong>什么时候使用 Meteor.js：</strong></p><p>Meteor.js 具有快速原型设计的能力，并能生成跨平台（Android、iOS、Web）的代码。它也是最直接的学习框架之一，因为它不遵循任何严格的结构规则。因此，Meteor.js 应该被任何希望以最少的学习曲线为多个平台创建应用程序的初级或中级开发人员使用。</p><p><strong>谁在使用 Meteor.js：</strong></p><ul><li>Accenture</li><li>NetApp</li><li>Rocket Chat</li><li>Esri</li><li>NordStorm</li></ul><h3 id="7-Adonis-js"><a href="#7-Adonis-js" class="headerlink" title="7. Adonis.js"></a>7. Adonis.js</h3><ul><li><strong>Github</strong><ul><li><strong>Stars</strong>：9.7k</li><li><strong>Forks</strong>：498</li><li><strong>版本</strong>：5.0.13</li><li><strong>贡献者</strong>：47</li><li><strong>使用人数</strong>：754</li></ul></li><li><strong>周下载数</strong>：3808</li><li><strong>License</strong>：MIT</li></ul><p><img src="https://cdn-images-1.medium.com/max/2052/1*QVu2Z2wbudWv06tBwkJ8mQ.png" alt="adoni.js 流行度"></p><p>Adonis.js 是一个 <a href="https://aglowiditsolutions.com/blog/hidden-cost-of-app-development/">Node.js</a> 的 MVC 框架，可以运行在所有的操作系统上。它为编写服务器端 Web 应用程序提供了一个稳定的生态系统，以让开发者专注于业务需求，如最终确定选择或排除哪个包。对于想要换个口味，正在尝试 <a href="https://aglowiditsolutions.com/blog/hidden-cost-of-app-development/">Node.js</a> 框架的 Laravel 开发者来说，它是理想的选择。Adonis.js为 <a href="https://aglowiditsolutions.com/blog/hidden-cost-of-app-development/">Node.js</a> 提供了与Laravel自然具有的相同的功能和能力。</p><p><strong>Adonis.js 可以被用于：</strong></p><ul><li>构建 web 应用</li><li>应用程序接口服务</li></ul><p><strong>Adonis.js 主要特性：</strong></p><ul><li>强大的 ORM，帮助进行安全的 SQL 查询。</li><li>API 和基于会话的认证系统</li><li>验证和给每一个用户的输入做卫生处理。</li><li>高度强调安全问题</li><li>可扩展的应用分层</li></ul><p><strong>什么时候使用 Adonis.js：</strong></p><p>如果你是一个正在寻找 MVC 工具的 <a href="https://aglowiditsolutions.com/blog/hidden-cost-of-app-development/">Node.js</a> 开发者，Adonis.js 是你的首选 Node.js 框架。然而，如果你是一个 Laravel 开发者或任何其他移动应用框架开发者，你仍然可以给 Adonis.js 一个机会，甚至从 PHP 迁移到 <a href="https://aglowiditsolutions.com/blog/hidden-cost-of-app-development/">Node.js</a> 也可以尝试一下 Adonis.js。</p><p><strong>谁在使用 Adonis.js：</strong></p><ul><li>Dotgroup</li><li>DORMshed</li><li>Nina</li><li>Zelo</li><li>FindUp</li></ul><h3 id="8-Sails-js"><a href="#8-Sails-js" class="headerlink" title="8. Sails.js"></a>8. Sails.js</h3><ul><li><strong>Github</strong><ul><li><strong>Stars</strong>：21.9k</li><li><strong>Forks</strong>：1.9k</li><li><strong>版本</strong>：1.4.0</li><li><strong>贡献者</strong>：229</li><li><strong>使用人数</strong>：23.3k</li></ul></li><li><strong>周下载数</strong>：20,457</li><li><strong>License</strong>：MIT</li></ul><p><img src="https://cdn-images-1.medium.com/max/2000/1*nYu5aJOd4f933c6WZkX8eA.png" alt="sails.js 框架流行度"></p><p>Sails.js 是又一个实时 <a href="https://aglowiditsolutions.com/blog/hidden-cost-of-app-development/">Node.js</a> MVC 框架。它基于 Express 构建，其 MVC 架构与 Ruby on Rails 相似。它与 Ruby on Rails 的不同之处在于，它提供了对更现代的、以数据为中心的 API 和 Web 应用开发风格的支持。</p><p><strong>Sails.js 可以被用于：</strong></p><ul><li>构建企业级 <a href="https://aglowiditsolutions.com/blog/hidden-cost-of-app-development/">Node.js</a> 应用</li><li>构建前端应用</li><li>构建处理 HTTP 请求的后端应用</li></ul><p><strong>Sails.js 主要特性：</strong></p><ul><li>支持自动生成 REST APIs</li><li>具有简单的 WebSocket 集成</li><li>兼容任何流行的前端框架：Angular、Android、React、iOS、Windows 或任何自定义硬件。</li><li>它还具有实时功能支持</li></ul><p><strong>什么时候使用 Sails.js：</strong></p><p>任何想要一个模拟 MVC 模式的 <a href="https://aglowiditsolutions.com/blog/hidden-cost-of-app-development/">Node.js</a> 框架（如 Laravel 和 Ruby on Rails）、想要实现现代应用架构，并构建以数据为中心的 API 和实时应用的开发者都应该在他们的下一个项目中使用 Sails.js。</p><p><strong>谁在使用 Sails.js：</strong></p><ul><li>Tutor Platform</li><li>Redox Engine</li><li>Brainhub</li><li>Created Informed</li><li>People Grove</li></ul><h2 id="总结一下"><a href="#总结一下" class="headerlink" title="总结一下"></a>总结一下</h2><p>市场上有很多新的 <a href="https://aglowiditsolutions.com/blog/hidden-cost-of-app-development/">Node.js</a> 框架。不同的 <a href="https://aglowiditsolutions.com/blog/hidden-cost-of-app-development/">Node.js</a> 框架会在不同阶段帮助你开发项目，并带来很多价值和功能。合理利用这些框架，妈妈再也不用担心你的应用开发啦。</p><blockquote><p>如果发现译文存在错误或其他需要改进的地方，欢迎到 <a href="https://github.com/xitu/gold-miner">掘金翻译计划</a> 对译文进行修改并 PR，也可获得相应奖励积分。文章开头的 <strong>本文永久链接</strong> 即为本文在 GitHub 上的 MarkDown 链接。</p></blockquote><hr><blockquote><p><a href="https://github.com/xitu/gold-miner">掘金翻译计划</a> 是一个翻译优质互联网技术文章的社区，文章来源为 <a href="https://juejin.im/">掘金</a> 上的英文分享文章。内容覆盖 <a href="https://github.com/xitu/gold-miner#android">Android</a>、<a href="https://github.com/xitu/gold-miner#ios">iOS</a>、<a href="https://github.com/xitu/gold-miner#%E5%89%8D%E7%AB%AF">前端</a>、<a href="https://github.com/xitu/gold-miner#%E5%90%8E%E7%AB%AF">后端</a>、<a href="https://github.com/xitu/gold-miner#%E5%8C%BA%E5%9D%97%E9%93%BE">区块链</a>、<a href="https://github.com/xitu/gold-miner#%E4%BA%A7%E5%93%81">产品</a>、<a href="https://github.com/xitu/gold-miner#%E8%AE%BE%E8%AE%A1">设计</a>、<a href="https://github.com/xitu/gold-miner#%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD">人工智能</a>等领域，想要查看更多优质译文请持续关注 <a href="https://github.com/xitu/gold-miner">掘金翻译计划</a>、<a href="http://weibo.com/juejinfanyi">官方微博</a>、<a href="https://zhuanlan.zhihu.com/juejinfanyi">知乎专栏</a>。</p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/76292c0fc52d/</id>
    <link href="https://blog.zisheng.pro/posts/76292c0fc52d/"/>
    <published>2023-04-08T11:22:08.000Z</published>
    <summary>Node.js 是最敏捷的服务端 web 应用平台，因为它为应用开发公司提供了构建可扩展的单一编程语言 web 平台的便利。它是最热门的开源的 JavaScript 运行时框架之一，具有跨平台属性，让我们可以在浏览器以外的环境运行代码。</summary>
    <title>2023 年最值得使用的 Node.js 框架</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="加密货币" scheme="https://blog.zisheng.pro/categories/%E5%8A%A0%E5%AF%86%E8%B4%A7%E5%B8%81/"/>
    <category term="区块链" scheme="https://blog.zisheng.pro/categories/%E5%8A%A0%E5%AF%86%E8%B4%A7%E5%B8%81/%E5%8C%BA%E5%9D%97%E9%93%BE/"/>
    <category term="DOGE" scheme="https://blog.zisheng.pro/tags/DOGE/"/>
    <category term="Twitter" scheme="https://blog.zisheng.pro/tags/Twitter/"/>
    <category term="狗狗币" scheme="https://blog.zisheng.pro/tags/%E7%8B%97%E7%8B%97%E5%B8%81/"/>
    <category term="推特" scheme="https://blog.zisheng.pro/tags/%E6%8E%A8%E7%89%B9/"/>
    <category term="埃隆·马斯克" scheme="https://blog.zisheng.pro/tags/%E5%9F%83%E9%9A%86%C2%B7%E9%A9%AC%E6%96%AF%E5%85%8B/"/>
    <category term="马斯克" scheme="https://blog.zisheng.pro/tags/%E9%A9%AC%E6%96%AF%E5%85%8B/"/>
    <content>
      <![CDATA[<div class="note info modern"><p>DOGE 是马斯克的饭票，每到他遇到现金流问题或者特斯拉亏损的时候，就会来喊一嗓子 DOGE。</p></div><p>在 2023 年 4 月 4 日凌晨，推特（Twitter）网页端用户的官方主页键上方发生了变化。取代以往的蓝色小鸟推特标志的是一个柴犬的可爱卡通头像。这一改变引起了人们的注意，而随后，马斯克在推特上发布了一条包含狗狗币表情和推特蓝鸟 Logo 图片的消息。DOGE狗狗币价格也因此出现大幅波动，一度从 0.077 美元涨至 0.102 美元，24 小时涨幅曾达到 30%，创下自去年 12 月以来的最高点。</p><p>在此之前，推特官方没有发布任何关于网页端图标改变的官方声明。直到 4 月 4 日，推特官方仍未对该改变进行任何回复或解释。</p><p>然而，当推特将其网页端图标更改为狗狗币 Logo 时，DOGE 价格开始上涨。据 Lookonchain 监测，排名第五的 DOGE 持有者在4月4日3:48:22转出了价值约 6130 万美元的 6.5 亿枚 DOGE。</p><p>有许多用户猜测，这次网页端图标调整可能只是暂时的，埃隆·马斯克很可能是为了拉高DOGE价格以便抛售而进行的炒作。这种猜测源于 2021 年 1 月 19 日比特币牛市期间马斯克曾将自己的推特用户签名更改为 Bitcoin（比特币），结果导致 BTC 价格从 3.1 万美元飙升至 3.8 万美元，涨幅高达 16%。但是马斯克很快又删除了该签名。</p><p>自 2021 年开始，马斯克多次表达对狗狗币的看好，并公开喊单该加密货币。他称狗狗币是“人民的加密货币”和自己最爱的“货币”，还发布了很多狗狗币的表情包。此外，他也时不时在推特上发布与狗狗币相关的内容，导致狗狗币币价剧烈波动。这样的行为在加密市场已经司空见惯，不足为奇。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1680913456169.png" alt="马斯克公开喊单狗狗币的过往"></p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/e758dae0a86f/</id>
    <link href="https://blog.zisheng.pro/posts/e758dae0a86f/"/>
    <published>2023-04-08T00:28:24.000Z</published>
    <summary>DOGE 是马斯克的饭票，每到他遇到现金流问题或者特斯拉亏损的时候，就会来喊一嗓子 DOGE。</summary>
    <title>一张图解析马斯克过去公开喊单狗狗币的行为历史</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React-Native/"/>
    <category term="编程" scheme="https://blog.zisheng.pro/categories/%E7%BC%96%E7%A8%8B/"/>
    <category term="React Native 动画" scheme="https://blog.zisheng.pro/tags/React-Native-%E5%8A%A8%E7%94%BB/"/>
    <category term="React Native 组件库" scheme="https://blog.zisheng.pro/tags/React-Native-%E7%BB%84%E4%BB%B6%E5%BA%93/"/>
    <category term="React Native Animation Libraries" scheme="https://blog.zisheng.pro/tags/React-Native-Animation-Libraries/"/>
    <content>
      <![CDATA[<p>动画在手机上有很大的影响，它可以创造更好的用户体验，这些动画主要用于与用户的行为互动，使用户更多的参与你的应用程序。</p><p>在技术层面，React Native 为我们提供了强大的声明式 API 去创建动画，通常我们称之为 <code>Animated API</code>。然而，有时你可能想使用一些第三方库来为你处理动画，而不用直接处理 <code>Animated API</code>，本文我们便是探索和讨论 5 个你值得是使用的 React Native 插件。</p><h2 id="重用-React-组件"><a href="#重用-React-组件" class="headerlink" title="重用 React 组件"></a>重用 React 组件</h2><p>使用 <a href="https://bit.dev/">Bit</a> 可以跨平台分享和重用 React 组件。作为一个团队在共享组件上进行协作，可以更敏捷地共同构建应用程序。让 Bit 做繁重的工作，这样你就可以轻松地发布、安装和更新你的个人组件而不需要任何开销。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e1c5f1759c924881ba62904a329645ef~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><h2 id="react-native-animatable"><a href="#react-native-animatable" class="headerlink" title="react-native-animatable"></a>react-native-animatable</h2><p>我是 <a href="https://github.com/oblador/react-native-animatable">react-native-animatable</a> 的忠实粉丝。它为你提供了声明性的包装器，你可以用它来为 React Native 中的元素制作动画，这个库的好处是它的 API 很容易使用，你不需要做任何 linking 操作。所以让我们举个例子，看看这个库是如何工作的😃。</p><p>首先，安装该库！</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn add react-native-animatable</span><br></pre></td></tr></table></figure><p>然后，让我们制作一个 slideInDown 动画效果！</p><p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/abc35f9d7b304742b867c58f3d07f7e5~tplv-k3u1fbpfcp-watermark.image" alt="1_amBoBML8nY2DxpuNC5MbNQ.gif"></p><p>我们的组件看起来如下：</p><figure class="highlight jsx"><figcaption><span>| pure</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span>, &#123;<span class="title class_">Component</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">Text</span>, <span class="title class_">View</span>, <span class="title class_">Dimensions</span>, <span class="title class_">SafeAreaView</span>, <span class="title class_">StyleSheet</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;react-native&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> <span class="title class_">Animatable</span> <span class="keyword">from</span> <span class="string">&#x27;react-native-animatable&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">AnimatableScreen</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Component</span> &#123;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">SafeAreaView</span> <span class="attr">style</span>=<span class="string">&#123;styles.container&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">View</span> <span class="attr">style</span>=<span class="string">&#123;&#123;alignItems:</span> &#x27;<span class="attr">center</span>&#x27;&#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Animatable.View</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">style</span>=<span class="string">&#123;styles.card&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">animation</span>=<span class="string">&quot;slideInDown&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">iterationCount</span>=<span class="string">&#123;5&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">direction</span>=<span class="string">&quot;alternate&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">Text</span> <span class="attr">style</span>=<span class="string">&#123;styles.whiteText&#125;</span>&gt;</span>slideInDown Animation<span class="tag">&lt;/<span class="name">Text</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;/<span class="name">Animatable.View</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">View</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">SafeAreaView</span>&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> styles = <span class="title class_">StyleSheet</span>.<span class="title function_">create</span>(&#123;</span><br><span class="line">  <span class="attr">container</span>: &#123;</span><br><span class="line">    <span class="attr">width</span>: <span class="title class_">Dimensions</span>.<span class="title function_">get</span>(<span class="string">&#x27;screen&#x27;</span>).<span class="property">width</span>,</span><br><span class="line">    <span class="attr">height</span>: <span class="title class_">Dimensions</span>.<span class="title function_">get</span>(<span class="string">&#x27;screen&#x27;</span>).<span class="property">height</span>,</span><br><span class="line">    <span class="attr">justifyContent</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">card</span>: &#123;</span><br><span class="line">    <span class="attr">width</span>: <span class="title class_">Dimensions</span>.<span class="title function_">get</span>(<span class="string">&#x27;screen&#x27;</span>).<span class="property">width</span> * <span class="number">0.6</span>,</span><br><span class="line">    <span class="attr">height</span>: <span class="title class_">Dimensions</span>.<span class="title function_">get</span>(<span class="string">&#x27;screen&#x27;</span>).<span class="property">height</span> * <span class="number">0.35</span>,</span><br><span class="line">    <span class="attr">backgroundColor</span>: <span class="string">&#x27;#206225&#x27;</span>,</span><br><span class="line">    <span class="attr">alignItems</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">    <span class="attr">justifyContent</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">    <span class="attr">borderRadius</span>: <span class="number">8</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">whiteText</span>: &#123;</span><br><span class="line">    <span class="attr">color</span>: <span class="string">&#x27;#ffffff&#x27;</span>,</span><br><span class="line">    <span class="attr">fontSize</span>: <span class="number">18</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">AnimatableScreen</span>;</span><br></pre></td></tr></table></figure><p>我们首先通过 <code>react-native-animatable</code> 导入了 Animatable 对象，然后我们将 Animatable 元素与我们想要动画化的元素结合起来，你可以使用 Image 和 Text 元素，另外你也可以使用 <code>createAnimatableComponent</code> 制作其他元素的动画，你可以查看 <a href="https://github.com/oblador/react-native-animatable">文档</a> 了解更多细节。</p><figure class="highlight jsx"><figcaption><span>| pure</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">MyCustomComponent</span> = <span class="title class_">Animatable</span>.<span class="title function_">createAnimatableComponent</span>(<span class="title class_">MyCustomComponent</span>);</span><br></pre></td></tr></table></figure><p><strong>Props：</strong></p><ul><li><strong>animation</strong>：它接受一个字符串，用来指定我们想要的动画类型，例如：<code>SlideInDown</code>、<code>SlideInLeft</code> 请查看 <a href="https://github.com/oblador/react-native-animatable">docs</a> 来探索你可以使用的动画类型。</li><li><strong>iteractionCount</strong>：它指定了动画应该运行和迭代的次数。我们可以使用 <code>infinite</code> 来使动画永远运行。</li><li><strong>direction</strong>：指定动画的方向，可以使用 <code>normal</code>,<code>alternate-reverse</code>,<code>reverse</code>。</li></ul><p>我们也可以使用其他属性，比如 <code>transition</code> 允许我们定义我们想要动画化的样式属性，如下图所示：</p><figure class="highlight jsx"><figcaption><span>| pure</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span>, &#123;<span class="title class_">Component</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">Text</span>, <span class="title class_">Dimensions</span>, <span class="title class_">SafeAreaView</span>, <span class="title class_">StyleSheet</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;react-native&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> <span class="title class_">Animatable</span> <span class="keyword">from</span> <span class="string">&#x27;react-native-animatable&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">AnimatableScreen</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Component</span> &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">opacity</span>: <span class="number">0</span>,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">componentDidMount</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;<span class="attr">opacity</span>: <span class="number">1</span>&#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">SafeAreaView</span> <span class="attr">style</span>=<span class="string">&#123;styles.container&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Animatable.View</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">style</span>=<span class="string">&#123;[</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">styles.card</span>,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            &#123;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">              <span class="attr">opacity:</span> <span class="attr">this.state.opacity</span>,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            &#125;,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          ]&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">iterationCount</span>=<span class="string">&quot;infinite&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">direction</span>=<span class="string">&quot;reverse&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">duration</span>=<span class="string">&#123;3000&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">transition</span>=<span class="string">&quot;opacity&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Text</span> <span class="attr">style</span>=<span class="string">&#123;styles.whiteText&#125;</span>&gt;</span>Transition Animation<span class="tag">&lt;/<span class="name">Text</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">Animatable.View</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">SafeAreaView</span>&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> styles = <span class="title class_">StyleSheet</span>.<span class="title function_">create</span>(&#123;</span><br><span class="line">  <span class="attr">container</span>: &#123;</span><br><span class="line">    <span class="attr">alignItems</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">    <span class="attr">width</span>: <span class="title class_">Dimensions</span>.<span class="title function_">get</span>(<span class="string">&#x27;screen&#x27;</span>).<span class="property">width</span>,</span><br><span class="line">    <span class="attr">height</span>: <span class="title class_">Dimensions</span>.<span class="title function_">get</span>(<span class="string">&#x27;screen&#x27;</span>).<span class="property">height</span>,</span><br><span class="line">    <span class="attr">justifyContent</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">card</span>: &#123;</span><br><span class="line">    <span class="attr">width</span>: <span class="title class_">Dimensions</span>.<span class="title function_">get</span>(<span class="string">&#x27;screen&#x27;</span>).<span class="property">width</span> * <span class="number">0.6</span>,</span><br><span class="line">    <span class="attr">height</span>: <span class="title class_">Dimensions</span>.<span class="title function_">get</span>(<span class="string">&#x27;screen&#x27;</span>).<span class="property">height</span> * <span class="number">0.35</span>,</span><br><span class="line">    <span class="attr">backgroundColor</span>: <span class="string">&#x27;#206225&#x27;</span>,</span><br><span class="line">    <span class="attr">alignItems</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">    <span class="attr">justifyContent</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">    <span class="attr">borderRadius</span>: <span class="number">8</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">whiteText</span>: &#123;</span><br><span class="line">    <span class="attr">color</span>: <span class="string">&#x27;#ffffff&#x27;</span>,</span><br><span class="line">    <span class="attr">fontSize</span>: <span class="number">18</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">AnimatableScreen</span>;</span><br></pre></td></tr></table></figure><p><a href="https://github.com/oblador/react-native-animatable">react-native-animatable</a> 给我们提供了更多的选项，使我们在动画中拥有更多的控制权，关于这个库还有更多的东西要讲，一篇文章无法涵盖，我真的推荐你去看看文档。</p><h2 id="Lottie"><a href="#Lottie" class="headerlink" title="Lottie"></a>Lottie</h2><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fc00afe5e9ef421aa00ca61a5e7b5e8c~tplv-k3u1fbpfcp-watermark.image" alt="1_audbt0qXd6H5RQl7ebkgeQ.gif"></p><p>这是迄今为止我最喜欢的库，Lottie 是一个由 Airbnb 创建的动画库，它将 After Effect 动画解析成 JSON 文件，你可以将其导出并在你的应用程序中使用，你可以在 ios、Android、Windows、Web 和 React Native 上使用 Lottie。</p><p>在 React Native 中 使用 <a href="https://airbnb.io/lottie/#/">Lottie</a> 你需要安装适配插件 <a href="http://lottie-react-native/">lottie-react-native</a>，它是一个简单的接收 JSON 文件的封装元素：</p><figure class="highlight jsx"><figcaption><span>| pure</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span>, &#123;<span class="title class_">Component</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">StyleSheet</span>, <span class="title class_">SafeAreaView</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;react-native&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">LottieView</span> <span class="keyword">from</span> <span class="string">&#x27;lottie-react-native&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">class</span> <span class="title class_">index</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Component</span> &#123;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">SafeAreaView</span> <span class="attr">style</span>=<span class="string">&#123;styles.container&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">LottieView</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">source</span>=<span class="string">&#123;require(</span>&#x27;<span class="attr">.</span>/<span class="attr">animation.json</span>&#x27;)&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">loop</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">autoPlay</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">style</span>=<span class="string">&#123;&#123;width:</span> <span class="attr">100</span>&#125;&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">SafeAreaView</span>&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> styles = <span class="title class_">StyleSheet</span>.<span class="title function_">create</span>(&#123;</span><br><span class="line">  <span class="attr">container</span>: &#123;</span><br><span class="line">    <span class="attr">alignItems</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>你可以查看 <a href="https://lottiefiles.com/%EF%BC%8C%E8%8E%B7%E5%BE%97%E5%85%8D%E8%B4%B9%E7%9A%84">https://lottiefiles.com/，获得免费的</a> After Effect 动画的 JSON 文件与 Lottie 一起使用，享受不一样的动画体验😎。</p><h2 id="react-native-reanimated"><a href="#react-native-reanimated" class="headerlink" title="react-native-reanimated"></a>react-native-reanimated</h2><p><a href="https://github.com/kmagiera/react-native-reanimated">react-native-reanimated</a> 的作者从零重写了 React Native 的 Animated API，正如作者在 <a href="https://github.com/kmagiera/react-native-reanimated">官方文档</a> 中描述的，react-native-reanimated 为以它基础的 Animated 库的 API 提供了更全面、更底层的抽象，因此在编写动画时可以有更大的灵活性，尤其是在涉及到基于手势的交互时。</p><p>react-native-reanimated 提供了一个新的 Animated API 来让你替代 <a href="https://reactnative.cn/docs/animations">React Native Animated API</a>，你可以用它来代替 <a href="https://reactnative.cn/docs/animations">React Native Animated API</a> 在 React native 中创建动画。以下是为什么你可能必须使用 <a href="https://github.com/kmagiera/react-native-reanimated">react-native-reanimated</a> 而不是 <a href="https://reactnative.cn/docs/animations">React Native Animated API</a>：</p><ul><li>react-native-reanimated 通过提供多个声明性 API，让你对动画值有更大的控制权，以保持对值变化的跟踪。</li><li>不再需要使用 <code>useNativeDriver</code>，因为 react-native-reanimated 直接在 UI 线程执行动画。</li></ul><p>了解更多关系该库的信息，我建议你阅读他们托管在 <a href="https://github.com/kmagiera/react-native-reanimated">GitHub 的文档</a>，我也建议你关注 <a href="https://medium.com/@wcandillon">William Candillon</a> 和它的 <a href="https://www.youtube.com/channel/UC806fwFWpiLQV5y-qifzHnA">youtube channel</a>，他在 React Native 中做了一些很棒的动画，而且他一直在使用 react-native-reanimated。</p><h2 id="React-Native-Animations-Library-rnal"><a href="#React-Native-Animations-Library-rnal" class="headerlink" title="React Native Animations Library (rnal)"></a>React Native Animations Library (rnal)</h2><p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1772cb10093d489b9357f6c5c08f431e~tplv-k3u1fbpfcp-watermark.image" alt="1_BLFTzAhn1uV5OLtHFUQJQg.gif"></p><p><code>rnal</code> , 是由 <a href="https://saidhayani.medium.com/">SaidHayani@</a> 创建的 React Native 动画库，它的目的是使 React Native 中使用动画变得足够简单，通过提供简单的封装来创建过渡效果，如 <code>Fade</code>，<code>Scale</code>，或 <code>rotation</code> 效果，并可选择创建自定义动画，要创建 <code>Fade</code> 效果，你可以只做以下事情：</p><p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0259be58f06642e592bd39c55f263bc2~tplv-k3u1fbpfcp-watermark.image" alt="1_zuqjwJjTqPUMXAoOYGfYIw.gif"></p><p>你可以查看 <a href="https://github.com/hayanisaid/rnal">docs</a> 探索更多你可以使用的选项.</p><h2 id="react-native-motion"><a href="#react-native-motion" class="headerlink" title="react-native-motion"></a>react-native-motion</h2><p><a href="https://github.com/xotahal/react-native-motion">react-native-motion</a> 是一个在 React Native 中制作动画的库，使用起来非常简单，下面是一个使用 <a href="https://github.com/xotahal/react-native-motion">react-native-motion</a> 制作简单 Shake 动画的例子：</p><figure class="highlight jsx"><figcaption><span>| pure</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span>, &#123;<span class="title class_">Component</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">Text</span>, <span class="title class_">StyleSheet</span>, <span class="title class_">TouchableOpacity</span>, <span class="title class_">SafeAreaView</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;react-native&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">Shake</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;react-native-motion&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">class</span> <span class="title class_">index</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Component</span> &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">value</span>: <span class="number">0</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">  _startAnimation = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;<span class="attr">value</span>: <span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">value</span> + <span class="number">1</span>&#125;);</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">SafeAreaView</span> <span class="attr">style</span>=<span class="string">&#123;styles.container&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">TouchableOpacity</span> <span class="attr">style</span>=<span class="string">&#123;styles.btn&#125;</span> <span class="attr">onPress</span>=<span class="string">&#123;this._startAnimation&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Text</span> <span class="attr">style</span>=<span class="string">&#123;styles.textBtn&#125;</span>&gt;</span>Start animation 🚀<span class="tag">&lt;/<span class="name">Text</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">TouchableOpacity</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Shake</span> <span class="attr">style</span>=<span class="string">&#123;[styles.card]&#125;</span> <span class="attr">value</span>=<span class="string">&#123;this.state.value&#125;</span> <span class="attr">type</span>=<span class="string">&quot;timing&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Text</span> <span class="attr">style</span>=<span class="string">&#123;styles.whiteText&#125;</span>&gt;</span>&#123;this.state.value&#125;<span class="tag">&lt;/<span class="name">Text</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">Shake</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">SafeAreaView</span>&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> styles = <span class="title class_">StyleSheet</span>.<span class="title function_">create</span>(&#123;</span><br><span class="line">  <span class="attr">container</span>: &#123;</span><br><span class="line">    <span class="attr">alignItems</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">btn</span>: &#123;</span><br><span class="line">    <span class="attr">alignItems</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">    <span class="attr">backgroundColor</span>: <span class="string">&#x27;#d3d3d3&#x27;</span>,</span><br><span class="line">    <span class="attr">padding</span>: <span class="number">10</span>,</span><br><span class="line">    <span class="attr">width</span>: <span class="string">&#x27;80%&#x27;</span>,</span><br><span class="line">    <span class="attr">borderRadius</span>: <span class="number">10</span>,</span><br><span class="line">    <span class="attr">marginTop</span>: <span class="number">20</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">textBtn</span>: &#123;</span><br><span class="line">    <span class="attr">fontWeight</span>: <span class="string">&#x27;bold&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">card</span>: &#123;</span><br><span class="line">    <span class="attr">backgroundColor</span>: <span class="string">&#x27;#007fff&#x27;</span>,</span><br><span class="line">    <span class="attr">width</span>: <span class="string">&#x27;60%&#x27;</span>,</span><br><span class="line">    <span class="attr">height</span>: <span class="number">300</span>,</span><br><span class="line">    <span class="attr">marginTop</span>: <span class="number">100</span>,</span><br><span class="line">    <span class="attr">justifyContent</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">    <span class="attr">alignItems</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">    <span class="attr">borderRadius</span>: <span class="number">10</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">whiteText</span>: &#123;</span><br><span class="line">    <span class="attr">color</span>: <span class="string">&#x27;#ffffff&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/15b7fc60d48d46ed94457eda63d5aa5d~tplv-k3u1fbpfcp-watermark.image" alt="1_nhKMy9zlUPvCYWWbiGNFaQ.gif"></p><p>react-native-motion 为我们提供了一个简单的 API 来制作共享的过渡效果，该库的作者写了 <a href="https://medium.com/react-native-motion/transition-challenge-9bc9fdef56c7">一篇关于共享过渡效果的文章</a>。值得注意的是。</p><blockquote><p>译者 Tips：该库已有 3 年未更新，且没有 TypeScript 类型声明，生产环境慎用！</p></blockquote><blockquote><p>原文链接：<a href="https://blog.bitsrc.io/top-5-animation-libraries-in-react-native-d00ec8ddfc8d">Top 5 Animation Libraries in React Native</a><br>原文作者：<a href="https://saidhayani.medium.com/">SaidHayani@</a><br>译文出自：<a href="https://blog.zisheng.pro/categories/%E6%B4%9B%E7%AB%B9%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/">紫升翻译计划</a></p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/dca3341914f8/</id>
    <link href="https://blog.zisheng.pro/posts/dca3341914f8/"/>
    <published>2023-04-06T15:53:15.000Z</published>
    <summary>在技术层面，React Native 为我们提供了强大的声明式 API 去创建动画，通常我们称之为 `Animated API`。然而，有时你可能想使用一些第三方库来为你处理动画，而不用直接处理 `Animated API`，本文我们便是探索和讨论 5 个你值得是使用的 React Native 插件。</summary>
    <title>2023 React Native Top5 动画库</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="前端工程化" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%8C%96/"/>
    <category term="源码解析" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/"/>
    <category term="Rollup 源码解析" scheme="https://blog.zisheng.pro/tags/Rollup-%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/"/>
    <category term="Rollup 插件机制" scheme="https://blog.zisheng.pro/tags/Rollup-%E6%8F%92%E4%BB%B6%E6%9C%BA%E5%88%B6/"/>
    <category term="tapable" scheme="https://blog.zisheng.pro/tags/tapable/"/>
    <content>
      <![CDATA[<h2 id="rollup-c-简要流程"><a href="#rollup-c-简要流程" class="headerlink" title="rollup -c 简要流程"></a><code>rollup -c</code> 简要流程</h2><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3bd4a66beffc4b6182498a4bda07ce8f~tplv-k3u1fbpfcp-zoom-1.image" alt="rollup -c 简要流程"></p><h2 id="插件系统相关模块"><a href="#插件系统相关模块" class="headerlink" title="插件系统相关模块"></a>插件系统相关模块</h2><ul><li>Graph: 全局唯一的图，包含入口以及各种依赖的相互关系，操作方法，缓存等。是 rollup 的核心</li><li>PluginDriver: 插件驱动器，调用插件和提供插件环境上下文等</li></ul><h2 id="插件机制分析"><a href="#插件机制分析" class="headerlink" title="插件机制分析"></a>插件机制分析</h2><h3 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h3><p>一个 Rollup 插件是由一个或多个属性、构建钩子函数、输出钩子函数组成的对象，插件还需要符合一些官方的约定。一个插件应该作为一个包来发布，这个包导出一个可以用插件特定的选项来调用的函数，并且该函数返回一个对象。</p><p>插件允许你自定义 Rollup 的行为，比如，打包之前转换代码或者在你的 <code>node_modules</code> 文件夹中查找第三方包。</p><p>官方插件维护在 <a href="https://github.com/rollup/plugins">rollup&#x2F;plugins</a> 仓库，社区精选插件维护在 <a href="https://github.com/rollup/awesome">rollup&#x2F;awesome</a>。如果你想给某个插件提建议，请提交一个 pr。</p><h3 id="一个简单的例子"><a href="#一个简单的例子" class="headerlink" title="一个简单的例子"></a>一个简单的例子</h3><p>下面的插件可以在不访问文件系统的前提下拦截任何 <code>virtual-module</code> 的导入。例如，如果你想在浏览器中使用 Rollup，这是必要的。它甚至可以用来替换入口点，如例子中所示。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// index.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">myExample</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;my-example&#x27;</span>, <span class="comment">// 名字会在 warnings 和 errors 中显示</span></span><br><span class="line">    <span class="title function_">resolveId</span>(<span class="params">source</span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (source === <span class="string">&#x27;virtual-module&#x27;</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> source; <span class="comment">// 这表明 rollup 不应该询问其他插件或检查文件系统来寻找这个 ID。</span></span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">null</span>; <span class="comment">// 其他的 ID 应该按照通常的方式处理</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="title function_">load</span>(<span class="params">id</span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (id === <span class="string">&#x27;virtual-module&#x27;</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&#x27;export default &quot;This is virtual!&quot;&#x27;</span>; <span class="comment">// &quot;virtual-module&quot; 的源码</span></span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">null</span>; <span class="comment">// 其他的 ID 应该按照通常的方式处理</span></span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// rollup.config.js</span></span><br><span class="line"><span class="keyword">import</span> myExample <span class="keyword">from</span> <span class="string">&#x27;./index&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  <span class="attr">input</span>: <span class="string">&#x27;virtual-module&#x27;</span>, <span class="comment">// 被我们的插件解析</span></span><br><span class="line">  <span class="attr">plugins</span>: [<span class="title function_">myExample</span>()],</span><br><span class="line">  <span class="attr">output</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">file</span>: <span class="string">&#x27;bundle.js&#x27;</span>,</span><br><span class="line">      <span class="attr">format</span>: <span class="string">&#x27;es&#x27;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">  ],</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="约定"><a href="#约定" class="headerlink" title="约定"></a>约定</h3><ul><li>插件应该有一个清晰的名字，并且必须带上 <code>rollup-plugin-</code> 前缀。</li><li>在 <code>package.json</code> 中包含 <code>rollup-plugin</code> 关键字。</li><li>插件应该被测试，我们推荐 mocha 或者 ava 这类开箱支持 promises 的库。</li><li>尽可能使用异步方法。</li><li>使用英文编写插件文档</li><li>如果合适的话，确保你的插件输出正确的 sourcemap</li><li>如果你的插件使用 ‘virtual modules’（比如帮助函数），给模块名加上 <code>\0</code> 前缀。这可以阻止其他插件执行它。</li></ul><h3 id="钩子函数"><a href="#钩子函数" class="headerlink" title="钩子函数"></a>钩子函数</h3><p>rollup 插件的核心是钩子函数，rollup 钩子函数分为两类：</p><h4 id="构建钩子函数"><a href="#构建钩子函数" class="headerlink" title="构建钩子函数"></a>构建钩子函数</h4><p>为了与构建过程交互，你的插件对象需要包含一些构建钩子函数。构建钩子是构建的各个阶段调用的函数。构建钩子函数可以影响构建执行方式、提供构建的信息或者在构建完成后修改构建。rollup 中有不同的构建钩子函数：</p><ul><li><code>async</code>：这类 hook 也可以返回一个解析为相同类型值的 promise;否则，hook 将被标记为 <code>sync</code>。</li><li><code>first</code>：如果有多个插件实现了这个 hook，hook 将依次运行，直到钩子返回一个非 <code>null</code> 或非 <code>undefined</code> 的值。</li><li><code>sequential</code>：如果有多个插件实现了这个 hook，所有的插件都将按照指定的插件顺序运行。如果一个 hook 是异步的，这种类型的后续 hook 将一直等待，直到当前 hook 被解析。</li><li><code>parallel</code>：如果有多个插件实现了这个 hook，所有的插件都将按照指定的插件顺序运行。如果一个 hook 是异步的，这种类型的后续 hook 将并行运行，而不等待当前钩子。</li></ul><p>构建钩子函数在构建阶段执行，它们被 <a href="https://github.com/rollup/rollup/blob/07b3a02069594147665daa95d3fa3e041a82b2d0/cli/run/build.ts#L34"><code>rollup.rollup(inputOptions)</code></a> 触发。它们主要关注在 Rollup 处理输入文件之前定位、提供和转换输入文件。构建阶段的第一个钩子是 <code>options</code>，最后一个钩子总是 <code>buildEnd</code>，除非有一个构建错误，在这种情况下 <code>closeBundle</code> 将在这之后被调用。</p><p>此外，在观察模式下，<code>watchChange</code> 钩子可以在任何时候被触发，以通知新的运行将在当前运行产生其输出后被触发。另外，当 watcher 关闭时，closeWatcher 钩子函数将被触发。</p><h4 id="输出生成钩子函数"><a href="#输出生成钩子函数" class="headerlink" title="输出生成钩子函数"></a>输出生成钩子函数</h4><p>输出生成钩子函数可以提供关于生成的包的信息并在构建完成后立马执行。它们和构建钩子函数拥有一样的工作原理和相同的类型，但是不同的是它们分别被 ·<a href="https://github.com/rollup/rollup/blob/07b3a02069594147665daa95d3fa3e041a82b2d0/cli/run/build.ts#L44"><code>bundle.generate(output)</code></a> 或 <a href="https://github.com/rollup/rollup/blob/07b3a02069594147665daa95d3fa3e041a82b2d0/cli/run/build.ts#L64"><code>bundle.write(outputOptions)</code></a> 调用。只使用输出生成钩子的插件也可以通过输出选项传入，因为只对某些输出运行。</p><p>输出生成阶段的第一个钩子函数是 <a href="https://github.com/rollup/rollup/blob/07b3a02069594147665daa95d3fa3e041a82b2d0/src/Bundle.ts#L50">outputOptions</a>，如果输出通过 <a href="https://github.com/rollup/rollup/blob/master/cli/run/build.ts#L44">bundle.generate(…)</a> 成功生成则第一个钩子函数是 <a href="https://github.com/rollup/rollup/blob/master/src/Bundle.ts#L73">generateBundle</a>，如果输出通过 <a href="https://github.com/rollup/rollup/blob/07b3a02069594147665daa95d3fa3e041a82b2d0/src/watch/watch.ts#L200"><code>bundle.write(...)</code></a> 生成则最后一个钩子函数是 <a href="https://github.com/rollup/rollup/blob/master/src/rollup/rollup.ts#L176"><code>writeBundle</code></a>，另外如果输出生成阶段发生了错误的话，最后一个钩子函数则是 <a href="https://github.com/rollup/rollup/blob/master/src/Bundle.ts#L70">renderError</a>。</p><p>另外，<a href="https://github.com/rollup/rollup/blob/master/src/rollup/rollup.ts#L59">closeBundle</a> 可以作为最后一个钩子被调用，但用户有责任手动调用 <code>bundle.close()</code> 来触发它。CLI 将始终确保这种情况发生。</p><h4 id="钩子函数加载实现"><a href="#钩子函数加载实现" class="headerlink" title="钩子函数加载实现"></a>钩子函数加载实现</h4><p><a href="https://github.com/rollup/rollup/blob/07b3a02069594147665daa95d3fa3e041a82b2d0/src/utils/PluginDriver.ts#L124"><code>PluginDriver</code></a> 中有 9 个 hook 加载函数。主要是因为每种类别的 hook 都有同步和异步的版本。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2bba7776f98d4cbc9d184101d1f1b752~tplv-k3u1fbpfcp-zoom-1.image" alt="rollup 钩子函数加载实现"></p><p><strong>1.hookFirst：</strong></p><blockquote><p>加载 <code>first</code> 类型的钩子函数，场景有 <code>resolveId</code>、<code>resolveAssetUrl</code> 等</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> hookFirst&lt;H <span class="keyword">extends</span> keyof <span class="title class_">PluginHooks</span>, R = <span class="title class_">ReturnType</span>&lt;<span class="title class_">PluginHooks</span>[H]&gt;&gt;(</span><br><span class="line">  <span class="attr">hookName</span>: H,</span><br><span class="line">  <span class="attr">args</span>: <span class="title class_">Args</span>&lt;<span class="title class_">PluginHooks</span>[H]&gt;,</span><br><span class="line">  replaceContext?: <span class="title class_">ReplaceContext</span> | <span class="literal">null</span>,</span><br><span class="line">  skip?: number | <span class="literal">null</span></span><br><span class="line">): <span class="title class_">EnsurePromise</span>&lt;R&gt; &#123;</span><br><span class="line">  <span class="comment">// 初始化 promise</span></span><br><span class="line">  <span class="keyword">let</span> <span class="attr">promise</span>: <span class="title class_">Promise</span>&lt;any&gt; = <span class="title class_">Promise</span>.<span class="title function_">resolve</span>();</span><br><span class="line">  <span class="comment">// this.plugins 在实例化 Graph 的时候，进行了初始化</span></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; <span class="variable language_">this</span>.<span class="property">plugins</span>.<span class="property">length</span>; i++) &#123;</span><br><span class="line">    <span class="keyword">if</span> (skip === i) <span class="keyword">continue</span>;</span><br><span class="line">    <span class="comment">// 覆盖之前的 promise，换言之就是串行执行钩子函数</span></span><br><span class="line">    promise = promise.<span class="title function_">then</span>(<span class="function">(<span class="params">result: any</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="comment">// 返回非 null 或 undefined 的时候，停止运行，返回结果</span></span><br><span class="line">      <span class="keyword">if</span> (result != <span class="literal">null</span>) <span class="keyword">return</span> result;</span><br><span class="line">      <span class="comment">// 执行钩子函数</span></span><br><span class="line">      <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="title function_">runHook</span>(hookName, args <span class="keyword">as</span> any[], i, <span class="literal">false</span>, replaceContext);</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 返回 hook 过的 promise</span></span><br><span class="line">  <span class="keyword">return</span> promise;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>2.hookFirstSync</strong>：</p><blockquote><p>hookFirst 的同步版本，使用场景有 <code>resolveFileUrl</code>、<code>resolveImportMeta</code> 等</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> hookFirstSync&lt;H <span class="keyword">extends</span> keyof <span class="title class_">PluginHooks</span>, R = <span class="title class_">ReturnType</span>&lt;<span class="title class_">PluginHooks</span>[H]&gt;&gt;(</span><br><span class="line">  <span class="attr">hookName</span>: H,</span><br><span class="line">  <span class="attr">args</span>: <span class="title class_">Args</span>&lt;<span class="title class_">PluginHooks</span>[H]&gt;,</span><br><span class="line">  replaceContext?: <span class="title class_">ReplaceContext</span></span><br><span class="line">): R &#123;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; <span class="variable language_">this</span>.<span class="property">plugins</span>.<span class="property">length</span>; i++) &#123;</span><br><span class="line">    <span class="comment">// runHook 的同步版本</span></span><br><span class="line">    <span class="keyword">const</span> result = <span class="variable language_">this</span>.<span class="title function_">runHookSync</span>(hookName, args, i, replaceContext);</span><br><span class="line">    <span class="comment">// 返回非 null 或 undefined 的时候，停止运行，返回结果</span></span><br><span class="line">    <span class="keyword">if</span> (result != <span class="literal">null</span>) <span class="keyword">return</span> result <span class="keyword">as</span> any;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 否则返回 null</span></span><br><span class="line">  <span class="keyword">return</span> <span class="literal">null</span> <span class="keyword">as</span> any;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>3.hookSeq</strong></p><blockquote><p>加载 <code>sequential</code> 类型的钩子函数，和 hookFirst 的区别就是不能中断，使用场景有 <code>onwrite</code>、<code>generateBundle</code> 等</p></blockquote><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> hookSeq&lt;H <span class="keyword">extends</span> keyof <span class="title class_">PluginHooks</span>&gt;(</span><br><span class="line">  <span class="attr">hookName</span>: H,</span><br><span class="line">  <span class="attr">args</span>: <span class="title class_">Args</span>&lt;<span class="title class_">PluginHooks</span>[H]&gt;,</span><br><span class="line">  replaceContext?: <span class="title class_">ReplaceContext</span>,</span><br><span class="line">  <span class="comment">// hookFirst 通过 skip 参数决定是否跳过某个钩子函数</span></span><br><span class="line">): <span class="title class_">Promise</span>&lt;<span class="built_in">void</span>&gt; &#123;</span><br><span class="line">  <span class="keyword">let</span> <span class="attr">promise</span>: <span class="title class_">Promise</span>&lt;<span class="built_in">void</span>&gt; = <span class="title class_">Promise</span>.<span class="title function_">resolve</span>();</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; <span class="variable language_">this</span>.<span class="property">plugins</span>.<span class="property">length</span>; i++)</span><br><span class="line">    promise = promise.<span class="title function_">then</span>(<span class="function">() =&gt;</span></span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">runHook</span>&lt;<span class="built_in">void</span>&gt;(hookName, args <span class="keyword">as</span> <span class="built_in">any</span>[], i, <span class="literal">false</span>, replaceContext),</span><br><span class="line">    );</span><br><span class="line">  <span class="keyword">return</span> promise;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>4.hookSeqSync</strong></p><blockquote><p>hookSeq 同步版本，不需要构造 promise，而是直接使用 <code>runHookSync</code> 执行钩子函数。使用场景有 <code>closeWatcher</code>、<code>watchChange</code> 等。</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">hookSeqSync&lt;H <span class="keyword">extends</span> <span class="title class_">SyncPluginHooks</span> &amp; <span class="title class_">SequentialPluginHooks</span>&gt;(</span><br><span class="line">  <span class="attr">hookName</span>: H,</span><br><span class="line">  <span class="attr">args</span>: <span class="title class_">Parameters</span>&lt;<span class="title class_">PluginHooks</span>[H]&gt;,</span><br><span class="line">  replaceContext?: <span class="title class_">ReplaceContext</span></span><br><span class="line">): <span class="keyword">void</span> &#123;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> plugin <span class="keyword">of</span> <span class="variable language_">this</span>.<span class="property">plugins</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">runHookSync</span>(hookName, args, plugin, replaceContext);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>5.hookReduceArg0</strong></p><blockquote><p>对 arg 第一项进行 reduce 操作。使用场景: <code>options</code>、<code>renderChunk</code> 等</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> hookReduceArg0&lt;H <span class="keyword">extends</span> keyof <span class="title class_">PluginHooks</span>, V, R = <span class="title class_">ReturnType</span>&lt;<span class="title class_">PluginHooks</span>[H]&gt;&gt;(</span><br><span class="line">    <span class="attr">hookName</span>: H,</span><br><span class="line">    [arg0, ...args]: any[], <span class="comment">// 取出传入的数组的第一个参数，将剩余的置于一个数组中</span></span><br><span class="line">    <span class="attr">reduce</span>: <span class="title class_">Reduce</span>&lt;V, R&gt;,</span><br><span class="line">    replaceContext?: <span class="title class_">ReplaceContext</span> <span class="comment">// 替换当前 plugin 调用时候的上下文环境</span></span><br><span class="line">) &#123;</span><br><span class="line">  <span class="keyword">let</span> promise = <span class="title class_">Promise</span>.<span class="title function_">resolve</span>(arg0); <span class="comment">// 默认返回 source.code</span></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; <span class="variable language_">this</span>.<span class="property">plugins</span>.<span class="property">length</span>; i++) &#123;</span><br><span class="line">    <span class="comment">// 第一个 promise 的时候只会接收到上面传递的arg0</span></span><br><span class="line">    <span class="comment">// 之后每一次 promise 接受的都是上一个插件处理过后的 source.code 值</span></span><br><span class="line">    promise = promise.<span class="title function_">then</span>(<span class="function"><span class="params">arg0</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> hookPromise = <span class="variable language_">this</span>.<span class="title function_">runHook</span>(hookName, [arg0, ...args], i, <span class="literal">false</span>, replaceContext);</span><br><span class="line">      <span class="comment">// 如果没有返回 promise，那么直接返回 arg0</span></span><br><span class="line">      <span class="keyword">if</span> (!hookPromise) <span class="keyword">return</span> arg0;</span><br><span class="line">      <span class="comment">// result 代表插件执行完成的返回值</span></span><br><span class="line">      <span class="keyword">return</span> hookPromise.<span class="title function_">then</span>(<span class="function">(<span class="params">result: any</span>) =&gt;</span></span><br><span class="line">        reduce.<span class="title function_">call</span>(<span class="variable language_">this</span>.<span class="property">pluginContexts</span>[i], arg0, result, <span class="variable language_">this</span>.<span class="property">plugins</span>[i])</span><br><span class="line">      );</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> promise;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>6.hookReduceArg0Sync</strong></p><p><code>hookReduceArg0</code> 同步版本，使用场景 <code>transform</code>、<code>generateBundle</code> 等</p><p><strong>7.hookParallel</strong></p><blockquote><p>并行执行 hook，不会等待当前 hook 完成。使用场景 <code>buildEnd</code>、<code>buildStart</code>、<code>moduleParsed</code> 等。</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">hookParallel&lt;H <span class="keyword">extends</span> <span class="title class_">AsyncPluginHooks</span> &amp; <span class="title class_">ParallelPluginHooks</span>&gt;(</span><br><span class="line">  <span class="attr">hookName</span>: H,</span><br><span class="line">  <span class="attr">args</span>: <span class="title class_">Parameters</span>&lt;<span class="title class_">PluginHooks</span>[H]&gt;,</span><br><span class="line">  replaceContext?: <span class="title class_">ReplaceContext</span></span><br><span class="line">): <span class="title class_">Promise</span>&lt;<span class="keyword">void</span>&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> <span class="attr">promises</span>: <span class="title class_">Promise</span>&lt;<span class="keyword">void</span>&gt;[] = [];</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> plugin <span class="keyword">of</span> <span class="variable language_">this</span>.<span class="property">plugins</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> hookPromise = <span class="variable language_">this</span>.<span class="title function_">runHook</span>(hookName, args, plugin, <span class="literal">false</span>, replaceContext);</span><br><span class="line">    <span class="keyword">if</span> (!hookPromise) <span class="keyword">continue</span>;</span><br><span class="line">    promises.<span class="title function_">push</span>(hookPromise);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">all</span>(promises).<span class="title function_">then</span>(<span class="function">() =&gt;</span> &#123;&#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="runHook"><a href="#runHook" class="headerlink" title="runHook"></a>runHook</h4><p>上面的钩子函数加载函数，内部都调用了执行钩子函数的方法 <code>runHook</code> 或 <code>runHookSync</code>，我们以 <code>runHook</code> 为例分析一下源码：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> runHook&lt;T&gt;(</span><br><span class="line">  <span class="attr">hookName</span>: <span class="built_in">string</span>,</span><br><span class="line">  <span class="attr">args</span>: <span class="built_in">any</span>[],</span><br><span class="line">  <span class="attr">pluginIndex</span>: <span class="built_in">number</span>,</span><br><span class="line">  <span class="attr">permitValues</span>: <span class="built_in">boolean</span>,</span><br><span class="line">  hookContext?: <span class="title class_">ReplaceContext</span> | <span class="literal">null</span>,</span><br><span class="line">): <span class="title class_">Promise</span>&lt;T&gt; &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">previousHooks</span>.<span class="title function_">add</span>(hookName);</span><br><span class="line">  <span class="comment">// 找到当前 plugin</span></span><br><span class="line">  <span class="keyword">const</span> plugin = <span class="variable language_">this</span>.<span class="property">plugins</span>[pluginIndex];</span><br><span class="line">  <span class="comment">// 找到当前执行的在 plugin 中定义的 hooks 钩子函数</span></span><br><span class="line">  <span class="keyword">const</span> hook = (plugin <span class="keyword">as</span> <span class="built_in">any</span>)[hookName];</span><br><span class="line">  <span class="keyword">if</span> (!hook) <span class="keyword">return</span> <span class="literal">undefined</span> <span class="keyword">as</span> <span class="built_in">any</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// pluginContexts 在初始化 plugin 驱动器类的时候定义，是个数组，数组保存对应着每个插件的上下文环境</span></span><br><span class="line">  <span class="keyword">let</span> context = <span class="variable language_">this</span>.<span class="property">pluginContexts</span>[pluginIndex];</span><br><span class="line">  <span class="comment">// 用于区分对待不同钩子函数的插件上下文</span></span><br><span class="line">  <span class="keyword">if</span> (hookContext) &#123;</span><br><span class="line">    context = <span class="title function_">hookContext</span>(context, plugin);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">resolve</span>()</span><br><span class="line">    .<span class="title function_">then</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="comment">// 许可值允许返回值，而不是一个函数钩子，使用 hookReduceValue 或 hookReduceValueSync 加载。</span></span><br><span class="line">      <span class="keyword">if</span> (<span class="keyword">typeof</span> hook !== <span class="string">&#x27;function&#x27;</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (permitValues) <span class="keyword">return</span> hook;</span><br><span class="line">        <span class="keyword">return</span> <span class="title function_">error</span>(&#123;</span><br><span class="line">          <span class="attr">code</span>: <span class="string">&#x27;INVALID_PLUGIN_HOOK&#x27;</span>,</span><br><span class="line">          <span class="attr">message</span>: <span class="string">`Error running plugin hook <span class="subst">$&#123;hookName&#125;</span> for <span class="subst">$&#123;plugin.name&#125;</span>, expected a function hook.`</span>,</span><br><span class="line">        &#125;);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">// 传入插件上下文和参数，返回插件执行结果</span></span><br><span class="line">      <span class="keyword">return</span> hook.<span class="title function_">apply</span>(context, args);</span><br><span class="line">    &#125;)</span><br><span class="line">    .<span class="title function_">catch</span>(<span class="function"><span class="params">err</span> =&gt;</span> <span class="title function_">throwPluginError</span>(err, plugin.<span class="property">name</span>, &#123; <span class="attr">hook</span>: hookName &#125;));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="核心依赖"><a href="#核心依赖" class="headerlink" title="核心依赖"></a>核心依赖</h2><ul><li><a href="https://www.npmjs.com/package/yargs-parser">yargs-parser</a>：yargs 使用的强大的选项解析插件</li><li><a href="https://www.npmjs.com/package/source-map-support">source-map-support</a>：这个模块通过 V8 堆栈追踪 API 支持 堆栈 sourcemap 支持</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Rollup 的插件和其他大型框架大同小异，都是提供统一的接口并贯彻了约定优于配置的思想。9 种 hook 加载函数使 rollup 的插件开发非常灵活，同时也带来了学习成本。</p><p>和 webpack 相比，rollup 的插件系统自称一派且没有区分 plugin 和 loader。</p><p>Rollup 插件机制的核心是构建阶段和输出生成阶段的各种钩子函数。内部通过基于 Promise 实现异步 hook 的调度。</p><p>rollup 的源码全都糅杂在一个库中，阅读起来着实头大，模块、工具函数管理的看起来很随意。而且我们无法直接移植它的任何工具到我们的项目中，相比起来，webpack 的插件系统封装成了一个插件 <a href="https://github.com/webpack/tapable">tapable</a> 就很利于我们学习和使用。</p><blockquote><p>本文首发于「<a href="https://blog.zisheng.pro/">紫升的博客</a>」，同步于公众号「<a href="https://cdn.jsdelivr.net/gh/youngjuning/images/20210418112129.jpeg">紫升早茶馆</a>」和「<a href="https://juejin.cn/user/325111174662855">掘金专栏</a>」。</p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/1470e2efc9e9/</id>
    <link href="https://blog.zisheng.pro/posts/1470e2efc9e9/"/>
    <published>2023-04-06T15:41:26.000Z</published>
    <summary>一个 Rollup 插件是由一个或多个属性、构建钩子函数、输出钩子函数组成的对象，插件还需要符合一些官方的约定。一个插件应该作为一个包来发布，这个包导出一个可以用插件特定的选项来调用的函数，并且该函数返回一个对象。</summary>
    <title>2023 Rollup 插件机制源码解析</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="副业" scheme="https://blog.zisheng.pro/categories/%E5%89%AF%E4%B8%9A/"/>
    <category term="Google Adsense" scheme="https://blog.zisheng.pro/tags/Google-Adsense/"/>
    <category term="Google Adsense 付款周期" scheme="https://blog.zisheng.pro/tags/Google-Adsense-%E4%BB%98%E6%AC%BE%E5%91%A8%E6%9C%9F/"/>
    <category term="Google Adsense 付款时间" scheme="https://blog.zisheng.pro/tags/Google-Adsense-%E4%BB%98%E6%AC%BE%E6%97%B6%E9%97%B4/"/>
    <content>
      <![CDATA[<div class="note info modern"><p>AdSense 的付款周期为每月一次。</p></div><p>Google Adsense 服务团队会将你一整个月的估算收入累积起来，然后在下个月月初确定你的最终收入，将其计入帐号余额并显示在付款页中。</p><p>如果你的余额超出付款最低限额，且你没有设置暂停付款状态，Google Adsense 会在<strong>该月的 21 日至 26 日</strong>为你发放付款。</p><p>请注意，你收到付款的确切时间取决于你所在的时区、21 日是否为周末或节假日以及你选择的付款方式。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1680831939815.png" alt="2023 Google Adsense 具体付款时间"></p><h2 id="结算（3-日）"><a href="#结算（3-日）" class="headerlink" title="结算（3 日）"></a>结算（3 日）</h2><p>当月 3 日 Google Adsense 会确定你上个月的估算收入并将其公布到你的付款页上。你会在付款页上看到一个专列项，其中显示了你上月交易的总收入。</p><p>你的付款页还会显示与调整项或费用有关的各种扣减金额。</p><p>请注意，Google Adsense 在确定收入的过程中扣减了你因无效活动而产生的收入，但是你无法看到这些扣减金额的专列项。详细了解<a href="https://support.google.com/adsense/answer/2808531">Google Adsense 无效活动扣减金额</a>。</p><h2 id="付款（20-日）"><a href="#付款（20-日）" class="headerlink" title="付款（20 日）"></a>付款（20 日）</h2><p>如果你需要对付款信息进行更改（包括解除暂停付款状态），请务必在每月的 20 日或之前完成。每月 20 日之后进行的更改要到下一个月的付款周期才会生效。此外，你每月 20 日的总余额必须达到付款最低限额。</p><p>如果你的余额未达到付款最低限额或者你的帐号处于暂停付款状态，那么你在该月不会收到任何付款，且你的余额将会结转到下个月的收入中。</p><h2 id="付款中（21-日-26-日）"><a href="#付款中（21-日-26-日）" class="headerlink" title="付款中（21 日 - 26 日）"></a>付款中（21 日 - 26 日）</h2><p>每月 21 日至 26 日，付款页上会显示一个“正在付款”专列项，表示 Google Adsense 已处理收入并将其支付给你的银行机构。当这条专列项更新为“付款已发出”时，即表示 Google Adsense 银行已同意支付这笔款项，并已发放进行处理。</p><div class="note warning modern"><p>如果 21 日是周末或节假日，那么付款将在该月 21 日之后第一个工作日发放。</p></div><h2 id="5-个工作日"><a href="#5-个工作日" class="headerlink" title="5 个工作日"></a>5 个工作日</h2><p>如下图的邮件，发放付款后，你最晚会在 5 个工作日内收到付款。如果月底之前你仍未收到付款，请与你的银行机构联系。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1682230887140.png"></p><div class="note warning modern"><p>电汇付款可能需要 15 个工作日的时间才能汇入你的账户。电汇到达你银行账户所需的具体时间取决于你账户所在的银行机构。</p></div>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/c9874d785dd6/</id>
    <link href="https://blog.zisheng.pro/posts/c9874d785dd6/"/>
    <published>2023-04-06T13:38:36.000Z</published>
    <summary>Google Adsense 服务团队会将您一整个月的估算收入累积起来，然后在下个月月初确定您的最终收入，将其计入帐号余额并显示在付款页中。如果您的余额超出付款最低限额，且您没有设置暂停付款状态，我们会在该月的 21 日至 26 日为您发放付款。</summary>
    <title>2023 Google Adsense 付款周期和时间</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="issues" scheme="https://blog.zisheng.pro/categories/issues/"/>
    <category term="GitHub" scheme="https://blog.zisheng.pro/categories/issues/GitHub/"/>
    <category term="GitHub" scheme="https://blog.zisheng.pro/tags/GitHub/"/>
    <category term="GitHub Actions" scheme="https://blog.zisheng.pro/tags/GitHub-Actions/"/>
    <category term="GitHub Actions Error" scheme="https://blog.zisheng.pro/tags/GitHub-Actions-Error/"/>
    <category term="ci/cd" scheme="https://blog.zisheng.pro/tags/ci-cd/"/>
    <content>
      <![CDATA[<p>当我使用 GitHub Actions 时，有一个漫长的等待，它显示 “Waiting for a runner to pick up this job”，但是这个配置已经成功运行了很多次，我不知道为什么会出现这个问题。</p><p>runner 是由 <code>runs-on</code> 指定的，所以我尝试将 <code>runs-on</code> 的版本从 18 升到了 22，问题解决了。但是我想如果我不想总是升级 runner 的版本，我应该怎么做？我们可以像给 docker 指定 os 一样指定：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/0c97438eff72/</id>
    <link href="https://blog.zisheng.pro/posts/0c97438eff72/"/>
    <published>2023-04-05T15:36:05.000Z</published>
    <summary>当我使用 GitHub Actions 时，有一个漫长的等待，它显示 &quot;Waiting for a runner to pick up this job&quot;，但是这个配置已经成功运行了很多次，我不知道为什么会出现这个问题。</summary>
    <title>「已解决」Error &quot;Waiting for a runner to pick up this job&quot; using GitHub Actions</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="站点运营" scheme="https://blog.zisheng.pro/categories/%E7%AB%99%E7%82%B9%E8%BF%90%E8%90%A5/"/>
    <category term="SEO" scheme="https://blog.zisheng.pro/categories/%E7%AB%99%E7%82%B9%E8%BF%90%E8%90%A5/SEO/"/>
    <category term="SEO" scheme="https://blog.zisheng.pro/tags/SEO/"/>
    <category term="优化" scheme="https://blog.zisheng.pro/tags/%E4%BC%98%E5%8C%96/"/>
    <category term="文章" scheme="https://blog.zisheng.pro/tags/%E6%96%87%E7%AB%A0/"/>
    <category term="网站" scheme="https://blog.zisheng.pro/tags/%E7%BD%91%E7%AB%99/"/>
    <category term="优化实战" scheme="https://blog.zisheng.pro/tags/%E4%BC%98%E5%8C%96%E5%AE%9E%E6%88%98/"/>
    <category term="hexo seo" scheme="https://blog.zisheng.pro/tags/hexo-seo/"/>
    <content>
      <![CDATA[<h2 id="SEO-是什么"><a href="#SEO-是什么" class="headerlink" title="SEO 是什么"></a>SEO 是什么</h2><p>SEO 全名为 Search Engine Optimization，是搜索引擎优化的缩写。 SEO 原理是一种利用搜索引擎的规则提高网站在有关搜索引擎内自然排名的方式。 它是一种可衡量、可重复的过程，可用于向搜索引擎发送信号，表明你的页面值得出现在搜索结果中。</p><h2 id="SEO-内容优化技巧"><a href="#SEO-内容优化技巧" class="headerlink" title="SEO 内容优化技巧"></a>SEO 内容优化技巧</h2><h3 id="使用合适的标题"><a href="#使用合适的标题" class="headerlink" title="使用合适的标题"></a>使用合适的标题</h3><p>在页面中使用 H1 标题标记主要内容。此外，确保使用 H2、H3 等副标题来组织和说明页面上的其他内容。</p><h3 id="增加描述性-meta-标签"><a href="#增加描述性-meta-标签" class="headerlink" title="增加描述性 meta 标签"></a>增加描述性 meta 标签</h3><p>在 head 部分中添加 meta 标签并填写描述信息，向搜索引擎解释该网页的主要内容和关键字。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">title</span>&gt;</span>页面标题<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;description&quot;</span> <span class="attr">content</span>=<span class="string">&quot;页面描述&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;keywords&quot;</span> <span class="attr">content</span>=<span class="string">&quot;关键词&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br></pre></td></tr></table></figure><p>只要正确添加了 Hexo Front-Matter，Hexo 就会自动为你生成 meta 标签。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1680594944707.png" alt="seo meta 标签"></p><h3 id="图像优化"><a href="#图像优化" class="headerlink" title="图像优化"></a>图像优化</h3><p>对于每个图像，在 alt 属性中使用适当的文本描述。这有助于提高搜索引擎对页面的理解。同时，压缩图像以减少页面加载时间，并将其大小放在无需滚动即可看到的位置。如下图，可以看到我之前的文章中，有些图片是没有 alt 属性的，这是不合理的。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1680594655436.png" alt="seo 图像优化"></p><h3 id="内部链接"><a href="#内部链接" class="headerlink" title="内部链接"></a>内部链接</h3><p>添加内部链接，使用户能够轻松浏览相关页面。此外，添加具有含义的锚文本，以便搜索引擎更好地理解您的网站结构。</p><h3 id="状态码和-URL-结构"><a href="#状态码和-URL-结构" class="headerlink" title="状态码和 URL 结构"></a>状态码和 URL 结构</h3><p>确保所有页面都有正确的状态码（如 404 或 301）和规范的 URL 结构。这将有助于保持您的页面在搜索引擎中的良好排名。</p><p>可以看到由于我之前博客迁移过一次，造成有很多 404 的页面。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1680596391394.png" alt="Google Search 404"></p><p>Google Search Console 没有批量删除失效链接的功能，如果嫌麻烦，可以给博客配置一个精美的 404 页面。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1680596445237.png" alt="Google Search 删除 404 链接"></p><p>我有强迫症，正在一点一点地删除失效链接。我的建议就是文章发布之后，不要轻易改动文章的 URL、标题、分类、标签等，这样会造成很多的 404 链接。</p><h3 id="良好的内容和排版"><a href="#良好的内容和排版" class="headerlink" title="良好的内容和排版"></a>良好的内容和排版</h3><p>最后，一个优化 SEO 的重要因素是内容和排版。确保您的页面具有有价值的、原创的、易于阅读的内容，并使用适当的文本格式、段落和标题来使其易于阅读和理解。</p><h3 id="爬虫友好"><a href="#爬虫友好" class="headerlink" title="爬虫友好"></a>爬虫友好</h3><ol><li>提交网站地图：hexo 的话使用 hexo-generator-search 插件</li><li><code>robots.txt</code>：告诉爬虫哪些页面可以抓取，哪些页面不可以抓取</li><li>减少重复网页，使用 <code>canonical</code> 标签。</li><li>使用 <code>rel=&quot;nofollow&quot;</code> 标签来告诉爬虫不要抓取外链，hexo 的话使用 hexo-filter-nofollow。</li></ol><blockquote><p>注意：请不要给友情链接添加 <code>rel=&quot;nofollow&quot;</code> 标签，这样会影响到你的友链网站的 SEO。</p></blockquote><ul><li><a href="https://ahrefs.com/blog/zh/seo-chrome-extensions/">14个最适合 SEO 的免费 Chrome 扩展程序（亲测好用）</a></li></ul><h3 id="关键词挖掘"><a href="#关键词挖掘" class="headerlink" title="关键词挖掘"></a>关键词挖掘</h3><p>使用关键词优化工具，比如 <a href="https://ads.google.com/home/tools/keyword-planner/">Google Keyword Planner</a>，来找到最适合您的网站的关键词。</p><p>还有一些功能更强大的关键词优化工具：</p><ul><li><a href="https://keywordplanner.net/">Keyword Planner</a></li><li><a href="https://keywordtool.io/">Keyword Tool</a></li><li><a href="https://www.wordstream.com/keywords">WordStream</a></li><li><a href="https://www.cifnews.com/article/126696">好用的Google关键词分析工具</a></li></ul><p>除了关键词挖掘工具，你还可以使用 <a href="https://trends.google.com/trends/?geo=US">Google Trends</a> 来找到热门的关键词，并针对性地去创作文章。</p><p>另外还可以直接找排名靠前的文章，看看他们的关键词是怎么做的，直接复制过来就行了。</p><h2 id="免费自然流量"><a href="#免费自然流量" class="headerlink" title="免费自然流量"></a>免费自然流量</h2><p>写博客本来就不盈利，为什么要花钱去买流量呢？下面是一些免费的自然流量工具，可以让你的博客获得更多的免费流量。</p><ul><li><a href="https://www.traffic-engine.com/">Traffic Engine</a>：自动的流量交换系统，可以让你的网站获得来自世界各地的免费流量。</li><li><a href="https://somiibo.com/">Somiibo</a>：一个免费的社交媒体自动化工具，当然也可以优化网站流量。</li><li><a href="https://www.sparktraffic.com/">Spark Traffic</a>：流量机器人免费试用，在短短 1 小时内获得 2000 个免费页面浏览量</li></ul><h2 id="交换友链"><a href="#交换友链" class="headerlink" title="交换友链"></a>交换友链</h2><p>交换友链是一种很好的获取流量的方式，但是要注意的是，不要给不相关的网站交换友链，这样会影响到你的 SEO。我的建议是可以将博客同步到不同的平台，比如 CSDN、掘金、简书等，然后在这些平台上附上原文链接。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/480812bf02a4/</id>
    <link href="https://blog.zisheng.pro/posts/480812bf02a4/"/>
    <published>2023-04-04T07:44:38.000Z</published>
    <summary>本文主要记录了一些用 Hexo 写技术比克的 SEO 优化方法，包括文章标题、文章内容、文章标签、文章分类等。</summary>
    <title>Hexo 博客 SEO 优化实战</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="互联网" scheme="https://blog.zisheng.pro/categories/%E4%BA%92%E8%81%94%E7%BD%91/"/>
    <category term="机器学习" scheme="https://blog.zisheng.pro/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    <category term="推特" scheme="https://blog.zisheng.pro/tags/%E6%8E%A8%E7%89%B9/"/>
    <category term="马斯克" scheme="https://blog.zisheng.pro/tags/%E9%A9%AC%E6%96%AF%E5%85%8B/"/>
    <category term="推荐算法" scheme="https://blog.zisheng.pro/tags/%E6%8E%A8%E8%8D%90%E7%AE%97%E6%B3%95/"/>
    <category term="开源" scheme="https://blog.zisheng.pro/tags/%E5%BC%80%E6%BA%90/"/>
    <category term="推荐系统" scheme="https://blog.zisheng.pro/tags/%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F/"/>
    <content>
      <![CDATA[<p>3 月 31 日，正如马斯克一直在推特上宣传的那样，他将推特的推荐算法开源。这是一个非常有趣的项目，因为它是一个非常复杂的系统，涉及到数百万用户，每天产生数十亿条推文。这篇文章将介绍推特的推荐算法是如何工作的。</p><p>Twitter 旨在为您提供当今世界上发生的最好的事情。这需要一种推荐算法，将每天发布的大约 5 亿条推文提炼成少数几条热门推文，最终显示在您设备的 For You 时间轴上。该博客介绍了该算法如何为您的时间线选择推文。</p><p>我们的推荐系统由许多相互关联的服务和工作组成，我们将在本文中详细介绍。虽然该应用程序的许多区域都推荐推文——搜索、探索、广告——但这篇文章将重点关注主页时间线的“为你推荐”提要。</p><h2 id="我们如何选择推文？"><a href="#我们如何选择推文？" class="headerlink" title="我们如何选择推文？"></a>我们如何选择推文？</h2><p>Twitter 推荐的基础是一组核心模型和功能，它们从推文、用户和参与数据中提取潜在信息。这些模型旨在回答有关 Twitter 网络的重要问题，例如“您将来与其他用户互动的可能性有多大？” 或者，“Twitter 上有哪些社区，其中有哪些热门推文？” 准确回答这些问题使 Twitter 能够提供更相关的推荐。</p><p>推荐管道由使用这些功能的三个主要阶段组成：</p><p>在称为候选采购的过程中从不同的推荐来源获取最佳推文。<br>使用机器学习模型对每条推文进行排名。<br>应用启发式和过滤器，例如过滤掉来自您已阻止的用户的推文、NSFW 内容和您已经看过的推文。<br>负责构建和提供 For You 时间线的服务称为 Home Mixer。Home Mixer 建立在 Product Mixer 之上，Product Mixer 是我们的自定义 Scala 框架，有助于构建内容提要。该服务充当连接不同候选源、评分函数、启发式方法和过滤器的软件主干。</p><p>下图说明了用于构建时间线的主要组件：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1680331902056.png" alt="推特推荐算法"></p><p>让我们探索这个系统的关键部分，大致按照它们在单个时间线请求期间被调用的顺序，从从Candidate Sources检索候选人开始。</p><h2 id="候选来源"><a href="#候选来源" class="headerlink" title="候选来源"></a>候选来源</h2><p>Twitter 有几个候选源，我们用它们来为用户检索最近和相关的推文。对于每个请求，我们尝试通过这些来源从数亿推文中提取最佳的 1500 条推文。我们从您关注的人（网络内）和您不关注的人（网络外）中寻找候选人。如今，For You 时间轴平均包含 50% 的网络内推文和 50% 的网络外推文，尽管这可能因用户而异。</p><h3 id="网内资源"><a href="#网内资源" class="headerlink" title="网内资源"></a>网内资源</h3><p>网络内来源是最大的候选来源，旨在提供您关注的用户的最相关、最新的推文。它使用逻辑回归模型根据推文的相关性对您关注的推文进行有效排名。然后将热门推文发送到下一阶段。</p><p>对网络内推文进行排名的最重要组成部分是 <a href="https://www.ueo-workshop.com/wp-content/uploads/2014/04/sig-alternate.pdf">Real Graph</a>。Real Graph 是一种预测两个用户之间参与可能性的模型。您和推文作者之间的 Real Graph 得分越高，我们将包括的他们的推文就越多。</p><p>网络内源一直是 Twitter 最近工作的主题。我们最近停止使用 Fanout Service，这是一项已有 12 年历史的服务，以前用于从每个用户的推文缓存中提供网络内推文。我们还在重新设计几年前最后一次更新和训练的逻辑回归排名模型！</p><h3 id="网络外资源"><a href="#网络外资源" class="headerlink" title="网络外资源"></a>网络外资源</h3><p>在用户网络之外查找相关推文是一个更棘手的问题：如果您不关注作者，我们如何判断某个推文是否与您相关？Twitter 采用两种方法来解决这个问题。</p><h4 id="社交图谱"><a href="#社交图谱" class="headerlink" title="社交图谱"></a>社交图谱</h4><p>我们的第一种方法是通过分析您关注的人或具有相似兴趣的人的参与度来估计您会发现什么是相关的。</p><p>我们遍历参与图并回答以下问题：</p><p>我关注的人最近与哪些推文进行了互动？<br>谁喜欢和我相似的推文，他们最近还喜欢什么？<br>我们根据这些问题的答案生成候选推文，并使用逻辑回归模型对生成的推文进行排名。这种类型的图遍历对于我们的网络外推荐至关重要；我们开发了GraphJet，这是一种图形处理引擎，可维护用户和推文之间的实时交互图，以执行这些遍历。虽然这种用于搜索 Twitter 参与度和关注网络的启发式方法已被证明是有用的（这些目前服务于大约 15% 的家庭时间线推文），但嵌入空间方法已成为网络外推文的更大来源。</p><h4 id="嵌入空间"><a href="#嵌入空间" class="headerlink" title="嵌入空间"></a>嵌入空间</h4><p>嵌入空间方法旨在回答一个关于内容相似性的更普遍的问题：哪些推文和用户与我的兴趣相似？</p><p>嵌入通过生成用户兴趣和推文内容的数字表示来工作。然后，我们可以计算该嵌入空间中任意两个用户、推文或用户-推文对之间的相似度。如果我们生成准确的嵌入，我们可以使用这种相似性作为相关性的替代。</p><p>Twitter 最有用的嵌入空间之一是<a href="https://dl.acm.org/doi/10.1145/3394486.3403370">SimClusters</a>。<a href="https://github.com/twitter/sbf">SimClusters 使用自定义矩阵分解算法</a>发现由一群有影响力的用户锚定的社区。有 145,000 个社区，每三周更新一次。用户和推文在社区空间中表示，并且可以属于多个社区。社区的规模从个别朋友组的几千用户到新闻或流行文化的数亿用户不等。这些是一些最大的社区：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1680332081420.png" alt="Twitter 推荐算法"></p><p>我们可以通过查看推文在每个社区中的当前流行度来将推文嵌入到这些社区中。喜欢推文的社区用户越多，推文与该社区的关联度就越高。</p><h2 id="排行"><a href="#排行" class="headerlink" title="排行"></a>排行</h2><p>For You 时间线的目标是为您提供相关的推文。在这一点上，我们有大约 1500 名可能相关的候选人。评分直接预测每个候选推文的相关性，并且是在你的时间线上对推文进行排名的主要信号。在此阶段，所有候选人都受到平等对待，而不管其来自哪个候选人来源。</p><p>排名是通过一个约 48M 参数的神经网络实现的，该神经网络在推文交互上持续训练以优化积极参与（例如喜欢、转推和回复）。这种排名机制考虑了数千个特征并输出十个标签来为每条推文打分，其中每个标签代表参与的概率。我们根据这些分数对推文进行排名。</p><h2 id="启发式、过滤器和产品功能"><a href="#启发式、过滤器和产品功能" class="headerlink" title="启发式、过滤器和产品功能"></a>启发式、过滤器和产品功能</h2><p>在排名阶段之后，我们应用启发式和过滤器来实现各种产品功能。这些功能协同工作以创建平衡且多样化的提要。一些例子包括：</p><ul><li>可见性过滤：根据内容和您的偏好过滤掉推文。例如，从您屏蔽或静音的帐户中删除推文。</li><li>作者多样性：避免来自同一作者的太多连续推文。</li><li>内容平衡：确保我们提供网络内和网络外推文的公平平衡。</li><li>基于反馈的疲劳：如果观众提供了负面反馈，则降低某些推文的分数。</li><li>社会证明：排除与推文没有二级关联的网络外推文作为质量保障。换句话说，确保你关注的人与推文互动或关注推文的作者。</li><li>对话：通过将回复与原始推文串连在一起，为回复提供更多上下文。</li><li>已编辑的推文：确定设备上当前的推文是否过时，并发送指令以将其替换为已编辑的版本。</li></ul><h2 id="混合和服务"><a href="#混合和服务" class="headerlink" title="混合和服务"></a>混合和服务</h2><p>此时，Home Mixer 已准备好发送到您的设备的一组推文。作为流程的最后一步，系统将推文与其他非推文内容（如广告、关注推荐和入职提示）混合在一起，这些内容将返回到您的设备上进行显示。</p><p>上面的管道每天运行大约 50 亿次，平均完成时间不到 1.5 秒。单个管道执行需要 220 秒的 CPU 时间，几乎是您在应用程序上看到的延迟的 150 倍。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1680332154794.png" alt="Twitter 推荐算法"></p><p>我们开源努力的目标是向您（我们的用户）提供有关我们系统工作方式的完全透明信息。我们已经发布了为我们的建议提供支持的代码，您可以<a href="https://github.com/twitter/the-algorithm">在此处</a> （和此处）查看这些代码以更详细地了解我们的算法，并且我们还在开发多项功能，以便在我们的应用程序中为您提供更大的透明度。我们计划的一些新开发项目包括：</p><ul><li>为创作者提供更好的 Twitter 分析平台，提供更多关于影响力和参与度的信息</li><li>提高应用到你的推文或帐户的任何安全标签的透明度</li><li>更好地了解推文出现在你的时间线上的原因</li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/48e2be28f51c/</id>
    <link href="https://blog.zisheng.pro/posts/48e2be28f51c/"/>
    <published>2023-04-01T06:50:59.000Z</published>
    <summary>3 月 31 日，正如马斯克一直在推特上宣传的那样，他将推特的推荐算法开源。这是一个非常有趣的项目，因为它是一个非常复杂的系统，涉及到数百万用户，每天产生数十亿条推文。这篇文章将介绍推特的推荐算法是如何工作的。</summary>
    <title>马斯克开源的推特推荐算法是什么？</title>
    <updated>2026-07-12T09:21:36.911Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="issues" scheme="https://blog.zisheng.pro/categories/issues/"/>
    <category term="antd" scheme="https://blog.zisheng.pro/categories/issues/antd/"/>
    <category term="React" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React/"/>
    <category term="antd" scheme="https://blog.zisheng.pro/tags/antd/"/>
    <category term="react" scheme="https://blog.zisheng.pro/tags/react/"/>
    <category term="table" scheme="https://blog.zisheng.pro/tags/table/"/>
    <category term="fixed" scheme="https://blog.zisheng.pro/tags/fixed/"/>
    <category term="issues" scheme="https://blog.zisheng.pro/tags/issues/"/>
    <category term="columns" scheme="https://blog.zisheng.pro/tags/columns/"/>
    <content>
      <![CDATA[<p>一般 antd Table columns 比较多的时候，我们会使用 fixed 属性来固定列，但是在使用的时候发现，fixed 属性不生效，这是为什么呢？</p><p>如下，我们使用了 fixed 属性，但是并没有生效。</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> dataSource = [</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">key</span>: <span class="string">&#x27;1&#x27;</span>,</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;胡彦斌&#x27;</span>,</span><br><span class="line">    <span class="attr">age</span>: <span class="number">32</span>,</span><br><span class="line">    <span class="attr">address</span>: <span class="string">&#x27;西湖区湖底公园1号&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">key</span>: <span class="string">&#x27;2&#x27;</span>,</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;胡彦祖&#x27;</span>,</span><br><span class="line">    <span class="attr">age</span>: <span class="number">42</span>,</span><br><span class="line">    <span class="attr">address</span>: <span class="string">&#x27;西湖区湖底公园1号&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">];</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> columns = [</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">title</span>: <span class="string">&#x27;姓名&#x27;</span>,</span><br><span class="line">    <span class="attr">dataIndex</span>: <span class="string">&#x27;name&#x27;</span>,</span><br><span class="line">    <span class="attr">fixed</span>: <span class="string">&#x27;left&#x27;</span>,</span><br><span class="line">    <span class="attr">key</span>: <span class="string">&#x27;name&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">title</span>: <span class="string">&#x27;住址&#x27;</span>,</span><br><span class="line">    <span class="attr">dataIndex</span>: <span class="string">&#x27;address&#x27;</span>,</span><br><span class="line">    <span class="attr">ellipsis</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">key</span>: <span class="string">&#x27;address&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">title</span>: <span class="string">&#x27;住址&#x27;</span>,</span><br><span class="line">    <span class="attr">dataIndex</span>: <span class="string">&#x27;address&#x27;</span>,</span><br><span class="line">    <span class="attr">width</span>: <span class="string">&#x27;100&#x27;</span>,</span><br><span class="line">    <span class="attr">key</span>: <span class="string">&#x27;address&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">title</span>: <span class="string">&#x27;住址&#x27;</span>,</span><br><span class="line">    <span class="attr">dataIndex</span>: <span class="string">&#x27;address&#x27;</span>,</span><br><span class="line">    <span class="attr">width</span>: <span class="string">&#x27;100&#x27;</span>,</span><br><span class="line">    <span class="attr">key</span>: <span class="string">&#x27;address&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">title</span>: <span class="string">&#x27;住址&#x27;</span>,</span><br><span class="line">    <span class="attr">dataIndex</span>: <span class="string">&#x27;address&#x27;</span>,</span><br><span class="line">    <span class="attr">width</span>: <span class="string">&#x27;100&#x27;</span>,</span><br><span class="line">    <span class="attr">key</span>: <span class="string">&#x27;address&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">title</span>: <span class="string">&#x27;住址&#x27;</span>,</span><br><span class="line">    <span class="attr">dataIndex</span>: <span class="string">&#x27;address&#x27;</span>,</span><br><span class="line">    <span class="attr">width</span>: <span class="string">&#x27;100&#x27;</span>,</span><br><span class="line">    <span class="attr">key</span>: <span class="string">&#x27;address&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">title</span>: <span class="string">&#x27;年龄&#x27;</span>,</span><br><span class="line">    <span class="attr">dataIndex</span>: <span class="string">&#x27;age&#x27;</span>,</span><br><span class="line">    <span class="attr">key</span>: <span class="string">&#x27;age&#x27;</span>,</span><br><span class="line">    <span class="attr">fixed</span>: <span class="string">&#x27;right&#x27;</span></span><br><span class="line">  &#125;,</span><br><span class="line">];</span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">Table</span> <span class="attr">dataSource</span>=<span class="string">&#123;dataSource&#125;</span> <span class="attr">columns</span>=<span class="string">&#123;columns&#125;</span> /&gt;</span></span>;</span><br></pre></td></tr></table></figure><p>代码里有两处使用了 fixed 属性，一处是在第一列，一处是在最后一列，但是并没有生效。原因是有配置项设置了 <code>ellipsis</code> 和 <code>width</code>，这两个属性删除后，就可以表现正常了。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/e9a681ea0422/</id>
    <link href="https://blog.zisheng.pro/posts/e9a681ea0422/"/>
    <published>2023-03-31T03:24:13.000Z</published>
    <summary>一般 antd Table columns 比较多的时候，我们会使用 fixed 属性来固定列，但是在使用的时候发现，fixed 属性不生效，这是为什么呢？</summary>
    <title>「已解决」Antd Table columns fixed 不生效</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="副业" scheme="https://blog.zisheng.pro/categories/%E5%89%AF%E4%B8%9A/"/>
    <category term="网赚" scheme="https://blog.zisheng.pro/categories/%E5%89%AF%E4%B8%9A/%E7%BD%91%E8%B5%9A/"/>
    <category term="CPA" scheme="https://blog.zisheng.pro/tags/CPA/"/>
    <category term="每次行动成本" scheme="https://blog.zisheng.pro/tags/%E6%AF%8F%E6%AC%A1%E8%A1%8C%E5%8A%A8%E6%88%90%E6%9C%AC/"/>
    <category term="广告计费" scheme="https://blog.zisheng.pro/tags/%E5%B9%BF%E5%91%8A%E8%AE%A1%E8%B4%B9/"/>
    <category term="高效性" scheme="https://blog.zisheng.pro/tags/%E9%AB%98%E6%95%88%E6%80%A7/"/>
    <category term="量身定制" scheme="https://blog.zisheng.pro/tags/%E9%87%8F%E8%BA%AB%E5%AE%9A%E5%88%B6/"/>
    <category term="精确度高" scheme="https://blog.zisheng.pro/tags/%E7%B2%BE%E7%A1%AE%E5%BA%A6%E9%AB%98/"/>
    <category term="投放门槛高" scheme="https://blog.zisheng.pro/tags/%E6%8A%95%E6%94%BE%E9%97%A8%E6%A7%9B%E9%AB%98/"/>
    <category term="流量难控制" scheme="https://blog.zisheng.pro/tags/%E6%B5%81%E9%87%8F%E9%9A%BE%E6%8E%A7%E5%88%B6/"/>
    <category term="Cost Per Action" scheme="https://blog.zisheng.pro/tags/Cost-Per-Action/"/>
    <content>
      <![CDATA[<h2 id="什么是-CPA？"><a href="#什么是-CPA？" class="headerlink" title="什么是 CPA？"></a>什么是 CPA？</h2><p>CPA（每次行动成本，Cost Per Action）是一种广告计费方式，其中广告主只需为广告获得的实际行动（例如点击、下载、注册等）付费。与传统的广告计费方式（例如CPC和CPM）相比，CPA通常更符合广告主的目标，因为他们只需要为真正的业绩付费。</p><h2 id="CPA-优点"><a href="#CPA-优点" class="headerlink" title="CPA 优点"></a>CPA 优点</h2><h3 id="高效性"><a href="#高效性" class="headerlink" title="高效性"></a>高效性</h3><p>CPA 广告模式更加高效。相比于 CPC 广告模式依赖广告点击，CPA 广告模式更注重广告销售或者申请转化。当广告转化可预测时，CPA 广告模式将比 CPC 广告更具有吸引力。</p><h3 id="量身定制"><a href="#量身定制" class="headerlink" title="量身定制"></a>量身定制</h3><p>CPA 广告是基于受众的特征进行选择的。它可以精确地针对用户兴趣和行为来决定哪些用户更可能产生招募或购买转化，并将广告放在适当的平台上。这时候，品牌也就能够通过小投入获得较大收益</p><h3 id="精确度高"><a href="#精确度高" class="headerlink" title="精确度高"></a>精确度高</h3><p>CPA 广告不同于其他广告运营模式，由于其明确的转化目标，才能够真正达到有效广告计费的目的，广告方会根据其所期望的用户行为类型来进行广告曝光，从而达到更好的广告效果。</p><h2 id="CPA-缺点"><a href="#CPA-缺点" class="headerlink" title="CPA 缺点"></a>CPA 缺点</h2><h3 id="投放门槛高"><a href="#投放门槛高" class="headerlink" title="投放门槛高"></a>投放门槛高</h3><p>与传统的 CPC 和 CPM 广告计费方式相比，CPA 对广告主的要求较高。首先要精准地选择用户受众，其次需要建立一个可追踪转化的推广渠道。</p><h3 id="流量难控制"><a href="#流量难控制" class="headerlink" title="流量难控制"></a>流量难控制</h3><p>当你的投放目标是阅读文章、下载 APP 等有一些的行为去实现，这种情况下很难通过流量的监测来判断真正的转化行为是否发生，在这方面的效果无法够足范围内的期待。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>虽然 CPA 不适用于所有营销活动，但它已经成为了许多品牌市场营销策略中非常重要的一个组成部分。根据实际情况权衡利弊，合理整合资源，并在 CPA 广告模式下得到回报。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/25919c20aa71/</id>
    <link href="https://blog.zisheng.pro/posts/25919c20aa71/"/>
    <published>2023-03-30T13:11:30.000Z</published>
    <summary>本文描述了什么是 CPA 广告计费方式，及其优点和缺点。CPA 广告的高效性在于它更加关注广告销售或申请转化，而且能够量身定制受众，适当地放置相应平台。然而，CPA广告也有投放门槛高、流量难控等问题。</summary>
    <title>什么是 CPA</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="跨境电商" scheme="https://blog.zisheng.pro/categories/%E8%B7%A8%E5%A2%83%E7%94%B5%E5%95%86/"/>
    <category term="独立站" scheme="https://blog.zisheng.pro/tags/%E7%8B%AC%E7%AB%8B%E7%AB%99/"/>
    <category term="跨境电商" scheme="https://blog.zisheng.pro/tags/%E8%B7%A8%E5%A2%83%E7%94%B5%E5%95%86/"/>
    <category term="免费" scheme="https://blog.zisheng.pro/tags/%E5%85%8D%E8%B4%B9/"/>
    <category term="Hexo" scheme="https://blog.zisheng.pro/tags/Hexo/"/>
    <category term="静态站点" scheme="https://blog.zisheng.pro/tags/%E9%9D%99%E6%80%81%E7%AB%99%E7%82%B9/"/>
    <category term="GitHub" scheme="https://blog.zisheng.pro/tags/GitHub/"/>
    <content>
      <![CDATA[<h2 id="环境准备"><a href="#环境准备" class="headerlink" title="环境准备"></a>环境准备</h2><ol><li>NodeJS: <a href="https://nodejs.org/zh-cn/download">https://nodejs.org/zh-cn/download</a></li><li>Git: <a href="https://git-scm.com/downloads">https://git-scm.com/downloads</a></li><li>GitHub: <a href="https://github.com/">https://github.com</a></li></ol><h2 id="Hexo-搭建"><a href="#Hexo-搭建" class="headerlink" title="Hexo 搭建"></a>Hexo 搭建</h2><h3 id="创建-Hexo-站点"><a href="#创建-Hexo-站点" class="headerlink" title="创建 Hexo 站点"></a>创建 Hexo 站点</h3><blockquote><p>请替换 hyonline 为你自己的</p></blockquote><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">npm install hexo-cli -g</span><br><span class="line">hexo init hyonline</span><br><span class="line"><span class="built_in">cd</span> blog</span><br><span class="line">yarn install</span><br><span class="line">yarn server</span><br></pre></td></tr></table></figure><p>按照提示打开 <code>http://localhost:4000</code>，如果看到 Hexo 的默认页面，说明安装成功。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1680158474181.png" alt="Hexo 本地预览"></p><h3 id="推送到-GitHub"><a href="#推送到-GitHub" class="headerlink" title="推送到 GitHub"></a>推送到 GitHub</h3><p>1、创建 GitHub 仓库</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1680158386923.png" alt="创建 GitHub 仓库"></p><blockquote><p>仓库格式是 username.github.io</p></blockquote><p>2、推送到 GitHub</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">git init</span><br><span class="line">git add .</span><br><span class="line">git commit -m <span class="string">&quot;First Commit&quot;</span></span><br><span class="line">git remote add origin https://github.com/hyonline-store/hyonline-store.github.io.git</span><br><span class="line">git branch -M main</span><br><span class="line">git push -u origin main</span><br></pre></td></tr></table></figure><h2 id="自动部署"><a href="#自动部署" class="headerlink" title="自动部署"></a>自动部署</h2><p>1、设置 Workflow permissions 为 Read and write permissions</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1680162995972.png" alt="Workflow permissions"></p><p>2、添加 <code>.github/workflows</code> 目录，创建 <code>gh-pages.yml</code> 文件，内容如下：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">github</span> <span class="string">pages</span></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line">  <span class="attr">push:</span></span><br><span class="line">    <span class="attr">branches:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">main</span> <span class="comment"># default branch</span></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">deploy:</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-22.04</span></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">actions/checkout@v2</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">c-hive/gha-yarn-cache@v2</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">run:</span> <span class="string">yarn</span> <span class="string">install</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">run:</span> <span class="string">yarn</span> <span class="string">build</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Deploy</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">peaceiris/actions-gh-pages@v3</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">github_token:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.GITHUB_TOKEN</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">publish_dir:</span> <span class="string">./public</span></span><br></pre></td></tr></table></figure><p>3、 部署成功后，设置 Pages</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1680163086920.png" alt="设置 GitHub Pages"></p><p>等待 GitHub Action 执行完成，就可以通过 <code>https://hyonline-store.github.io</code> 访问了。</p><h2 id="域名解析"><a href="#域名解析" class="headerlink" title="域名解析"></a>域名解析</h2><p>1、添加 A 记录（Godaddy 为例）</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1680169974154.png" alt="添加 A 记录"></p><p>2、添加 CNAME 记录（Godaddy 为例）</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1680170058837.png" alt="添加 CNAME 记录"></p><p>3、GitHub Pages 解析</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1680170403897.png" alt="GitHub Pages 解析"></p><p>4、Hexo CNAME 设置</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">name: github pages</span><br><span class="line">on:</span><br><span class="line">  push:</span><br><span class="line">    branches:</span><br><span class="line">      - main # default branch</span><br><span class="line">jobs:</span><br><span class="line">  deploy:</span><br><span class="line">    runs-on: ubuntu-22.04</span><br><span class="line">    steps:</span><br><span class="line">      - uses: actions/checkout@v2</span><br><span class="line">      - uses: c-hive/gha-yarn-cache@v2</span><br><span class="line">      - run: yarn install</span><br><span class="line">      - run: yarn build</span><br><span class="line">      - name: Deploy</span><br><span class="line">        uses: peaceiris/actions-gh-pages@v3</span><br><span class="line">        with:</span><br><span class="line">          github_token: $&#123;&#123; secrets.GITHUB_TOKEN &#125;&#125;</span><br><span class="line">          publish_dir: ./public</span><br><span class="line"><span class="addition">+         cname: you.hyonline.store</span></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/632d3a121d65/</id>
    <link href="https://blog.zisheng.pro/posts/632d3a121d65/"/>
    <published>2023-03-29T11:05:04.000Z</published>
    <summary>
      <![CDATA[<h2 id="环境准备"><a href="#环境准备" class="headerlink" title="环境准备"></a>环境准备</h2><ol>
<li>NodeJS: <a href="https://nodejs.org/zh-cn/download">http]]>
    </summary>
    <title>Hexo × Github Pages 免费搭建博客</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="Golang" scheme="https://blog.zisheng.pro/categories/Golang/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/tags/Golang/"/>
    <category term="Cobra" scheme="https://blog.zisheng.pro/tags/Cobra/"/>
    <category term="CLI" scheme="https://blog.zisheng.pro/tags/CLI/"/>
    <category term="中文文档" scheme="https://blog.zisheng.pro/tags/%E4%B8%AD%E6%96%87%E6%96%87%E6%A1%A3/"/>
    <content>
      <![CDATA[<h2 id="关于"><a href="#关于" class="headerlink" title="关于"></a>关于</h2><p>Cobra 是 Go 的 CLI 框架。它包含一个用于创建功能强大的现代 CLI 应用程序的库，以及一个用于快速生成基于 Cobra 的应用程序和命令文件的工具。</p><p>Cobra 由 Go 项目成员和 hugo 作者 <a href="https://github.com/spf13">spf13</a> 创建，已经被许多流行的 Go 项目采用，比如 <a href="https://github.com/cli/cli">GitHub CLI</a> 和 <a href="https://github.com/docker/cli">Docker CLI</a>。</p><h2 id="特性"><a href="#特性" class="headerlink" title="特性"></a>特性</h2><ul><li>简单的基于子命令的 CLIs：<code>app server</code>、<code>app fetch</code> 等；</li><li>完全兼容 <a href="https://zh.wikipedia.org/wiki/%E5%8F%AF%E7%A7%BB%E6%A4%8D%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E6%8E%A5%E5%8F%A3">POSIX（可移植操作系统接口）</a> 的标志（包括短版和长版）</li><li>嵌套子命令</li><li>全局、局部和级联的标志</li><li>使用 <code>cobra init appname</code> 和 <code>cobra add cmdname</code> 轻松生成应用程序和命令</li><li>智能提示（<code>app srver</code> …did you mean <code>app server</code>）</li><li>自动生成命令和标志的帮助</li><li>自动识别 <code>-h</code>、<code>--help</code> 等帮助标识</li><li>自动为你的应用程序生成的 bash 自动完成</li><li>自动为你的应用程序生成 man 手册</li><li>命令别名，以便你可以更改内容而不会破坏它们</li><li>定义自己的帮助，用法等的灵活性。</li><li>可选与 <a href="https://github.com/spf13/viper">viper</a> 紧密集成，可用于 <a href="https://12factor.net/zh_cn/">12factor</a> 应用程序</li></ul><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><p>Cobra 非常易用，首先使用 <code>go get</code> 命令安装最新版本。此命令将安装 <code>cobra</code> generator 的可执行文件及其依赖项：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ go get -u github.com/spf13/cobra/cobra</span><br></pre></td></tr></table></figure><h2 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h2><p>Cobra 构建在命令（commands）、参数（arguments）和 标志（flags）上。</p><p><strong>Commands</strong> 代表动作，<strong>Args</strong> 是事物，<strong>Flags</strong> 是这些动作的修饰符。</p><p>最好的应用程序在使用时会像句子一样读起来。用户将知道如何使用该应用程序，因为他们将自然地了解如何使用它。</p><p>遵循的模式是 <code>APPNAME VERB NOUN --ADJECTIVE</code>。 或 <code>APPNAME COMMAND ARG --FLAG</code></p><p>一些真实的例子可以更好地说明这一点。</p><p>在以下示例中，<code>server</code> 是命令，<code>port</code> 是标志：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hugo server --port=1313</span><br></pre></td></tr></table></figure><p>在此命令中，我们告诉 Git 克隆 url 的内容：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> URL --bare</span><br></pre></td></tr></table></figure><h3 id="命令（Command）"><a href="#命令（Command）" class="headerlink" title="命令（Command）"></a>命令（Command）</h3><p>命令是应用程序的核心。应用程序提供的每一个交互都包含在 Command 中。一个命令可以有子命令和可选的运行一个动作。</p><p>在上面的示例中，<code>server</code> 是命令。</p><p><a href="https://godoc.org/github.com/spf13/cobra#Command">cobra.Command API</a></p><h3 id="标志（Flags）"><a href="#标志（Flags）" class="headerlink" title="标志（Flags）"></a>标志（Flags）</h3><p>一个标志是一种修饰命令行为的方式。Cobra 支持完全符合 <a href="https://zh.wikipedia.org/wiki/%E5%8F%AF%E7%A7%BB%E6%A4%8D%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E6%8E%A5%E5%8F%A3">POSIX（可移植操作系统接口）</a> 的标志和 Go <a href="https://golang.org/pkg/flag/">flag</a> 包。</p><p>Cobra 命令可以定义一直保留到子命令的标志和仅可用于该命令的标志。</p><p>在上面的例子中，<code>port</code> 是标志。</p><p>标志的功能是 <a href="https://github.com/spf13/pflag">pflag</a> 库提供的，该库是一个标准库的 fork，在维护相同接口的基础上兼容了 <a href="https://zh.wikipedia.org/wiki/%E5%8F%AF%E7%A7%BB%E6%A4%8D%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E6%8E%A5%E5%8F%A3">POSIX（可移植操作系统接口）</a>。</p><h2 id="入门"><a href="#入门" class="headerlink" title="入门"></a>入门</h2><p>欢迎大家提供自己的项目组织结构，但是通常基于 Cobra 的应用程序将遵循以下组织结构</p><Tree title="appName">  <ul>    <li>      cmd      <small>放置命令的文件夹</small>      <ul>        <li>add.go</li>        <li>your.go</li>        <li>commands.go</li>        <li>here.go</li>      </ul>    </li>    <li>      main.go      <small>应用程序入口</small>    </li>  </ul></Tree><p>在 Cobra 应用程序中，通常 <code>main.go</code> 文件非常。它有一个目的：初始化 Cobra。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">  <span class="string">&quot;&#123;pathToYourApp&#125;/cmd&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">  cmd.Execute()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="使用-Cobra-生成器"><a href="#使用-Cobra-生成器" class="headerlink" title="使用 Cobra 生成器"></a>使用 Cobra 生成器</h3><p>Cobra 提供了 CLI 来创建您的应用程序和添加任意你想要的命令。这是将 Cobra 集成到您的应用程序中的最简单方法。</p><p><a href="https://github.com/spf13/cobra/blob/master/cobra/README.md">这里</a> 你可以查看更多关于生成器的资料。</p><h3 id="使用-Cobra-库"><a href="#使用-Cobra-库" class="headerlink" title="使用 Cobra 库"></a>使用 Cobra 库</h3><p>要手动接入 Cobra，您需要创建一个 <code>main.go</code> 文件和 <code>rootCmd</code> 文件。您可以选择提供合适的其他命令。</p><h4 id="创建-rootCmd"><a href="#创建-rootCmd" class="headerlink" title="创建 rootCmd"></a>创建 rootCmd</h4><p>Cobra 不需要任何特殊的构造函数。只需创建您的命令。</p><p>理想情况下，将其放置在 <code>/cmd/root.go</code> 中：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// rootCmd 代表没有调用子命令时的基础命令</span></span><br><span class="line"><span class="keyword">var</span> rootCmd = &amp;cobra.Command&#123;</span><br><span class="line">Use:   <span class="string">&quot;hugo&quot;</span>,</span><br><span class="line">Short: <span class="string">&quot;Hugo is a very fast static site generator&quot;</span>,</span><br><span class="line">  Long: <span class="string">`A Fast and Flexible Static Site Generator built with</span></span><br><span class="line"><span class="string">                love by spf13 and friends in Go.</span></span><br><span class="line"><span class="string">                Complete documentation is available at http://hugo.spf13.com`</span>,</span><br><span class="line">  <span class="comment">// 如果有相关的 action 要执行，请取消下面这行代码的注释</span></span><br><span class="line">  <span class="comment">// Run: func(cmd *cobra.Command, args []string) &#123; &#125;,</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Execute 将所有子命令添加到root命令并适当设置标志。</span></span><br><span class="line"><span class="comment">// 这由 main.main() 调用。它只需要对 rootCmd 调用一次。</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Execute</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">if</span> err := rootCmd.Execute(); err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Println(err)</span><br><span class="line">os.Exit(<span class="number">1</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>您还将在 <code>init()</code> 函数中定义标志并处理配置。例子如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// cmd/root.go</span></span><br><span class="line"><span class="keyword">package</span> cmd</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line"><span class="string">&quot;os&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="string">&quot;github.com/spf13/cobra&quot;</span></span><br><span class="line"></span><br><span class="line">homedir <span class="string">&quot;github.com/mitchellh/go-homedir&quot;</span></span><br><span class="line"><span class="string">&quot;github.com/spf13/viper&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> cfgFile <span class="type">string</span></span><br><span class="line"><span class="keyword">var</span> projectBase <span class="type">string</span></span><br><span class="line"><span class="keyword">var</span> userLicense <span class="type">string</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// rootCmd 代表没有调用子命令时的基础命令</span></span><br><span class="line"><span class="keyword">var</span> rootCmd = &amp;cobra.Command&#123;</span><br><span class="line">Use:   <span class="string">&quot;hugo&quot;</span>,</span><br><span class="line">Short: <span class="string">&quot;Hugo is a very fast static site generator&quot;</span>,</span><br><span class="line">  Long: <span class="string">`A Fast and Flexible Static Site Generator built with</span></span><br><span class="line"><span class="string">                love by spf13 and friends in Go.</span></span><br><span class="line"><span class="string">                Complete documentation is available at http://hugo.spf13.com`</span>,</span><br><span class="line">  <span class="comment">// 如果有相关的 action 要执行，请取消下面这行代码的注释</span></span><br><span class="line">  <span class="comment">// Run: func(cmd *cobra.Command, args []string) &#123; &#125;,</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Execute 将所有子命令添加到root命令并适当设置标志。会被 main.main() 调用一次。</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Execute</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">if</span> err := rootCmd.Execute(); err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Println(err)</span><br><span class="line">os.Exit(<span class="number">1</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">init</span><span class="params">()</span></span> &#123;</span><br><span class="line">cobra.OnInitialize(initConfig)</span><br><span class="line">rootCmd.PersistentFlags().StringVar(&amp;cfgFile, <span class="string">&quot;config&quot;</span>, <span class="string">&quot;&quot;</span>, <span class="string">&quot;config file (default is $HOME/.cobra.yaml)&quot;</span>)</span><br><span class="line">rootCmd.PersistentFlags().StringVarP(&amp;projectBase, <span class="string">&quot;projectbase&quot;</span>, <span class="string">&quot;b&quot;</span>, <span class="string">&quot;&quot;</span>, <span class="string">&quot;base project directory eg. github.com/spf13/&quot;</span>)</span><br><span class="line">rootCmd.PersistentFlags().StringP(<span class="string">&quot;author&quot;</span>, <span class="string">&quot;a&quot;</span>, <span class="string">&quot;YOUR NAME&quot;</span>, <span class="string">&quot;Author name for copyright attribution&quot;</span>)</span><br><span class="line">rootCmd.PersistentFlags().StringVarP(&amp;userLicense, <span class="string">&quot;license&quot;</span>, <span class="string">&quot;l&quot;</span>, <span class="string">&quot;&quot;</span>, <span class="string">&quot;Name of license for the project (can provide `licensetext` in config)&quot;</span>)</span><br><span class="line">rootCmd.PersistentFlags().Bool(<span class="string">&quot;viper&quot;</span>, <span class="literal">true</span>, <span class="string">&quot;Use Viper for configuration&quot;</span>)</span><br><span class="line">viper.BindPFlag(<span class="string">&quot;author&quot;</span>, rootCmd.PersistentFlags().Lookup(<span class="string">&quot;author&quot;</span>))</span><br><span class="line">viper.BindPFlag(<span class="string">&quot;projectbase&quot;</span>, rootCmd.PersistentFlags().Lookup(<span class="string">&quot;projectbase&quot;</span>))</span><br><span class="line">viper.BindPFlag(<span class="string">&quot;useViper&quot;</span>, rootCmd.PersistentFlags().Lookup(<span class="string">&quot;viper&quot;</span>))</span><br><span class="line">viper.SetDefault(<span class="string">&quot;author&quot;</span>, <span class="string">&quot;NAME HERE &lt;EMAIL ADDRESS&gt;&quot;</span>)</span><br><span class="line">viper.SetDefault(<span class="string">&quot;license&quot;</span>, <span class="string">&quot;apache&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">initConfig</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// Don&#x27;t forget to read config either from cfgFile or from home directory!</span></span><br><span class="line"><span class="keyword">if</span> cfgFile != <span class="string">&quot;&quot;</span> &#123;</span><br><span class="line"><span class="comment">// Use config file from the flag.</span></span><br><span class="line">viper.SetConfigFile(cfgFile)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line"><span class="comment">// Find home directory.</span></span><br><span class="line">home, err := homedir.Dir()</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Println(err)</span><br><span class="line">os.Exit(<span class="number">1</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Search config in home directory with name &quot;.cobra&quot; (without extension).</span></span><br><span class="line">viper.AddConfigPath(home)</span><br><span class="line">viper.SetConfigName(<span class="string">&quot;.cobra&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> err := viper.ReadInConfig(); err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;Can&#x27;t read config:&quot;</span>, err)</span><br><span class="line">os.Exit(<span class="number">1</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="创建-main-go"><a href="#创建-main-go" class="headerlink" title="创建 main.go"></a>创建 main.go</h3><p>有了根命令，你需要一个 main 函数去执行它。为了清晰起见，<code>Execute</code> 应该在根目录上运行，尽管可以在任何命令上调用它。</p><p>在 Cobra 应用中，<code>main.go</code> 是非常简单的。它只有一个作用——初始化 Cobra。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// main.go</span></span><br><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">  <span class="string">&quot;&#123;pathToYourApp&#125;/cmd&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">  cmd.Execute()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="创建额外的命令"><a href="#创建额外的命令" class="headerlink" title="创建额外的命令"></a>创建额外的命令</h3><p>可以定义其他命令，并且通常在 <code>cmd/</code> 目录中为每个命令提供自己的文件。</p><p>如果要创建 <code>version</code> 命令，则可以创建 <code>cmd/version.go</code> 并使用以下命令进行填充：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// cmd/version.go</span></span><br><span class="line"><span class="keyword">package</span> cmd</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">  <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line">  <span class="string">&quot;github.com/spf13/cobra&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">init</span><span class="params">()</span></span> &#123;</span><br><span class="line">  rootCmd.AddCommand(versionCmd)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> versionCmd = &amp;cobra.Command&#123;</span><br><span class="line">  Use:   <span class="string">&quot;version&quot;</span>,</span><br><span class="line">  Short: <span class="string">&quot;Print the version number of Hugo&quot;</span>,</span><br><span class="line">  Long:  <span class="string">`All software has versions. This is Hugo&#x27;s`</span>,</span><br><span class="line">  Run: <span class="function"><span class="keyword">func</span><span class="params">(cmd *cobra.Command, args []<span class="type">string</span>)</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;Hugo Static Site Generator v0.9 -- HEAD&quot;</span>)</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="使用标志"><a href="#使用标志" class="headerlink" title="使用标志"></a>使用标志</h3><p>标志提供修饰符以控制命令的操作方式。</p><p>由于标志是在不同位置定义和使用的，我们需要在外部定义一个具有正确作用域的变量，以分配要使用的标志。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> verbose <span class="type">bool</span></span><br><span class="line"><span class="keyword">var</span> source <span class="type">string</span></span><br></pre></td></tr></table></figure><p>这里有两种不同分配标志的方法。</p><h4 id="持久标志"><a href="#持久标志" class="headerlink" title="持久标志"></a>持久标志</h4><p>标志可以是 “persistent” 的，这意味着该标志将可用于分配给它的命令以及该命令下的每个命令。对于全局标志，将标志分配为根上的持久标志。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rootCmd.PersistentFlags().BoolVarP(&amp;verbose, <span class="string">&quot;verbose&quot;</span>, <span class="string">&quot;v&quot;</span>, <span class="literal">false</span>, <span class="string">&quot;verbose output&quot;</span>)</span><br></pre></td></tr></table></figure><h4 id="本地标志"><a href="#本地标志" class="headerlink" title="本地标志"></a>本地标志</h4><p>也可以在本地分配一个标志，该标志仅适用于该特定命令。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rootCmd.Flags().StringVarP(&amp;source, <span class="string">&quot;source&quot;</span>, <span class="string">&quot;s&quot;</span>, <span class="string">&quot;&quot;</span>, <span class="string">&quot;Source directory to read from&quot;</span>)</span><br></pre></td></tr></table></figure><h4 id="父命令上的本地标志"><a href="#父命令上的本地标志" class="headerlink" title="父命令上的本地标志"></a>父命令上的本地标志</h4><p>默认情况下，Cobra 仅解析目标命令上的本地标志，而忽略父命令上的任何本地标志。通过启用 <code>Command.TraverseChildren</code>，Cobra 将在执行目标命令之前解析每个命令上的本地标志</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">command := cobra.Command&#123;</span><br><span class="line">  Use: <span class="string">&quot;print [OPTIONS] [COMMANDS]&quot;</span>,</span><br><span class="line">  TraverseChildren: <span class="literal">true</span>,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="用配置绑定标志"><a href="#用配置绑定标志" class="headerlink" title="用配置绑定标志"></a>用配置绑定标志</h4><p>您还可以将标志与 <a href="https://github.com/spf13/viper">viper</a> 绑定：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> author <span class="type">string</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">init</span><span class="params">()</span></span> &#123;</span><br><span class="line">  rootCmd.PersistentFlags().StringVar(&amp;author, <span class="string">&quot;author&quot;</span>, <span class="string">&quot;YOUR NAME&quot;</span>, <span class="string">&quot;Author name for copyright attribution&quot;</span>)</span><br><span class="line">  viper.BindPFlag(<span class="string">&quot;author&quot;</span>, rootCmd.PersistentFlags().Lookup(<span class="string">&quot;author&quot;</span>))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在此示例中，持久标记 <code>author</code> 与 viper 绑定。请注意，当用户未提供 <code>--author</code> 标志时，变量 <code>author</code> 不会设置为 <code>config</code> 中的值。</p><p>更多信息请查看 <a href="https://github.com/spf13/viper">viper</a>。</p><h4 id="必需标志"><a href="#必需标志" class="headerlink" title="必需标志"></a>必需标志</h4><p>标志默认是可选的。如果你想在缺少标志时命令报错，请设置该标志为必需：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> region <span class="type">string</span></span><br><span class="line"></span><br><span class="line">rootCmd.Flags().StringVarP(&amp;region, <span class="string">&quot;region&quot;</span>, <span class="string">&quot;r&quot;</span>, <span class="string">&quot;&quot;</span>, <span class="string">&quot;AWS region (required)&quot;</span>)</span><br><span class="line">rootCmd.MarkFlagRequired(<span class="string">&quot;region&quot;</span>)</span><br></pre></td></tr></table></figure><h3 id="位置和自定义参数"><a href="#位置和自定义参数" class="headerlink" title="位置和自定义参数"></a>位置和自定义参数</h3><p>可以使用 Command 的 Args 字段指定位置参数的验证。</p><p>下面的验证符是内置的：</p><ul><li><code>NoArgs</code> - 如果有任何位置参数，该命令将报告错误。</li><li><code>ArbitraryArgs</code> - 命令将接受任意参数</li><li><code>OnlyValidArgs</code> - 如果 Command 的 <code>ValidArgs</code> 字段中不存在该位置参数，则该命令将报告错误。</li><li><code>MinimumNArgs(int)</code> - 如果不存在至少 N 个位置参数，则该命令将报告错误。</li><li><code>MaximumNArgs(int)</code> - 如果存在超过 N 个位置参数，则该命令将报告错误。</li><li><code>ExactArgs(int)</code> - 如果不存在 N 个位置参数，则该命令将报告错误。</li><li><code>ExactValidArgs(int)</code> - 如果没有确切的 N 个位置参数，或者如果 Command 的 ValidArgs 字段中不存在该位置参数，则该命令将报告并出错。</li><li><code>RangeArgs(min, max)</code> - 如果 args 的数目不在期望的 args 的最小和最大数目之间，则该命令将报告并出错。</li></ul><p>内置验证符使用实例：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> cmd = &amp;cobra.Command&#123;</span><br><span class="line">Use:   <span class="string">&quot;hello&quot;</span>,</span><br><span class="line">Short: <span class="string">&quot;hello&quot;</span>,</span><br><span class="line">Args:  cobra.MinimumNArgs(<span class="number">2</span>),</span><br><span class="line">Run: <span class="function"><span class="keyword">func</span><span class="params">(cmd *cobra.Command, args []<span class="type">string</span>)</span></span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;Hello, World!&quot;</span>)</span><br><span class="line">&#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>如果只传递一个位置参数会报 <code>Error: requires at least 2 arg(s), only received 1</code> 的警告。</p></blockquote><p>设置自定义验证器的示例：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> cmd = &amp;cobra.Command&#123;</span><br><span class="line">  Short: <span class="string">&quot;hello&quot;</span>,</span><br><span class="line">  Args: <span class="function"><span class="keyword">func</span><span class="params">(cmd *cobra.Command, args []<span class="type">string</span>)</span></span> <span class="type">error</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> <span class="built_in">len</span>(args) &lt; <span class="number">1</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> errors.New(<span class="string">&quot;requires at least one arg&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> myapp.IsValidColor(args[<span class="number">0</span>]) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> fmt.Errorf(<span class="string">&quot;invalid color specified: %s&quot;</span>, args[<span class="number">0</span>])</span><br><span class="line">  &#125;,</span><br><span class="line">  Run: <span class="function"><span class="keyword">func</span><span class="params">(cmd *cobra.Command, args []<span class="type">string</span>)</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;Hello, World!&quot;</span>)</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h3><p>在下面的例子中，我们定义了三个命令。两个在顶层，一个（<code>cmdTimes</code>）是子命令。在这种情况下，根目录不可执行，这意味着需要一个子命令。通过不为 <code>rootCmd</code> 提供 <code>Run</code> 来实现。</p><p>我们只为一个命令定义了一个标志。</p><p>关于标志的文档在 [pflag]<a href="https://github.com/spf13/pflag)%E3%80%82">https://github.com/spf13/pflag)。</a></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line"><span class="string">&quot;strings&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="string">&quot;github.com/spf13/cobra&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> echoTimes <span class="type">int</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> cmdPrint = &amp;cobra.Command&#123;</span><br><span class="line">Use:   <span class="string">&quot;Print [string to print]&quot;</span>,</span><br><span class="line">Short: <span class="string">&quot;Print anything to the screen&quot;</span>,</span><br><span class="line">Long: <span class="string">`print is for printing anything back to the screen.</span></span><br><span class="line"><span class="string">For many years people have printed back to the screen.`</span>,</span><br><span class="line">Args: cobra.MinimumNArgs(<span class="number">1</span>),</span><br><span class="line">Run: <span class="function"><span class="keyword">func</span><span class="params">(cmd *cobra.Command, args []<span class="type">string</span>)</span></span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;Print: &quot;</span> + strings.Join(args, <span class="string">&quot; &quot;</span>))</span><br><span class="line">&#125;,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> cmdEcho = &amp;cobra.Command&#123;</span><br><span class="line">Use:   <span class="string">&quot;echo [string to echo]&quot;</span>,</span><br><span class="line">Short: <span class="string">&quot;Echo anything to the screen&quot;</span>,</span><br><span class="line">Long: <span class="string">`echo is for echoing anything back.</span></span><br><span class="line"><span class="string">Echo works a lot like print, except it has a child command.`</span>,</span><br><span class="line">Args: cobra.MinimumNArgs(<span class="number">1</span>),</span><br><span class="line">Run: <span class="function"><span class="keyword">func</span><span class="params">(cmd *cobra.Command, args []<span class="type">string</span>)</span></span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;Print: &quot;</span> + strings.Join(args, <span class="string">&quot; &quot;</span>))</span><br><span class="line">&#125;,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> cmdTimes = &amp;cobra.Command&#123;</span><br><span class="line">Use:   <span class="string">&quot;times [# times] [string to echo]&quot;</span>,</span><br><span class="line">Short: <span class="string">&quot;Echo anyting to the screen more times&quot;</span>,</span><br><span class="line">Long: <span class="string">`echo things multiple times back to the user y providing</span></span><br><span class="line"><span class="string">a count and a string.`</span>,</span><br><span class="line">Args: cobra.MinimumNArgs(<span class="number">1</span>),</span><br><span class="line">Run: <span class="function"><span class="keyword">func</span><span class="params">(cmd *cobra.Command, args []<span class="type">string</span>)</span></span> &#123;</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; echoTimes; i++ &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;Echo: &quot;</span> + strings.Join(args, <span class="string">&quot; &quot;</span>))</span><br><span class="line">&#125;</span><br><span class="line">&#125;,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">cmdTimes.Flags().IntVarP(&amp;echoTimes, <span class="string">&quot;times&quot;</span>, <span class="string">&quot;t&quot;</span>, <span class="number">1</span>, <span class="string">&quot;times to echo the input&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 设置根命令</span></span><br><span class="line"><span class="keyword">var</span> rootCmd = &amp;cobra.Command&#123;Use: <span class="string">&quot;app&quot;</span>&#125;</span><br><span class="line">rootCmd.AddCommand(cmdPrint, cmdEcho)</span><br><span class="line">cmdEcho.AddCommand(cmdTimes)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 初始化应用</span></span><br><span class="line">rootCmd.Execute()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>更复杂的应用，请参考 <a href="https://github.com/gohugoio/hugo">Hugo</a> 或者 <a href="https://github.com/cli/cli">GitHub CLI</a>。</p><h3 id="帮助命令"><a href="#帮助命令" class="headerlink" title="帮助命令"></a>帮助命令</h3><p>当你添加了子命令，Cobra 会自动添加一些帮助命令。当你执行 <code>app help</code> 命令时会显示帮助信息。另外，<code>help</code> 还支持其他命令作为输入参数。举例来说，你有一个没有额外配置的 <code>create</code> 命令，<code>app help create</code> 是有效的。每一个命令还会自动获取一个 <code>--help</code> 标志。</p><h4 id="示例-1"><a href="#示例-1" class="headerlink" title="示例"></a>示例</h4><p>以下输出由 Cobra 自动生成。 除了命令和标志定义外，什么都不需要。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4ab343af6ed24362b1926bb8fd0a23b0~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p><code>help</code> 就像其他命令一样。并没有特殊的逻辑或行为。实际上，你可以根据需要提供自己的服务。</p><h4 id="定义你自己的-help"><a href="#定义你自己的-help" class="headerlink" title="定义你自己的 help"></a>定义你自己的 help</h4><p>你可以使用下面的方法提供你自己的 Help 命令或模板。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">cmd.SetHelpCommand(cmd *Command)</span><br><span class="line">cmd.setHelpCommand(f <span class="function"><span class="keyword">func</span><span class="params">(*Command, []<span class="type">string</span>)</span></span>)</span><br><span class="line">cmd.setHelpTemplate(s <span class="type">string</span>)</span><br></pre></td></tr></table></figure><p>后两者也适用于所有子命令。</p><h3 id="使用信息"><a href="#使用信息" class="headerlink" title="使用信息"></a>使用信息</h3><p>当用户提供无效的标志或无效的命令时，Cobra 会通过向用户显示 <code>usage</code> 进行响应。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0f1975f1c578418ebd37e81b682e69dd~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><h4 id="定义你自己的使用信息"><a href="#定义你自己的使用信息" class="headerlink" title="定义你自己的使用信息"></a>定义你自己的使用信息</h4><p>你可以提供你自己的 usage 函数或模板。像 <code>help</code> 一样，函数和模板可通过公共方法重写：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cmd.SetUsageFunc(f <span class="function"><span class="keyword">func</span><span class="params">(*Command)</span></span> <span class="type">error</span>)</span><br><span class="line">cmd.SetUsageTemplate(s <span class="type">string</span>)</span><br></pre></td></tr></table></figure><p>可以参考 <a href="https://github.com/cli/cli/blob/dcf5a27f5343ea0e9b3ef71ca37a4c3948102667/pkg/cmd/root/root.go#L63">GitHub CLI</a> 的写法。</p><h3 id="版本标志"><a href="#版本标志" class="headerlink" title="版本标志"></a>版本标志</h3><p>如果给根命令设置了 <code>Version</code> 字段，Cobra 会添加一个顶级的 <code>--version</code> 标志。运行带有 <code>–version</code> 标志的应用程序，将使用版本模板将版本打印到 stdout。模板可以使用 <code>cmd.SetVersionTemplate(s string)</code> 函数自定义。</p><blockquote><p><code>SetVersionTemplate</code> 的使用可以参考 <a href="https://github.com/cli/cli/blob/dcf5a27f5343ea0e9b3ef71ca37a4c3948102667/pkg/cmd/root/root.go#L67">GitHub CLI</a></p></blockquote><h3 id="PreRun-和-PostRun-Hooks"><a href="#PreRun-和-PostRun-Hooks" class="headerlink" title="PreRun 和 PostRun Hooks"></a>PreRun 和 PostRun Hooks</h3><p>可以在执行命令之前和之后运行一个函数。<code>PersistentPreRun</code> 和 <code>PreRun</code> 函数将在 <code>Run</code> 之前执行。<code>PersistentPostRun</code> 和 <code>PostRun</code> 会在 <code>Run</code> 之后运行。如果子级未声明自己的 <code>Persistent * Run</code> 函数，则子级将继承父级的。这些函数的执行顺续如下：</p><ul><li>PersistentPreRun</li><li>PreRun</li><li>Run</li><li>PostRun</li><li>PersistentPostRun</li></ul><p>下面这个包含了两个命令的例子使用了这些特性。当子命令执行时，它会运行根命令的 <code>PersistentPreRun</code>，但是不会运行根命令的 <code>PersistentPostRun</code>：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="string">&quot;github.com/spf13/cobra&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> rootCmd = &amp;cobra.Command&#123;</span><br><span class="line">Use:   <span class="string">&quot;root [sub]&quot;</span>,</span><br><span class="line">Short: <span class="string">&quot;My root command&quot;</span>,</span><br><span class="line">PersistentPreRun: <span class="function"><span class="keyword">func</span><span class="params">(cmd *cobra.Command, args []<span class="type">string</span>)</span></span> &#123;</span><br><span class="line">fmt.Printf(<span class="string">&quot;Inside rootCmd PersistentPreRun with args: %v\n&quot;</span>, args)</span><br><span class="line">&#125;,</span><br><span class="line">PreRun: <span class="function"><span class="keyword">func</span><span class="params">(cmd *cobra.Command, args []<span class="type">string</span>)</span></span> &#123;</span><br><span class="line">fmt.Printf(<span class="string">&quot;Inside rootCmd PreRun with args: %v\n&quot;</span>, args)</span><br><span class="line">&#125;,</span><br><span class="line">Run: <span class="function"><span class="keyword">func</span><span class="params">(cmd *cobra.Command, args []<span class="type">string</span>)</span></span> &#123;</span><br><span class="line">fmt.Printf(<span class="string">&quot;Inside rootCmd Run with args: %v\n&quot;</span>, args)</span><br><span class="line">&#125;,</span><br><span class="line">PostRun: <span class="function"><span class="keyword">func</span><span class="params">(cmd *cobra.Command, args []<span class="type">string</span>)</span></span> &#123;</span><br><span class="line">fmt.Printf(<span class="string">&quot;Inside rootCmd PostRun with args: %v\n&quot;</span>, args)</span><br><span class="line">&#125;,</span><br><span class="line">PersistentPostRun: <span class="function"><span class="keyword">func</span><span class="params">(cmd *cobra.Command, args []<span class="type">string</span>)</span></span> &#123;</span><br><span class="line">fmt.Printf(<span class="string">&quot;Inside rootCmd PersistentPostRun with args: %v\n&quot;</span>, args)</span><br><span class="line">&#125;,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">subCmd := &amp;cobra.Command&#123;</span><br><span class="line">Use:   <span class="string">&quot;sub [no options!]&quot;</span>,</span><br><span class="line">Short: <span class="string">&quot;My subcommand&quot;</span>,</span><br><span class="line">PreRun: <span class="function"><span class="keyword">func</span><span class="params">(cmd *cobra.Command, args []<span class="type">string</span>)</span></span> &#123;</span><br><span class="line">fmt.Printf(<span class="string">&quot;Inside subCmd PreRun with args: %v\n&quot;</span>, args)</span><br><span class="line">&#125;,</span><br><span class="line">Run: <span class="function"><span class="keyword">func</span><span class="params">(cmd *cobra.Command, args []<span class="type">string</span>)</span></span> &#123;</span><br><span class="line">fmt.Printf(<span class="string">&quot;Inside subCmd Run with args: %v\n&quot;</span>, args)</span><br><span class="line">&#125;,</span><br><span class="line">PostRun: <span class="function"><span class="keyword">func</span><span class="params">(cmd *cobra.Command, args []<span class="type">string</span>)</span></span> &#123;</span><br><span class="line">fmt.Printf(<span class="string">&quot;Inside subCmd PostRun with args: %v\n&quot;</span>, args)</span><br><span class="line">&#125;,</span><br><span class="line">PersistentPostRun: <span class="function"><span class="keyword">func</span><span class="params">(cmd *cobra.Command, args []<span class="type">string</span>)</span></span> &#123;</span><br><span class="line">fmt.Printf(<span class="string">&quot;Inside subCmd PersistentPostRun with args: %v\n&quot;</span>, args)</span><br><span class="line">&#125;,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">rootCmd.AddCommand(subCmd)</span><br><span class="line"></span><br><span class="line">rootCmd.SetArgs([]<span class="type">string</span>&#123;<span class="string">&quot;&quot;</span>&#125;)</span><br><span class="line">rootCmd.Execute()</span><br><span class="line">fmt.Println()</span><br><span class="line">rootCmd.SetArgs([]<span class="type">string</span>&#123;<span class="string">&quot;sub&quot;</span>, <span class="string">&quot;arg1&quot;</span>, <span class="string">&quot;arg2&quot;</span>&#125;)</span><br><span class="line">rootCmd.Execute()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">Inside rootCmd PersistentPreRun with args: []</span><br><span class="line">Inside rootCmd PreRun with args: []</span><br><span class="line">Inside rootCmd Run with args: []</span><br><span class="line">Inside rootCmd PostRun with args: []</span><br><span class="line">Inside rootCmd PersistentPostRun with args: []</span><br><span class="line"></span><br><span class="line">Inside rootCmd PersistentPreRun with args: [arg1 arg2]</span><br><span class="line">Inside subCmd PreRun with args: [arg1 arg2]</span><br><span class="line">Inside subCmd Run with args: [arg1 arg2]</span><br><span class="line">Inside subCmd PostRun with args: [arg1 arg2]</span><br><span class="line">Inside subCmd PersistentPostRun with args: [arg1 arg2]</span><br></pre></td></tr></table></figure><h3 id="“unknown-command”-时的提示"><a href="#“unknown-command”-时的提示" class="headerlink" title="“unknown command” 时的提示"></a>“unknown command” 时的提示</h3><p>当 <code>&quot;unknown command&quot;</code> 错误发生时，Cobra 会自动打印提示。这和 git 命令的行为一致。比如</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">$ hugo srever</span><br><span class="line">Error: unknown <span class="built_in">command</span> <span class="string">&quot;srever&quot;</span> <span class="keyword">for</span> <span class="string">&quot;hugo&quot;</span></span><br><span class="line"></span><br><span class="line">Did you mean this?</span><br><span class="line">        server</span><br><span class="line"></span><br><span class="line">Run <span class="string">&#x27;hugo --help&#x27;</span> <span class="keyword">for</span> usage.</span><br></pre></td></tr></table></figure><p>系统会根据注册的每个子命令自动生成建议，并使用<a href="https://zh.wikipedia.org/wiki/%E8%90%8A%E6%96%87%E6%96%AF%E5%9D%A6%E8%B7%9D%E9%9B%A2">萊文斯坦距離</a>的实现。每个匹配最小距离 2（忽略大小写）的注册命令都将显示为建议。</p><p>如果需要禁用建议或在命令中调整字符串距离，请使用：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cmd.DisableSuggestions = <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>或</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cmd.SuggestionsMinimumDistance = <span class="number">1</span></span><br></pre></td></tr></table></figure><p>您还可以使用 <code>SuggestFor</code> 属性显式为给定命令设置建议的名称。这样就可以针对不是距离很近的字符串提出建议，但是对于您的命令集和不希望使用别名的命令来说，它们都是有意义的。比如：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">$ kubectl remove</span><br><span class="line">Error: unknown <span class="built_in">command</span> <span class="string">&quot;remove&quot;</span> <span class="keyword">for</span> <span class="string">&quot;kubectl&quot;</span></span><br><span class="line"></span><br><span class="line">Did you mean this?</span><br><span class="line">        delete</span><br><span class="line"></span><br><span class="line">Run <span class="string">&#x27;kubectl help&#x27;</span> <span class="keyword">for</span> usage.</span><br></pre></td></tr></table></figure><h3 id="为你的命令生成文档"><a href="#为你的命令生成文档" class="headerlink" title="为你的命令生成文档"></a>为你的命令生成文档</h3><p>Cobra 可以基于子命令、标志等生成文档。可用格式如下：</p><ul><li><a href="https://blog.zisheng.pro/cobra/md_docs">Markdown</a></li><li><a href="https://blog.zisheng.pro/cobra/rest_docs">ReStructured Text</a></li><li><a href="https://blog.zisheng.pro/cobra/man_docs">Man Page</a></li></ul><h3 id="为你的命令生成-Bash-Completions"><a href="#为你的命令生成-Bash-Completions" class="headerlink" title="为你的命令生成 Bash Completions"></a>为你的命令生成 Bash Completions</h3><p>Cobra 可以生成 bash-completion 文件。如果你给你的命令添加了更多的信息，这些自动提示的分析会非常强大和灵活。更是信息请阅读<a href="https://blog.zisheng.pro/cobra/bash_completions">Bash Completions</a></p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/65fc6b431371/</id>
    <link href="https://blog.zisheng.pro/posts/65fc6b431371/"/>
    <published>2023-03-28T14:18:09.000Z</published>
    <summary>Cobra 是 Go 的 CLI 框架。它包含一个用于创建功能强大的现代 CLI 应用程序的库，以及一个用于快速生成基于 Cobra 的应用程序和命令文件的工具。</summary>
    <title>Cobra 中文文档</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="TypeScript" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/TypeScript/"/>
    <category term="TypeScript" scheme="https://blog.zisheng.pro/tags/TypeScript/"/>
    <category term="types" scheme="https://blog.zisheng.pro/tags/types/"/>
    <category term="DefinitelyTyped" scheme="https://blog.zisheng.pro/tags/DefinitelyTyped/"/>
    <category term="dts-gen" scheme="https://blog.zisheng.pro/tags/dts-gen/"/>
    <category term="dtslint" scheme="https://blog.zisheng.pro/tags/dtslint/"/>
    <category term="tuya-panel-kit" scheme="https://blog.zisheng.pro/tags/tuya-panel-kit/"/>
    <category term="微软" scheme="https://blog.zisheng.pro/tags/%E5%BE%AE%E8%BD%AF/"/>
    <content>
      <![CDATA[<p>在前端社区中，TypeScript 差不多是老生常谈的主题了。这不仅反映了 TypeScript 的流行度，也反映了它的学习上手成本。今天我们不来探讨 TypeScript 本身。而是记录一下我艰难地发布一个 <a href="https://www.npmjs.com/package/@types/tuya-panel-kit">@types</a> 包的历程。</p><h2 id="a-year-ago"><a href="#a-year-ago" class="headerlink" title="a year ago"></a>a year ago</h2><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b4aeb4e02f1640069b7e763ea0b5be40~tplv-k3u1fbpfcp-watermark.image" height="400px"/><p>上图是我在掘金的第一篇文章 <a href="https://juejin.cn/post/6844903843155689486">优雅地使用 TypeScript 开发 React Native 应用</a> 中的一条素质问答。问题就是有些库不是 TS 写的，也没提供类型声明该怎么办。从图中可见我当时的解决方法都是不可复用且不利他的。但这就是我这一年来处理该问题的常规手段。</p><p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/04f411da146740aab8f620337a592850~tplv-k3u1fbpfcp-watermark.image" alt="紫升"></p><h2 id="DefinitelyTyped"><a href="#DefinitelyTyped" class="headerlink" title="DefinitelyTyped"></a>DefinitelyTyped</h2><p>像是 Node 有 NPM，Java 有 Maven，TypeScript 也有它的另一半，那就是号称 GitHub review 数量之最的 <a href="https://github.com/DefinitelyTyped/DefinitelyTyped">DefinitelyTyped</a> 项目。</p><p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d503ee17f0ab45068a0d50a4d6f6034a~tplv-k3u1fbpfcp-watermark.image" alt="紫升"></p><p>在 TypeScript 大规模应用之前，社区已经有超过 90% 的顶级 JavaScript 库，或基于 Flow 编写的库（React系）。如果没有 DefinitelyTyped 项目，这些库想要提供类型支持，无疑只有完全重构代码。这既不现实也没必要。</p><blockquote><p>鉴于 DefinitelyTyped 的作用，我们说 DefinitelyTyped 让 TypeScript 再次伟大也不为过。</p></blockquote><p>DefinitelyTyped 目前是由微软官方维护的开源项目，参与的方式和 Homebrew 差不多，都是基于 GitHub 的工作流:</p><ol><li>fork <a href="https://github.com/DefinitelyTyped/DefinitelyTyped">DefinitelyTyped</a> 到自己的账号下</li><li>添加自己的包并编写类型声明</li><li>提交 PR</li><li>官方 review 并合并发布到 NPM</li></ol><h2 id="艰辛的贡献历程"><a href="#艰辛的贡献历程" class="headerlink" title="艰辛的贡献历程"></a>艰辛的贡献历程</h2><p>1、检查是否已存在同名的包：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm info @types/tuya-panel-kit</span><br></pre></td></tr></table></figure><p>2、安装 dts-gen：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install -g dts-gen</span><br></pre></td></tr></table></figure><p>3、生成新包</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dts-gen --dt --name tuya-panel-kit --template module</span><br></pre></td></tr></table></figure><p>4、格式化代码</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm run prettier -- --write types/tuya-panel-kit/**/*.ts</span><br></pre></td></tr></table></figure><p>这一步务必要执行，因为 DefinitelyTyped 十分严格，格式不对过不了 CI。过不了 CI，就只配和机器人对话：</p><p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/140adf442a17460aa845aef8e4b8ac18~tplv-k3u1fbpfcp-watermark.image" alt="紫升"></p><p>5、dtslint</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn lint tuya-panel-kit</span><br></pre></td></tr></table></figure><p>这一步是最让人头大的一步，Definitely 的规则可谓严苛，真就对的起它的 SLOGAN：</p><blockquote><p>The repository for high quality TypeScript type definitions</p></blockquote><p>我梳理了一下成功路上的绊脚石：</p><p>1、 <a href="https://github.com/DefinitelyTyped/DefinitelyTyped/issues/24788">Cannot find module ‘csstype’ when npm run lint package-name</a></p><p>这是一个流程 BUG，如果你的包依赖了 react，你需要执行 <code>cd types/react &amp;&amp; npm install</code> 和 <code>cd ~/.dts/typescript-installs/3.2/ &amp;&amp; npm install</code></p><p>2、如果你的包依赖了别的外部库，需要添加到 <a href="https://github.com/microsoft/DefinitelyTyped-tools/pull/165/files">microsoft&#x2F;DefinitelyTyped-tools</a> 项目中，否则 CI 不给过。</p><p>3、你的类型声明可能有很多不符合 dtslint 的标准，我看到有的包是在 <code>tslint.json</code> 中配置禁用掉部分规则，但是我做了尝试后被人工拒绝了。</p><p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fafa598fac2e4015ab496cc15fd94496~tplv-k3u1fbpfcp-watermark.image" alt="紫升"></p><p>然后我尝试在顶部加入 <code>// tslint:disable:max-line-length</code> 禁用规则，在说明原因后通过了 Review。就在发稿时，最新 PR 却因为一个禁用规则，被要求修改：</p><p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/21a0b43a6f464c47a1b94a5fc7ed619f~tplv-k3u1fbpfcp-watermark.image" alt="紫升"></p><h2 id="规范的重要性"><a href="#规范的重要性" class="headerlink" title="规范的重要性"></a>规范的重要性</h2><p>刚开始时，这种严苛漫长的 review 流程着实让我头大。但在提过 4 个 PR 都被合并后，我发现 review 的人关心的是你为什么要这么写，是不是有什么不得已的苦衷，是否符合高质量的要求。</p><p>在参与 DefinitelyTyped 的协作中，我越来越发现规范的重要。如此体量的项目，如果没有严格有效的规范约束，势必会被开发者抛弃。那我们来看看 DefinitelyTyped 中是如何约束的：</p><ol><li><a href="https://github.com/microsoft/dtslint">dtslint</a> ：微软专门写的用来检验类型声明文件的工具。正是因为它，我做了大量优化工作。</li><li>机器人🤖（dt-mergebot、dt-review-bot、typescript-bot-watchdog）：在你的代码通过所有规范之前，都是这些机器人在和你交互。大家感兴趣的话，之后我会单独出一篇解析的文章</li><li>尽职尽责的维护：虽然有时 review 速度明显很慢（可能因为国外疫情）。但是这些维护者真的是尽职尽责的 review 你的代码。机器再厉害也只是一个减少工作量的工具。我们应该致敬的还是这些为社区默默奉献的人。</li></ol>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/5d12a72b0712/</id>
    <link href="https://blog.zisheng.pro/posts/5d12a72b0712/"/>
    <published>2023-03-26T15:17:29.000Z</published>
    <summary>在前端社区中，TypeScript 差不多是老生常谈的主题了。这不仅反映了 TypeScript 的流行度，也反映了它的学习上手成本。今天我们不来探讨 TypeScript 本身。而是记录一下我艰难地发布一个 @types包的历程。</summary>
    <title>向微软官方贡献 @types 包后引发的思考</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="issues" scheme="https://blog.zisheng.pro/categories/issues/"/>
    <category term="JavaScript" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/JavaScript/"/>
    <category term="moment" scheme="https://blog.zisheng.pro/categories/issues/moment/"/>
    <category term="moment" scheme="https://blog.zisheng.pro/tags/moment/"/>
    <category term="moment-timezone" scheme="https://blog.zisheng.pro/tags/moment-timezone/"/>
    <category term="TypeError" scheme="https://blog.zisheng.pro/tags/TypeError/"/>
    <category term="moment().tz" scheme="https://blog.zisheng.pro/tags/moment-tz/"/>
    <content>
      <![CDATA[<h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><p>迁移项目时下面的代码编译报错 <code>TypeError: moment().tz is not a function</code>：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> myDate = <span class="title function_">moment</span>().<span class="title function_">tz</span>(<span class="literal">undefined</span>, vm.<span class="property">timeZone</span>).<span class="title function_">format</span>(<span class="string">&#x27;YYYY-MM-DD&#x27;</span>);</span><br></pre></td></tr></table></figure><h2 id="解决办法-1"><a href="#解决办法-1" class="headerlink" title="解决办法 1"></a>解决办法 1</h2><p>使用 <code>moment-timezone</code> 代替：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> moment <span class="keyword">from</span> <span class="string">&#x27;moment-timezone&#x27;</span>;</span><br></pre></td></tr></table></figure><h2 id="解决办法-2"><a href="#解决办法-2" class="headerlink" title="解决办法 2"></a>解决办法 2</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> moment <span class="keyword">from</span> <span class="string">&#x27;moment&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">&#x27;moment-timezone&#x27;</span>;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/3ee8e89b57ce/</id>
    <link href="https://blog.zisheng.pro/posts/3ee8e89b57ce/"/>
    <published>2023-03-21T04:41:19.000Z</published>
    <summary>本文解决了迁移项目时下面的代码编译报错 TypeError moment().tz is not a function</summary>
    <title>「已解决」TypeError: moment().tz is not a function</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="副业" scheme="https://blog.zisheng.pro/categories/%E5%89%AF%E4%B8%9A/"/>
    <category term="网赚" scheme="https://blog.zisheng.pro/categories/%E5%89%AF%E4%B8%9A/%E7%BD%91%E8%B5%9A/"/>
    <category term="悬赏软件" scheme="https://blog.zisheng.pro/tags/%E6%82%AC%E8%B5%8F%E8%BD%AF%E4%BB%B6/"/>
    <category term="提现" scheme="https://blog.zisheng.pro/tags/%E6%8F%90%E7%8E%B0/"/>
    <category term="手续费" scheme="https://blog.zisheng.pro/tags/%E6%89%8B%E7%BB%AD%E8%B4%B9/"/>
    <category term="优赏吧" scheme="https://blog.zisheng.pro/tags/%E4%BC%98%E8%B5%8F%E5%90%A7/"/>
    <category term="赏帮" scheme="https://blog.zisheng.pro/tags/%E8%B5%8F%E5%B8%AE/"/>
    <content>
      <![CDATA[<p>悬赏软件是一种通过发布任务、悬赏金等方式吸引用户完成任务并获得奖励的应用程序。在这些平台上，用户可以发布各种各样的任务，如填写问卷、测试应用、写文章、翻译文本、拍照等等，其他用户可以接受这些任务并完成它们，获取相应的奖励。</p><h1 id="悬赏软件能赚到钱吗？"><a href="#悬赏软件能赚到钱吗？" class="headerlink" title="悬赏软件能赚到钱吗？"></a>悬赏软件能赚到钱吗？</h1><p>使用悬赏软件可以赚到一定的零花钱或者是收入，但具体赚多少取决于任务的类型和数量，以及自己的能力和时间投入。一些悬赏软件提供的任务奖励较低，而另一些则可能提供更高的奖励。除此之外，一些悬赏软件可能还会要求达到一定的任务数量或者质量才能提现，因此需要具体了解每个平台的规则和要求。</p><h1 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h1><p>在使用悬赏软件时，需要注意以下几点：</p><ol><li>不要盲目接受任务，要根据自己的能力和时间合理选择任务。</li><li>完成任务时要认真仔细，保证质量，否则可能会导致任务被拒绝或者扣除奖励。</li><li>注意个人信息和隐私保护，不要泄露个人敏感信息。</li><li>不要轻信一些涉及到付款或者转账的任务，避免上当受骗。</li></ol><p>总之，悬赏软件可以作为一种赚取零花钱或者收入的方式，但需要谨慎使用，选择正规、可信的平台，并根据自己的能力和时间合理选择任务。</p><h1 id="常见的悬赏软件"><a href="#常见的悬赏软件" class="headerlink" title="常见的悬赏软件"></a>常见的悬赏软件</h1><ul><li>百度天天领现金：签到领积分、吃饭补贴、睡觉补贴、0 元领水果、邀好友赚钱、做任务赚金币、看广告赚钱</li><li>悟空浏览器：最大特色是悟空自动赚积分，提现靠谱。</li><li>美团众包：提供各种任务包，如拍照、调研、晒单等，奖励方式多样。</li><li>微信读书：通过阅读完成任务，获得读书币，可以用于提现或换取各种奖励。</li><li>拼多多：提供签到、邀请好友、浏览商品等任务，奖励丰富。</li><li>任务猫：提供各种任务，如浏览网页、下载应用、完成问卷等，奖励较高。</li><li>小米有品：提供签到、分享、晒单等任务，奖励小米积分，可以兑换商品。</li><li>闲鱼：提供发布闲置物品、邀请好友等任务，奖励为闲鱼金币，可以用于购物。</li><li>任务行：提供各种任务，如拍照、录音、调查等，奖励方式多样。</li></ul><p>这些悬赏软件都有自己的特点和规则，用户在使用时要仔细阅读相关条款和规定，确保自己的权益得到保障。</p><h1 id="免手续费悬赏软件"><a href="#免手续费悬赏软件" class="headerlink" title="免手续费悬赏软件"></a>免手续费悬赏软件</h1><p>有网友表示，自己辛辛苦苦做任务赚钱，但每次提现都要扣手续费，让人很不舒服。问我有没有不需要提现手续费的任务应用程序。分享 2 款免手续费的赏金软件提现，纯干货。</p><h2 id="优赏吧"><a href="#优赏吧" class="headerlink" title="优赏吧"></a>优赏吧</h2><p>优赏吧也已经运营了几年。最低取款 1 元即付，不扣除取款手续费。即使它不是一个整数，所有的提款都可以一次完成。它是整个网络之中唯一的一个。。</p><p>它还具有分红树的功能。做任务的红包水果类似于种树的水果。时候到了，钱收了，工作也就完成了。。</p><p>不需要身份验证，界面干净，使用起来很舒服。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1679284811377.png" alt="紫升"></p><h2 id="赏帮"><a href="#赏帮" class="headerlink" title="赏帮"></a>赏帮</h2><p>赏帮的受欢迎程度是有目共睹的，也是一款提现不收手续费的打赏软件。最低1元开始，但需要是整数倍。</p><p>平台的操作更用心，更受欢迎。新手注册会给一个小红包，并且在排行榜之上有额外的奖励。它是非常稳定的，可以用于长期试验赚一点钱。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1679285041134.png" alt="紫升"></p><p>目前，这两款软件提现都不收取手续费。当然，趣闲大哥也可以做，但需要用积分兑换，这是多了一个步骤，很多人都不知道这个功能。对新手来说是很不友好的，所以我觉得应该排除。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/4dabea032500/</id>
    <link href="https://blog.zisheng.pro/posts/4dabea032500/"/>
    <published>2023-03-20T03:51:45.000Z</published>
    <summary>悬赏软件是一种通过发布任务、悬赏金等方式吸引用户完成任务并获得奖励的应用程序。在这些平台上，用户可以发布各种各样的任务，如填写问卷、测试应用、写文章、翻译文本、拍照等等，其他用户可以接受这些任务并完成它们，获取相应的奖励。</summary>
    <title>2023 能赚钱的悬赏软件推荐</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/categories/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/categories/Golang/"/>
    <category term="Go 语言系列教程" scheme="https://blog.zisheng.pro/categories/Golang/Go-%E8%AF%AD%E8%A8%80%E7%B3%BB%E5%88%97%E6%95%99%E7%A8%8B/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/tags/Golang/"/>
    <category term="Go 语言系列教程" scheme="https://blog.zisheng.pro/tags/Go-%E8%AF%AD%E8%A8%80%E7%B3%BB%E5%88%97%E6%95%99%E7%A8%8B/"/>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/tags/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="Go 字符串" scheme="https://blog.zisheng.pro/tags/Go-%E5%AD%97%E7%AC%A6%E4%B8%B2/"/>
    <category term="Go String" scheme="https://blog.zisheng.pro/tags/Go-String/"/>
    <content>
      <![CDATA[<p>在 Go 中，String 值得特别一提，因为与其他语言相比，它们在实现上有所不同。</p><h2 id="String-是什么？"><a href="#String-是什么？" class="headerlink" title="String 是什么？"></a>String 是什么？</h2><p><strong>在 Go 中，一个字符串是字节的一个切片。字符串可以通过将一组字符放在双引号内来创建</strong></p><p>让我们看看一个简单的例子，创建一个 <code>string</code> 并打印出来。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    name := <span class="string">&quot;Hello World&quot;</span></span><br><span class="line">    fmt.Println(name)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/o9OVDgEMU0">Run in playground</a></p><p>上述程序将打印 <code>Hello World</code>。</p><p>Go 中的字符串是 <a href="https://naveenr.net/unicode-character-set-and-utf-8-utf-16-utf-32-encoding/">符合 Unicode 标准</a> 并且是 <a href="https://naveenr.net/unicode-character-set-and-utf-8-utf-16-utf-32-encoding/">UTF-8 编码</a> 的。</p><h2 id="访问一个字符串的单个字节"><a href="#访问一个字符串的单个字节" class="headerlink" title="访问一个字符串的单个字节"></a>访问一个字符串的单个字节</h2><p>由于字符串是字节的一个切片，所以可以访问字符串的每个字节。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">printBytes</span><span class="params">(s <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">    fmt.Printf(<span class="string">&quot;Bytes: &quot;</span>)</span><br><span class="line">    <span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="built_in">len</span>(s); i++ &#123;</span><br><span class="line">      fmt.Printf(<span class="string">&quot;%x &quot;</span>, s[i])</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">  name := <span class="string">&quot;Hello World&quot;</span></span><br><span class="line">    fmt.Printf(<span class="string">&quot;String: %s\n&quot;</span>, name) <span class="comment">// 输入的字符串被打印出来</span></span><br><span class="line">    printBytes(name)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/B3KgBBQhiN9">Run in playground</a></p><p><code>%s</code> 是用于打印字符串的格式化标识符。<code>len(s)</code> 返回字符串中的字节数，我们使用 <code>for</code> 循环以十六进制符号打印这些字节。<code>%x</code> 是十六进制的格式指定符。上述程序的输出结果是：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">String: Hello World</span><br><span class="line">Bytes: 48 65 6c 6c 6f 20 57 6f 72 6c 64</span><br></pre></td></tr></table></figure><p>这是 <code>Hello World</code> 的 <a href="https://mothereff.in/utf-8#Hello%20World">Unicode UT8 编码</a> 值. 为了更好地理解字符串，需要对 Unicode 和 UTF-8 有一个基本的了解。 我推荐阅读 <a href="https://naveenr.net/unicode-character-set-and-utf-8-utf-16-utf-32-encoding/">https://naveenr.net/unicode-character-set-and-utf-8-utf-16-utf-32-encoding/</a> 了解更多 Unicode 和 UTF-8 的知识。</p><h2 id="访问字符串的单个字符"><a href="#访问字符串的单个字符" class="headerlink" title="访问字符串的单个字符"></a>访问字符串的单个字符</h2><p>让我们对上述程序稍作修改，以打印字符串的字符。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">printBytes</span><span class="params">(s <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">    fmt.Printf(<span class="string">&quot;Bytes: &quot;</span>)</span><br><span class="line">    <span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="built_in">len</span>(s); i++ &#123;</span><br><span class="line">        fmt.Printf(<span class="string">&quot;%x &quot;</span>, s[i])</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">printChars</span><span class="params">(s <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">    fmt.Printf(<span class="string">&quot;Characters: &quot;</span>)</span><br><span class="line">    <span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="built_in">len</span>(s); i++ &#123;</span><br><span class="line">        fmt.Printf(<span class="string">&quot;%c &quot;</span>, s[i])</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    name := <span class="string">&quot;Hello World&quot;</span></span><br><span class="line">    fmt.Printf(<span class="string">&quot;String: %s\n&quot;</span>, name)</span><br><span class="line">    printChars(name)</span><br><span class="line">    fmt.Printf(<span class="string">&quot;\n&quot;</span>)</span><br><span class="line">    printBytes(name)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/ZkXmyVNsqv7">Run in playground</a></p><p><code>%c</code> 格式化标识符用于打印 <code>printChars</code> 方法中字符串参数中的字符。该程序打印的是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">String: Hello World</span><br><span class="line">Characters: H e l l o   W o r l d</span><br><span class="line">Bytes: 48 65 6c 6c 6f 20 57 6f 72 6c 64</span><br></pre></td></tr></table></figure><p>虽然上面的程序看起来是访问字符串的单个字符的合法方式，但这有一个严重的错误。让我们来看看这个错误是什么。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">printBytes</span><span class="params">(s <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">    fmt.Printf(<span class="string">&quot;Bytes: &quot;</span>)</span><br><span class="line">    <span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="built_in">len</span>(s); i++ &#123;</span><br><span class="line">        fmt.Printf(<span class="string">&quot;%x &quot;</span>, s[i])</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">printChars</span><span class="params">(s <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">    fmt.Printf(<span class="string">&quot;Characters: &quot;</span>)</span><br><span class="line">    <span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="built_in">len</span>(s); i++ &#123;</span><br><span class="line">        fmt.Printf(<span class="string">&quot;%c &quot;</span>, s[i])</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    name := <span class="string">&quot;Hello World&quot;</span></span><br><span class="line">    fmt.Printf(<span class="string">&quot;String: %s\n&quot;</span>, name)</span><br><span class="line">    printChars(name)</span><br><span class="line">    fmt.Printf(<span class="string">&quot;\n&quot;</span>)</span><br><span class="line">    printBytes(name)</span><br><span class="line">    fmt.Printf(<span class="string">&quot;\n\n&quot;</span>)</span><br><span class="line">    name = <span class="string">&quot;Señor&quot;</span></span><br><span class="line">    fmt.Printf(<span class="string">&quot;String: %s\n&quot;</span>, name)</span><br><span class="line">    printChars(name) <span class="comment">//</span></span><br><span class="line">    fmt.Printf(<span class="string">&quot;\n&quot;</span>)</span><br><span class="line">    printBytes(name)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/2hyVf8l9fiO">Run in playground</a></p><p>上述程序的输出是</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">String: Hello World</span><br><span class="line">Characters: H e l l o   W o r l d</span><br><span class="line">Bytes: 48 65 6c 6c 6f 20 57 6f 72 6c 64</span><br><span class="line"></span><br><span class="line">String: Señor</span><br><span class="line">Characters: S e Ã ± o r</span><br><span class="line">Bytes: 53 65 c3 b1 6f 72</span><br></pre></td></tr></table></figure><p>我们试图打印 <strong>Señor</strong> 的字符，但它输出 <strong>S e Ã ± o r</strong>，这是错误的。为什么这个程序对 <code>Señor</code> 会出错，而对 <code>Hello World </code> 却能完全正常工作。原因是 <code>ñ</code> 的 Unicode 码位是 <code>U+00F1</code>，其 <a href="https://mothereff.in/utf-8#%C3%B1">UTF-8编码</a> 占用了 2 个字节 <code>c3</code> 和 <code>b1</code>。我们试图打印字符，假设每个代码点是一个字节，这是错误的。**在 UTF-8 编码中，一个代码点可以占用 1个以上的字节。**那么我们如何解决这个问题？这就需要 <strong>rune</strong> 拯救我们的地方了。</p><h2 id="Rune"><a href="#Rune" class="headerlink" title="Rune"></a>Rune</h2><p>Rune 是 Go 中的一个内置类型，它是 <code>int32</code> 的别名。Rune 在 Go 中代表一个 Unicode 代码点。不管这个代码点占用多少字节，它都可以用 Rune 来表示。让我们修改上面的程序，用 Rune 来打印字符。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">printBytes</span><span class="params">(s <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">    fmt.Printf(<span class="string">&quot;Bytes: &quot;</span>)</span><br><span class="line">    <span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="built_in">len</span>(s); i++ &#123;</span><br><span class="line">        fmt.Printf(<span class="string">&quot;%x &quot;</span>, s[i])</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">printChars</span><span class="params">(s <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">    fmt.Printf(<span class="string">&quot;Characters: &quot;</span>)</span><br><span class="line">    runes := []<span class="type">rune</span>(s) <span class="comment">// 字符串被转换为 runes 的切片</span></span><br><span class="line">    <span class="comment">// 然后我们对其进行循环，并显示这些字符。</span></span><br><span class="line">    <span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="built_in">len</span>(runes); i++ &#123;</span><br><span class="line">        fmt.Printf(<span class="string">&quot;%c &quot;</span>, runes[i])</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    name := <span class="string">&quot;Hello World&quot;</span></span><br><span class="line">    fmt.Printf(<span class="string">&quot;String: %s\n&quot;</span>, name)</span><br><span class="line">    printChars(name)</span><br><span class="line">    fmt.Printf(<span class="string">&quot;\n&quot;</span>)</span><br><span class="line">    printBytes(name)</span><br><span class="line">    fmt.Printf(<span class="string">&quot;\n\n&quot;</span>)</span><br><span class="line">    name = <span class="string">&quot;Señor&quot;</span></span><br><span class="line">    fmt.Printf(<span class="string">&quot;String: %s\n&quot;</span>, name)</span><br><span class="line">    printChars(name)</span><br><span class="line">    fmt.Printf(<span class="string">&quot;\n&quot;</span>)</span><br><span class="line">    printBytes(name)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/n8rsfagm2SJ">Run in playground</a></p><p>上述程序打印出：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">String: Hello World</span><br><span class="line">Characters: H e l l o   W o r l d</span><br><span class="line">Bytes: 48 65 6c 6c 6f 20 57 6f 72 6c 64</span><br><span class="line"></span><br><span class="line">String: Señor</span><br><span class="line">Characters: S e ñ o r</span><br><span class="line">Bytes: 53 65 c3 b1 6f 72</span><br></pre></td></tr></table></figure><p>上述输出是完美的。只是我们想要的😀。</p><h2 id="使用-for-range-循环访问单个-Rune"><a href="#使用-for-range-循环访问单个-Rune" class="headerlink" title="使用 for range 循环访问单个 Rune"></a>使用 <code>for range</code> 循环访问单个 Rune</h2><p>上面的程序是一个完美的方式来迭代一个字符串的各个 Rune。但是 Go 为我们提供了一种更简单的方法，即使用 <code>for range</code> 循环来实现这一目的。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">charsAndBytePosition</span><span class="params">(s <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">    <span class="comment">// 使用 for range 循环迭代 string</span></span><br><span class="line">    <span class="keyword">for</span> index, <span class="type">rune</span> := <span class="keyword">range</span> s &#123;</span><br><span class="line">        fmt.Printf(<span class="string">&quot;%c starts at byte %d\n&quot;</span>, <span class="type">rune</span>, index)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    name := <span class="string">&quot;Señor&quot;</span></span><br><span class="line">    charsAndBytePosition(name)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/0ldNBeffjYI">Run in playground</a></p><p>循环返回 Rune 开始的字节的位置，同时返回 Rune 的位置。这个程序输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">S starts at byte 0</span><br><span class="line">e starts at byte 1</span><br><span class="line">ñ starts at byte 2</span><br><span class="line">o starts at byte 4</span><br><span class="line">r starts at byte 5</span><br></pre></td></tr></table></figure><p>从上面的输出可以看出，<code>ñ</code> 占用了 2 个字节，因为下一个字符 <code>o</code> 是从第 4 字节开始的，而不是第 3 字节😀。</p><h2 id="从一个字节片中创建一个字符串"><a href="#从一个字节片中创建一个字符串" class="headerlink" title="从一个字节片中创建一个字符串"></a>从一个字节片中创建一个字符串</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    byteSlice := []<span class="type">byte</span>&#123;<span class="number">0x43</span>, <span class="number">0x61</span>, <span class="number">0x66</span>, <span class="number">0xC3</span>, <span class="number">0xA9</span>&#125;</span><br><span class="line">    str := <span class="type">string</span>(byteSlice)</span><br><span class="line">    fmt.Println(str)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/Vr9pf8X8xO">Run in playground</a></p><p><code>byteSlice</code> 包含字符串 <code>Café</code>的 <a href="https://mothereff.in/utf-8#Caf%C3%A9">UTF-8编码</a> 十六进制字节。该程序打印出</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Café</span><br></pre></td></tr></table></figure><p>如果我们有相当于十六进制的十进制值，怎么办？上面的程序能工作吗？让我们来看看。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    byteSlice := []<span class="type">byte</span>&#123;<span class="number">67</span>, <span class="number">97</span>, <span class="number">102</span>, <span class="number">195</span>, <span class="number">169</span>&#125; <span class="comment">// 十进制相当于 &#123;&#x27;\x43&#x27;, &#x27;\x61&#x27;, &#x27;\x66&#x27;, &#x27;\xC3&#x27;, &#x27;\xA9&#x27;&#125;</span></span><br><span class="line">    str := <span class="type">string</span>(byteSlice)</span><br><span class="line">    fmt.Println(str)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/jgsRowW6XN">Run in playground</a></p><p>小数点值也可以，上述程序也会打印出 <code>Café</code>。</p><blockquote><p>原文地址 <a href="https://golangbot.com/strings/">Golang tutorial series Strings</a><br>原文作者：<a href="https://golangbot.com/about/">Naveen Ramanathan</a><br>译文出自：<a href="https://blog.zisheng.pro/categories/%E6%B4%9B%E7%AB%B9%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/">紫升翻译计划</a></p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/e17097ce4465/</id>
    <link href="https://blog.zisheng.pro/posts/e17097ce4465/"/>
    <published>2023-03-19T07:14:00.000Z</published>
    <summary>在 Go 中，一个字符串是字节的一个切片。字符串可以通过将一组字符放在双引号内来创建</summary>
    <title>Go 语言系列教程の字符串</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/categories/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/categories/Golang/"/>
    <category term="Go 语言系列教程" scheme="https://blog.zisheng.pro/categories/Golang/Go-%E8%AF%AD%E8%A8%80%E7%B3%BB%E5%88%97%E6%95%99%E7%A8%8B/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/tags/Golang/"/>
    <category term="Go 语言系列教程" scheme="https://blog.zisheng.pro/tags/Go-%E8%AF%AD%E8%A8%80%E7%B3%BB%E5%88%97%E6%95%99%E7%A8%8B/"/>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/tags/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="Go Map" scheme="https://blog.zisheng.pro/tags/Go-Map/"/>
    <content>
      <![CDATA[<h2 id="map-是什么？"><a href="#map-是什么？" class="headerlink" title="map 是什么？"></a>map 是什么？</h2><p>map 是 Go 中的一个内置类型，用于存储键值对。让我们以一个有几个员工的创业公司为例。为了简单起见，我们假设所有这些员工的名字都是唯一的。我们正在寻找一个数据结构来存储每个员工的工资。对于这个用例来说，一个 map 将是一个完美的选择。员工的名字可以是键，工资可以是值。map 类似于其他语言中的字典，如 Python。</p><h2 id="如何创建-map"><a href="#如何创建-map" class="headerlink" title="如何创建 map"></a>如何创建 map</h2><p>通过向 <code>make</code> 函数传递键和值的类型，可以创建一个 map。下面是创建一个新 map 的语法。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">make</span>(<span class="keyword">map</span>[<span class="keyword">type</span> of key]<span class="keyword">type</span> of value)</span><br></pre></td></tr></table></figure><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">employeeSalary := <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>)</span><br></pre></td></tr></table></figure><p>上面这行代码创建了一个名为 <code>employeeSalary</code> 的地图，它有 <code>string</code> 键和 <code>int</code> 值。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    employeeSalary := <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>)</span><br><span class="line">    fmt.Println(employeeSalary)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/EoaQ_Xwz66Z">Run in Playground</a></p><p>上面的程序创建了一个名为 <code>employeeSalary</code> 的 map，有 <code>string</code> 键和 <code>int</code> 值。上面的程序将打印。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">map</span>[]</span><br></pre></td></tr></table></figure><p>由于我们没有向 map 添加任何元素，所以它是空的。</p><h2 id="向-map-添加元素"><a href="#向-map-添加元素" class="headerlink" title="向 map 添加元素"></a>向 map 添加元素</h2><p>向 map 添加新元素的语法与数组的语法相同。下面的程序在 <code>employeeSalary</code> map 中增加了一些新雇员。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    employeeSalary := <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>)</span><br><span class="line">    employeeSalary[<span class="string">&quot;steve&quot;</span>] = <span class="number">12000</span></span><br><span class="line">    employeeSalary[<span class="string">&quot;jamie&quot;</span>] = <span class="number">15000</span></span><br><span class="line">    employeeSalary[<span class="string">&quot;mike&quot;</span>] = <span class="number">9000</span></span><br><span class="line">    fmt.Println(<span class="string">&quot;employeeSalary map contents:&quot;</span>, employeeSalary)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/-IUSnvdgF2I">Run in playground</a></p><p>我们增加了三个雇员 <code>steve</code>、<code>jamie</code> 和 <code>mike</code> 以及他们相应的工资。</p><p>上述程序打印出来。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">employeeSalary map contents: map[steve:12000 jamie:15000 mike:9000]</span><br></pre></td></tr></table></figure><p>也可以在声明本身中初始化一个 map。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    employeeSalary := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span> &#123;</span><br><span class="line">        <span class="string">&quot;steve&quot;</span>: <span class="number">12000</span>,</span><br><span class="line">        <span class="string">&quot;jamie&quot;</span>: <span class="number">15000</span>,</span><br><span class="line">    &#125;</span><br><span class="line">    employeeSalary[<span class="string">&quot;mike&quot;</span>] = <span class="number">9000</span></span><br><span class="line">    fmt.Println(<span class="string">&quot;employeeSalary map contents:&quot;</span>, employeeSalary)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/oR_j4jkJflf">Run in playground</a></p><p>上面的程序声明了 <code>employeeSalary</code>，并在声明本身中添加了两个元素。后来又增加了一个键为 <code>mike</code> 的元素。该程序打印出</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">employeeSalary map contents: map[jamie:15000 mike:9000 steve:12000]</span><br></pre></td></tr></table></figure><p>不一定只有字符串类型才是键。所有可比较的类型，如布尔、整数、浮点、复数、字符串也可以是键。甚至用户定义的类型，如结构体也可以是键。如果你想了解更多关于可比较类型的信息，请访问 <a href="http://golang.org/ref/spec#Comparison_operators%E3%80%82">http://golang.org/ref/spec#Comparison_operators。</a></p><h2 id="map-的零值"><a href="#map-的零值" class="headerlink" title="map 的零值"></a>map 的零值</h2><p>map 的零值是 <code>nil</code>。如果你试图向一个 <code>nil</code> map 添加元素，将会发生运行时 <a href="https://golangbot.com/panic-and-recover/">panic</a>。因此，在添加元素之前，map 必须被初始化。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">var</span> employeeSalary <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span></span><br><span class="line">    employeeSalary[<span class="string">&quot;steve&quot;</span>] = <span class="number">12000</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/DH8gJVjn6M2">Run in playground</a></p><p>在上面的程序中，<code>employeeSalary</code> 是 <code>nil</code>，我们试图在 map 中添加一个新的键。该程序将出现错误</p><p><code>panic: assignment to entry in nil map</code></p><h2 id="从-map-中检索一个键的值"><a href="#从-map-中检索一个键的值" class="headerlink" title="从 map 中检索一个键的值"></a>从 map 中检索一个键的值</h2><p>现在我们已经向 map 添加了一些元素，让我们来学习如何检索它们。<code>map[key]</code> 是检索 map 元素的语法。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    employeeSalary := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>&#123;</span><br><span class="line">        <span class="string">&quot;steve&quot;</span>: <span class="number">12000</span>,</span><br><span class="line">        <span class="string">&quot;jamie&quot;</span>: <span class="number">15000</span>,</span><br><span class="line">        <span class="string">&quot;mike&quot;</span>: <span class="number">9000</span>,</span><br><span class="line">    &#125;</span><br><span class="line">    employee := <span class="string">&quot;jamie&quot;</span></span><br><span class="line">    salary := employeeSalary[employee]</span><br><span class="line">    fmt.Println(<span class="string">&quot;Salary of&quot;</span>, employee, <span class="string">&quot;is&quot;</span>, salary)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/qthGPQ6pj0Z">Run in playground</a></p><p>上面的程序是非常直接的。雇员 <code>jamie</code> 的工资被检索并打印出来。该程序打印了</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Salary of jamie is 15000</span><br></pre></td></tr></table></figure><p>如果一个元素不存在，会发生什么？map 将返回该元素的类型的零值。在 <code>employeeSalary</code> 地图的例子中，如果我们试图访问一个不存在的元素，将返回 <code>int</code> 的零值，即 <code>0</code>。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    employeeSalary := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>&#123;</span><br><span class="line">        <span class="string">&quot;steve&quot;</span>: <span class="number">12000</span>,</span><br><span class="line">        <span class="string">&quot;jamie&quot;</span>: <span class="number">15000</span>,</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Println(<span class="string">&quot;Salary of joe is&quot;</span>, employeeSalary[<span class="string">&quot;joe&quot;</span>])</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/iVal_ll7iN7">Run in playground</a></p><p>上述程序的输出是</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Salary of joe is 0</span><br></pre></td></tr></table></figure><p>上面的程序返回 joe 的工资为 <code>0</code>。当我们试图检索 map 中不存在的键的值时，将不会出现运行时错误。</p><h2 id="Checking-if-a-key-exists"><a href="#Checking-if-a-key-exists" class="headerlink" title="Checking if a key exists"></a>Checking if a key exists</h2><p>在上一节中我们了解到，当一个键不存在时，将返回该类型的零值。当我们想知道键是否真的存在于 map 中时，这并没有帮助。</p><p>例如，我们想知道一个键是否存在于 <code>employeeSalary</code> map 中。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">value, ok := <span class="keyword">map</span>[key]</span><br></pre></td></tr></table></figure><p>以上是查找一个特定的键是否存在于 map 中的语法。如果 <code>ok</code> 为真，那么该键就存在，其值也存在于变量 <code>value</code> 中，否则该键就不存在。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    employeeSalary := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>&#123;</span><br><span class="line">        <span class="string">&quot;steve&quot;</span>: <span class="number">12000</span>,</span><br><span class="line">        <span class="string">&quot;jamie&quot;</span>: <span class="number">15000</span>,</span><br><span class="line">    &#125;</span><br><span class="line">    newEmp := <span class="string">&quot;joe&quot;</span></span><br><span class="line">    value, ok := employeeSalary[newEmp]</span><br><span class="line">    <span class="keyword">if</span> ok == <span class="literal">true</span> &#123;</span><br><span class="line">        fmt.Println(<span class="string">&quot;Salary of&quot;</span>, newEmp, <span class="string">&quot;is&quot;</span>, value)</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Println(newEmp, <span class="string">&quot;not found&quot;</span>)</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/Y4n1p4yfdVi">Run in playground</a></p><p>在上述程序中，<code>ok</code> 将是假的，因为 <code>joe</code> 不存在。因此，该程序将打印。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">joe not found</span><br></pre></td></tr></table></figure><h2 id="遍历一个-map-中的所有元素"><a href="#遍历一个-map-中的所有元素" class="headerlink" title="遍历一个 map 中的所有元素"></a>遍历一个 map 中的所有元素</h2><p><code>for</code> 循环的 <code>range</code> 形式用于遍历一个 map 的所有元素。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    employeeSalary := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>&#123;</span><br><span class="line">        <span class="string">&quot;steve&quot;</span>: <span class="number">12000</span>,</span><br><span class="line">        <span class="string">&quot;jamie&quot;</span>: <span class="number">15000</span>,</span><br><span class="line">        <span class="string">&quot;mike&quot;</span>:  <span class="number">9000</span>,</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Println(<span class="string">&quot;Contents of the map&quot;</span>)</span><br><span class="line">    <span class="keyword">for</span> key, value := <span class="keyword">range</span> employeeSalary &#123;</span><br><span class="line">        fmt.Printf(<span class="string">&quot;employeeSalary[%s] = %dn&quot;</span>, key, value)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/rz8U_g2slb0">Run in playground</a></p><p>上述程序的输出。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Contents of the map</span><br><span class="line">employeeSalary[mike] = 9000</span><br><span class="line">employeeSalary[steve] = 12000</span><br><span class="line">employeeSalary[jamie] = 15000</span><br></pre></td></tr></table></figure><blockquote><p>一个重要的事实是，当使用 <code>for range</code> 时，从 map 中检索数值的顺序不保证在程序的每次执行中都是一样的。它也和元素被添加到 map 中的顺序不一样</p></blockquote><h2 id="从-map-中删除元素"><a href="#从-map-中删除元素" class="headerlink" title="从 map 中删除元素"></a>从 map 中删除元素</h2><p><a href="https://golang.org/pkg/builtin/#delete">delete(map, key)</a> 是从 <code>map</code> 中删除 <code>key</code> 的语法。删除函数不返回任何值。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    employeeSalary := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>&#123;</span><br><span class="line">        <span class="string">&quot;steve&quot;</span>: <span class="number">12000</span>,</span><br><span class="line">        <span class="string">&quot;jamie&quot;</span>: <span class="number">15000</span>,</span><br><span class="line">        <span class="string">&quot;mike&quot;</span>: <span class="number">9000</span>,</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Println(<span class="string">&quot;map before deletion&quot;</span>, employeeSalary)</span><br><span class="line">    <span class="built_in">delete</span>(employeeSalary, <span class="string">&quot;steve&quot;</span>)</span><br><span class="line">    fmt.Println(<span class="string">&quot;map after deletion&quot;</span>, employeeSalary)</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/u0WCB-Ta_dB">Run in playground</a></p><p>上面的程序删除了键 <code>steve</code>，并且打印了</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">map before deletion map[steve:12000 jamie:15000 mike:9000]</span><br><span class="line">map after deletion map[mike:9000 jamie:15000]</span><br></pre></td></tr></table></figure><p>如果我们试图删除一个不存在于 map 中的键，将不会出现运行时错误。</p><h2 id="结构体-map"><a href="#结构体-map" class="headerlink" title="结构体 map"></a>结构体 map</h2><p>到目前为止，我们只在地图中存储了雇员的工资。如果我们也能在地图中存储每个雇员的国家，那不是很好吗？这可以通过使用一个结构体 map 来实现。雇员可以被表示为一个包含工资和国家字段的结构，它们将以字符串键和结构值存储在 map 中。让我们写一个程序来了解如何做到这一点。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> employee <span class="keyword">struct</span> &#123;</span><br><span class="line">    salary  <span class="type">int</span></span><br><span class="line">    country <span class="type">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    emp1 := employee&#123;</span><br><span class="line">        salary:  <span class="number">12000</span>,</span><br><span class="line">        country: <span class="string">&quot;USA&quot;</span>,</span><br><span class="line">    &#125;</span><br><span class="line">    emp2 := employee&#123;</span><br><span class="line">        salary:  <span class="number">14000</span>,</span><br><span class="line">        country: <span class="string">&quot;Canada&quot;</span>,</span><br><span class="line">    &#125;</span><br><span class="line">    emp3 := employee&#123;</span><br><span class="line">        salary:  <span class="number">13000</span>,</span><br><span class="line">        country: <span class="string">&quot;India&quot;</span>,</span><br><span class="line">    &#125;</span><br><span class="line">    employeeInfo := <span class="keyword">map</span>[<span class="type">string</span>]employee&#123;</span><br><span class="line">        <span class="string">&quot;Steve&quot;</span>: emp1,</span><br><span class="line">        <span class="string">&quot;Jamie&quot;</span>: emp2,</span><br><span class="line">        <span class="string">&quot;Mike&quot;</span>:  emp3,</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> name, info := <span class="keyword">range</span> employeeInfo &#123;</span><br><span class="line">        fmt.Printf(<span class="string">&quot;Employee: %s Salary:$%d  Country: %s\n&quot;</span>, name, info.salary, info.country)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/wbGhkyZld1a">Run in playground</a></p><p>在上述程序中，<code>employee</code> 结构包含 <code>salary</code> 和 <code>country</code> 字段。我们创建了三个雇员<code>emp1</code>, <code>emp2</code> 和 <code>emp3</code>。</p><p>我们用我们创建的三个雇员初始化一个键类型为 <code>string</code>、值类型为 <code>employee</code> 的地图。</p><p>这个程序将打印。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Employee: Mike Salary:$13000  Country: India</span><br><span class="line">Employee: Steve Salary:$12000  Country: USA</span><br><span class="line">Employee: Jamie Salary:$14000  Country: Canada</span><br></pre></td></tr></table></figure><h2 id="map-的长度"><a href="#map-的长度" class="headerlink" title="map 的长度"></a>map 的长度</h2><p>map 的长度可以用 <a href="https://golang.org/pkg/builtin/#len">len</a>函数来确定。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    employeeSalary := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>&#123;</span><br><span class="line">        <span class="string">&quot;steve&quot;</span>: <span class="number">12000</span>,</span><br><span class="line">        <span class="string">&quot;jamie&quot;</span>: <span class="number">15000</span>,</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Println(<span class="string">&quot;length is&quot;</span>, <span class="built_in">len</span>(employeeSalary))</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/vDxsqn6g-0p">Run in playground</a></p><p>上面程序中的 <code>len(employeeSalary)</code> 返回 map 长度。上面的程序打印出来。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">length is 2</span><br></pre></td></tr></table></figure><h2 id="map-是引用类型"><a href="#map-是引用类型" class="headerlink" title="map 是引用类型"></a>map 是引用类型</h2><p>与切片类似，map 是引用类型。当一个 map 被分配给一个新的变量时，它们都指向同一个内部数据结构。因此，在一个中的变化将反映在另一个中。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    employeeSalary := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>&#123;</span><br><span class="line">        <span class="string">&quot;steve&quot;</span>: <span class="number">12000</span>,</span><br><span class="line">        <span class="string">&quot;jamie&quot;</span>: <span class="number">15000</span>,</span><br><span class="line">        <span class="string">&quot;mike&quot;</span>: <span class="number">9000</span>,</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Println(<span class="string">&quot;Original employee salary&quot;</span>, employeeSalary)</span><br><span class="line">    modified := employeeSalary</span><br><span class="line">    modified[<span class="string">&quot;mike&quot;</span>] = <span class="number">18000</span></span><br><span class="line">    fmt.Println(<span class="string">&quot;Employee salary changed&quot;</span>, employeeSalary)</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/hWouI1KvEb_i">Run in playground</a></p><p>在上述程序中，<code>employeeSalary</code> 被分配到 <code>modified</code>。在下一行中，<code>mike</code> 的工资在 <code>modified</code> map 中被改为<code>18000</code>。Mike 的工资现在在 <code>employeeSalary</code> 中也是 <code>18000</code>。该程序输出。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Original employee salary map[jamie:15000 mike:9000 steve:12000]</span><br><span class="line">Employee salary changed map[jamie:15000 mike:18000 steve:12000]</span><br></pre></td></tr></table></figure><p>当 map 作为参数传递给函数时也是类似的情况。当在函数中对 map 做任何改变时，它对调用者也是可见的。</p><h2 id="map-相等性"><a href="#map-相等性" class="headerlink" title="map 相等性"></a>map 相等性</h2><p>map 不能使用 <code>==</code> 操作符进行比较。<code>==</code> 只能用于检查一个 map 是否为<code>nil</code>。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    map1 := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>&#123;</span><br><span class="line">        <span class="string">&quot;one&quot;</span>: <span class="number">1</span>,</span><br><span class="line">        <span class="string">&quot;two&quot;</span>: <span class="number">2</span>,</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    map2 := map1</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> map1 == map2 &#123;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/MALqDyWkcT">Run in playground</a></p><p>上述程序将无法编译，错误为</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">invalid operation: map1 == map2 (map can only be compared to nil)</span><br></pre></td></tr></table></figure><p>检查两个 map 是否相等的一种方法是逐一比较每个 map 的各个元素。另一种方法是使用 <a href="https://golangbot.com/reflection/">反射</a>。我鼓励你为此写一个程序，并使其发挥作用。</p><p>我已经把我们讨论过的所有概念编成了一个程序。你可以从 <a href="https://github.com/golangbot/maps">github</a> 下载它。</p><p>至此，本教程结束。希望你喜欢它。请留下您的评论。</p><blockquote><p>原文地址 <a href="https://golangbot.com/maps/">Golang Maps Tutorial</a><br>原文作者：<a href="https://golangbot.com/about/">Naveen Ramanathan</a><br>译文出自：<a href="https://blog.zisheng.pro/categories/%E6%B4%9B%E7%AB%B9%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/">紫升翻译计划</a></p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/b6214c116672/</id>
    <link href="https://blog.zisheng.pro/posts/b6214c116672/"/>
    <published>2023-03-19T07:13:00.000Z</published>
    <summary>map 是 Go 中的一个内置类型，用于存储键值对。</summary>
    <title>Go 语言系列教程のMap</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/categories/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/categories/Golang/"/>
    <category term="Go 语言系列教程" scheme="https://blog.zisheng.pro/categories/Golang/Go-%E8%AF%AD%E8%A8%80%E7%B3%BB%E5%88%97%E6%95%99%E7%A8%8B/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/tags/Golang/"/>
    <category term="Go 语言系列教程" scheme="https://blog.zisheng.pro/tags/Go-%E8%AF%AD%E8%A8%80%E7%B3%BB%E5%88%97%E6%95%99%E7%A8%8B/"/>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/tags/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="Go 参数可变的函数" scheme="https://blog.zisheng.pro/tags/Go-%E5%8F%82%E6%95%B0%E5%8F%AF%E5%8F%98%E7%9A%84%E5%87%BD%E6%95%B0/"/>
    <content>
      <![CDATA[<h2 id="什么是参数可变的函数？"><a href="#什么是参数可变的函数？" class="headerlink" title="什么是参数可变的函数？"></a>什么是参数可变的函数？</h2><p>一般来说，函数只接受固定数量的参数。参数可变的函数是一个接受可变参数的函数。如果一个函数定义的最后一个参数前面有省略号 <strong>…</strong>，那么该函数可以接受该参数的任何数量的参数。</p><p><strong>只有一个函数的最后一个参数可以是可变的。我们将在本教程的下一节中了解为什么是这样的情况。</strong></p><h2 id="语法"><a href="#语法" class="headerlink" title="语法"></a>语法</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">hello</span><span class="params">(a <span class="type">int</span>, b ...<span class="type">int</span>)</span></span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在上面的函数中，参数 <code>b</code> 是可变的，因为它的前缀是省略号，它可以接受任何数量的参数。这个函数可以通过使用语法来调用。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">hello(<span class="number">1</span>, <span class="number">2</span>) <span class="comment">// 将一个参数 `2` 传递给 b</span></span><br><span class="line">hello(<span class="number">5</span>, <span class="number">6</span>, <span class="number">7</span>, <span class="number">8</span>, <span class="number">9</span>) <span class="comment">// 将参数 6、7、8 和 9 传递给 b</span></span><br></pre></td></tr></table></figure><p>在上面的代码中，我们调用 <code>hello</code>，参数 <code>b</code> 是一个参数 <code>2</code>。1 中，我们调用 <code>hello</code>，参数 <code>b</code> 是一个参数 <code>2</code>，我们在下一行向 <code>b</code> 传递四个参数 <code>6, 7, 8, 9</code>。</p><p>也可以向一个变量函数传递零参数。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hello(<span class="number">1</span>)</span><br></pre></td></tr></table></figure><p>在上面的代码中，我们调用 <code>hello</code> 时，<code>b</code> 的参数为零。这是很好的。</p><p>现在我想你已经明白了为什么变量参数应该只在最后一个。</p><p>让我们试着让 <code>hello</code> 函数的第一个参数变成变量。</p><p>语法将是这样的</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">hello</span><span class="params">(b ...<span class="type">int</span>, a <span class="type">int</span>)</span></span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在上面的函数中，不可能向参数 <code>a</code> 传递参数，因为无论我们传递什么参数，都会被分配给第一个参数 <code>b</code>，因为它是变元的。因此，变量参数只能出现在函数定义的最后。上述函数将无法编译，错误为 <code>syntax error: cannot use ... with non-final parameter b</code>。</p><h2 id="例子和了解变量函数的工作原理"><a href="#例子和了解变量函数的工作原理" class="headerlink" title="例子和了解变量函数的工作原理"></a>例子和了解变量函数的工作原理</h2><p>让我们来创建我们自己的变量函数。我们将编写一个简单的程序来查找输入的整数列表中是否存在一个整数。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">find</span><span class="params">(num <span class="type">int</span>, nums ...<span class="type">int</span>)</span></span> &#123;</span><br><span class="line">    fmt.Printf(<span class="string">&quot;type of nums is %T\n&quot;</span>, nums)</span><br><span class="line">    found := <span class="literal">false</span></span><br><span class="line">    <span class="keyword">for</span> i, v := <span class="keyword">range</span> nums &#123;</span><br><span class="line">        <span class="keyword">if</span> v == num &#123;</span><br><span class="line">            fmt.Println(num, <span class="string">&quot;found at index&quot;</span>, i, <span class="string">&quot;in&quot;</span>, nums)</span><br><span class="line">            found = <span class="literal">true</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> !found &#123;</span><br><span class="line">        fmt.Println(num, <span class="string">&quot;not found in &quot;</span>, nums)</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Printf(<span class="string">&quot;\n&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    find(<span class="number">89</span>, <span class="number">89</span>, <span class="number">90</span>, <span class="number">95</span>)</span><br><span class="line">    find(<span class="number">45</span>, <span class="number">56</span>, <span class="number">67</span>, <span class="number">45</span>, <span class="number">90</span>, <span class="number">109</span>)</span><br><span class="line">    find(<span class="number">78</span>, <span class="number">38</span>, <span class="number">56</span>, <span class="number">98</span>)</span><br><span class="line">    find(<span class="number">87</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/7occymiS6s">Run in playground</a></p><p>在上面的程序中，<code>func find(num int, nums ...int)</code> 接受参数 <code>nums</code> 的数量不定。在函数 <em>find</em> 中，<code>nums</code> 的类型是 <code>[]int</code>，即一个整数切片。</p><p>变量函数的工作方式是将变量参数的数量转换为变量参数的类型的切片。例如，在上面程序的中，<code>find</code> 函数的变量参数数是 89、90、95。查找函数期望一个变量 <code>int</code> 参数。因此，这三个参数将被编译器转换为 <code>int</code> 类型的切片 <code>[]int{89, 90, 95}</code>，然后它将被传递给 <code>find</code> 函数。</p><p><code>for</code> 的循环是在 <code>f</code> 的基础上进行的。<code>for</code> 循环在 <code>nums</code> 切片上运行，如果 <code>num</code> 存在于切片中，则打印出它的位置。如果没有，则打印出没有找到这个数字。</p><p>上述程序的输出。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">type</span> of nums is []int</span><br><span class="line">89 found at index 0 <span class="keyword">in</span> [89 90 95]</span><br><span class="line"></span><br><span class="line"><span class="built_in">type</span> of nums is []int</span><br><span class="line">45 found at index 2 <span class="keyword">in</span> [56 67 45 90 109]</span><br><span class="line"></span><br><span class="line"><span class="built_in">type</span> of nums is []int</span><br><span class="line">78 not found <span class="keyword">in</span>  [38 56 98]</span><br><span class="line"></span><br><span class="line"><span class="built_in">type</span> of nums is []int</span><br><span class="line">87 not found <span class="keyword">in</span>  []</span><br></pre></td></tr></table></figure><p>在上述程序中，<code>find</code> 函数的调用只有一个参数。我们没有向变量 <code>nums ...int</code> 参数传递任何参数。正如前面所讨论的，这是完全合法的，在这种情况下，<code>nums</code> 将是一个长度和容量为 0 的 <code>nil</code> 切片。</p><h2 id="切片参数-vs-可变参数"><a href="#切片参数-vs-可变参数" class="headerlink" title="切片参数 vs 可变参数"></a>切片参数 vs 可变参数</h2><p>我们现在肯定应该有一个问题在你的脑海中徘徊。在上一节中，我们了解到函数的变量参数实际上是转换了一个切片。那么，既然我们可以用切片实现同样的功能，为什么还要用变量函数呢？我在下面用切片重写了上面的程序。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">find</span><span class="params">(num <span class="type">int</span>, nums []<span class="type">int</span>)</span></span> &#123;</span><br><span class="line">    fmt.Printf(<span class="string">&quot;type of nums is %T\n&quot;</span>, nums)</span><br><span class="line">    found := <span class="literal">false</span></span><br><span class="line">    <span class="keyword">for</span> i, v := <span class="keyword">range</span> nums &#123;</span><br><span class="line">        <span class="keyword">if</span> v == num &#123;</span><br><span class="line">            fmt.Println(num, <span class="string">&quot;found at index&quot;</span>, i, <span class="string">&quot;in&quot;</span>, nums)</span><br><span class="line">            found = <span class="literal">true</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> !found &#123;</span><br><span class="line">        fmt.Println(num, <span class="string">&quot;not found in &quot;</span>, nums)</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Printf(<span class="string">&quot;\n&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    find(<span class="number">89</span>, []<span class="type">int</span>&#123;<span class="number">89</span>, <span class="number">90</span>, <span class="number">95</span>&#125;)</span><br><span class="line">    find(<span class="number">45</span>, []<span class="type">int</span>&#123;<span class="number">56</span>, <span class="number">67</span>, <span class="number">45</span>, <span class="number">90</span>, <span class="number">109</span>&#125;)</span><br><span class="line">    find(<span class="number">78</span>, []<span class="type">int</span>&#123;<span class="number">38</span>, <span class="number">56</span>, <span class="number">98</span>&#125;)</span><br><span class="line">    find(<span class="number">87</span>, []<span class="type">int</span>&#123;&#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/rG-XRL3yycJ">Run in playground</a></p><p>以下是使用可变参数而不是切片的优点。</p><ol><li>不需要在每次函数调用时创建一个片断。如果你看一下上面的程序，我们在每次函数调用时创建了新的分片。当使用变量函数时，可以避免这种额外的片断创建。</li><li>在上述程序中，我们创建了一个空的切片，以满足 <code>find</code> 函数的签名。在参数可变函数的情况下，这完全没有必要。当使用变量函数时，这一行可以直接用 <code>find(87)</code>。</li><li>我个人认为，有变量函数的程序比有切片的程序更具可读性 :)</li></ol><h2 id="Append-是一个变量函数"><a href="#Append-是一个变量函数" class="headerlink" title="Append 是一个变量函数"></a>Append 是一个变量函数</h2><p>你有没有想过，标准库中的 append 函数是如何将数值追加到一个切片中的，它可以接受任何数量的参数。这是因为它是一个变量函数。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">append</span><span class="params">(slice []Type, elems ...Type)</span></span> []Type</span><br></pre></td></tr></table></figure><p>以上是 <code>append</code> 函数的定义。在这个定义中，<code>elems</code> 是一个变量参数。因此 append 可以接受不同数量的参数。</p><h2 id="将一个切片传递给一个参数可变的函数"><a href="#将一个切片传递给一个参数可变的函数" class="headerlink" title="将一个切片传递给一个参数可变的函数"></a>将一个切片传递给一个参数可变的函数</h2><p>让我们把一个切片传递给一个变量函数，并从下面的例子中找出会发生什么。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">find</span><span class="params">(num <span class="type">int</span>, nums ...<span class="type">int</span>)</span></span> &#123;</span><br><span class="line">    fmt.Printf(<span class="string">&quot;type of nums is %T\n&quot;</span>, nums)</span><br><span class="line">    found := <span class="literal">false</span></span><br><span class="line">    <span class="keyword">for</span> i, v := <span class="keyword">range</span> nums &#123;</span><br><span class="line">        <span class="keyword">if</span> v == num &#123;</span><br><span class="line">            fmt.Println(num, <span class="string">&quot;found at index&quot;</span>, i, <span class="string">&quot;in&quot;</span>, nums)</span><br><span class="line">            found = <span class="literal">true</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> !found &#123;</span><br><span class="line">        fmt.Println(num, <span class="string">&quot;not found in &quot;</span>, nums)</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Printf(<span class="string">&quot;\n&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    nums := []<span class="type">int</span>&#123;<span class="number">89</span>, <span class="number">90</span>, <span class="number">95</span>&#125;</span><br><span class="line">    find(<span class="number">89</span>, nums)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/A-DNilpH2L">Run in playground</a></p><p>我们将一个切片传递给一个期望有可变数量参数的函数。</p><p>这是不可行的。上述程序将失败，编译错误为 <code>./prog.go:23:10: cannot use nums (type []int) as type int in argument to find</code>。</p><p>为什么会出现这种情况呢？嗯，这是很直接的问题。下面提供了 <code>find</code> 函数的签名。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">find</span><span class="params">(num <span class="type">int</span>, nums ...<span class="type">int</span>)</span></span></span><br></pre></td></tr></table></figure><p>根据变量函数的定义，<code>nums ...int</code>意味着它将接受可变数量的<code>int</code>类型的参数。</p><p>在上面程序中，<code>nums</code> 是 <code>[]int</code> 切片，被传递给 <code>find</code> 函数，该函数期待一个变量 <code>int</code> 参数。正如我们已经讨论过的，这些变量参数将被转换为 <code>int</code> 类型的切片，因为 <code>find</code> 期待变量 <code>int</code> 参数。在这种情况下，<code>nums</code> 已经是一个 <code>[]int</code> 分片，编译器试图创建一个新的 <code>[]int</code>，也就是说，编译器试图做到:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">find(<span class="number">89</span>, []<span class="type">int</span>&#123;nums&#125;)</span><br></pre></td></tr></table></figure><p>这将会失败，因为 <code>nums</code> 是一个 <code>[]int</code> 而不是一个 <code>int</code>。</p><p>那么有没有一种方法可以将一个切片传递给一个变量函数呢？答案是肯定的。</p><p>有一个语法糖可以用来传递一个分片到变量函数中。你必须用省略号 <code>...</code> 作为切片的后缀，如果这样做了，切片就会直接传递给函数，而不需要创建新的切片。</p><p>在上面的程序中，如果你把 <code>find(89, nums)</code> 替换为 <code>find(89, nums...)</code>，程序将被编译并打印出以下输出。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">type of nums is []int</span><br><span class="line">89 found at index 0 in [89 90 95]</span><br></pre></td></tr></table></figure><p>以下是完整的方案供您参考。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">find</span><span class="params">(num <span class="type">int</span>, nums ...<span class="type">int</span>)</span></span> &#123;</span><br><span class="line">    fmt.Printf(<span class="string">&quot;type of nums is %T\n&quot;</span>, nums)</span><br><span class="line">    found := <span class="literal">false</span></span><br><span class="line">    <span class="keyword">for</span> i, v := <span class="keyword">range</span> nums &#123;</span><br><span class="line">        <span class="keyword">if</span> v == num &#123;</span><br><span class="line">            fmt.Println(num, <span class="string">&quot;found at index&quot;</span>, i, <span class="string">&quot;in&quot;</span>, nums)</span><br><span class="line">            found = <span class="literal">true</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> !found &#123;</span><br><span class="line">        fmt.Println(num, <span class="string">&quot;not found in &quot;</span>, nums)</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Printf(<span class="string">&quot;\n&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    nums := []<span class="type">int</span>&#123;<span class="number">89</span>, <span class="number">90</span>, <span class="number">95</span>&#125;</span><br><span class="line">    find(<span class="number">89</span>, nums...)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/IvzwhzhFsT">Run in playground</a></p><h3 id="明白了吗？"><a href="#明白了吗？" class="headerlink" title="明白了吗？"></a>明白了吗？</h3><p>只要确保你知道当你在一个参数可变的函数内修改一个切片时你在做什么。</p><p>让我们看一个简单的例子。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">change</span><span class="params">(s ...<span class="type">string</span>)</span></span> &#123;</span><br><span class="line">    s[<span class="number">0</span>] = <span class="string">&quot;Go&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    welcome := []<span class="type">string</span>&#123;<span class="string">&quot;hello&quot;</span>, <span class="string">&quot;world&quot;</span>&#125;</span><br><span class="line">    change(welcome...)</span><br><span class="line">    fmt.Println(welcome)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/R0GsuW7rdd">Run in playground</a></p><p>你认为上述程序的输出会是什么？如果你认为会是 <code>[Go world]</code>，恭喜你！你已经理解了参数可变的函数和切片。如果你弄错了，也没什么大不了的，让我解释一下我们是如何得到这个输出的。</p><p>在上面程序中，我们使用了语法糖 <code>...</code>，并将切片作为变量参数传递给<code>change</code> 函数。</p><p>正如我们已经讨论过的，如果使用 <code>...</code>，<code>welcome</code> 切片本身将作为一个参数被传递，而不会创建一个新的切片。因此 <code>welcome</code> 将作为参数传递给 <code>change</code> 函数。</p><p>在 <code>change</code> 函数中，分片的第一个元素被改变为 <code>Go</code>。因此，这个程序输出</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[Go world]</span><br></pre></td></tr></table></figure><p>这里还有一个了解参数可变的函数的程序。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">change</span><span class="params">(s ...<span class="type">string</span>)</span></span> &#123;</span><br><span class="line">    s[<span class="number">0</span>] = <span class="string">&quot;Go&quot;</span></span><br><span class="line">    s = <span class="built_in">append</span>(s, <span class="string">&quot;playground&quot;</span>)</span><br><span class="line">    fmt.Println(s)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    welcome := []<span class="type">string</span>&#123;<span class="string">&quot;hello&quot;</span>, <span class="string">&quot;world&quot;</span>&#125;</span><br><span class="line">    change(welcome...)</span><br><span class="line">    fmt.Println(welcome)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/WdbFIkdLoe">Run in playground</a></p><p>我想把它作为一个练习，让你弄清楚上述程序是如何工作的</p><p>参数可变的函数就介绍到这里。谢谢你的阅读。请留下您宝贵的反馈和意见。祝你有个愉快的一天。</p><blockquote><p>原文地址 <a href="https://golangbot.com/variadic-functions/">Variadic Functions</a><br>原文作者：<a href="https://golangbot.com/about/">Naveen Ramanathan</a><br>译文出自：<a href="https://blog.zisheng.pro/categories/%E6%B4%9B%E7%AB%B9%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/">紫升翻译计划</a></p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/f4c08ab26cae/</id>
    <link href="https://blog.zisheng.pro/posts/f4c08ab26cae/"/>
    <published>2023-03-19T07:12:00.000Z</published>
    <summary>一般来说，函数只接受固定数量的参数。参数可变的函数是一个接受可变参数的函数。如果一个函数定义的最后一个参数前面有省略号 ...，那么该函数可以接受该参数的任何数量的参数。</summary>
    <title>Go 语言系列教程の参数可变的函数</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/categories/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/categories/Golang/"/>
    <category term="Go 语言系列教程" scheme="https://blog.zisheng.pro/categories/Golang/Go-%E8%AF%AD%E8%A8%80%E7%B3%BB%E5%88%97%E6%95%99%E7%A8%8B/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/tags/Golang/"/>
    <category term="Go 语言系列教程" scheme="https://blog.zisheng.pro/tags/Go-%E8%AF%AD%E8%A8%80%E7%B3%BB%E5%88%97%E6%95%99%E7%A8%8B/"/>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/tags/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="Go 切片" scheme="https://blog.zisheng.pro/tags/Go-%E5%88%87%E7%89%87/"/>
    <content>
      <![CDATA[<p>切片是数组上面的一个方便、灵活和强大的包装器。切片并不拥有自己的任何数据。它们只是对现有数组的引用。</p><h2 id="创建切片"><a href="#创建切片" class="headerlink" title="创建切片"></a>创建切片</h2><p>一个具有 T 类型元素的切片用 <code>[]T</code> 表示。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    a := [<span class="number">5</span>]<span class="type">int</span>&#123;<span class="number">76</span>, <span class="number">77</span>, <span class="number">78</span>, <span class="number">79</span>, <span class="number">80</span>&#125;</span><br><span class="line">    <span class="keyword">var</span> b []<span class="type">int</span> = a[<span class="number">1</span>:<span class="number">4</span>] <span class="comment">// 创建一个 a[1] 到 a[3] 的切片</span></span><br><span class="line">    fmt.Println(b)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/Za6w5eubBB">Run in playground</a></p><p>语法<code>a[start:end]</code>从数组 <code>a</code> 中创建一个切片，从索引 <code>start</code> 开始到索引 <code>end</code>-1<code>。所以在上述程序中，</code>a[1:4]<code>创建了一个从索引 1 到 3 的数组</code>a<code>的切片。因此切片</code>b<code>的值是</code>[77 78 79]&#96;。</p><p>让我们来看看创建切片的另一种方法。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    c := []<span class="type">int</span>&#123;<span class="number">6</span>, <span class="number">7</span>, <span class="number">8</span>&#125; <span class="comment">// 创建一个数组并返回切片的索引</span></span><br><span class="line">    fmt.Println(c)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/_Z97MgXavA">Run in playground</a></p><p>在上述程序中，<code>c := []int{6, 7, 8}</code> 创建了一个包含 3 个整数的数组，并返回一个存储在 c 中的切片引用。</p><h3 id="修改一个切片"><a href="#修改一个切片" class="headerlink" title="修改一个切片"></a>修改一个切片</h3><p>一个切片并不拥有自己的任何数据。它只是底层数组的一个代表。对切片所做的任何修改都会反映在底层数组中。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    darr := [...]<span class="type">int</span>&#123;<span class="number">57</span>, <span class="number">89</span>, <span class="number">90</span>, <span class="number">82</span>, <span class="number">100</span>, <span class="number">78</span>, <span class="number">67</span>, <span class="number">69</span>, <span class="number">59</span>&#125;</span><br><span class="line">    dslice := darr[<span class="number">2</span>:<span class="number">5</span>]</span><br><span class="line">    fmt.Println(<span class="string">&quot;array before&quot;</span>,darr)</span><br><span class="line">    <span class="keyword">for</span> i := <span class="keyword">range</span> dslice &#123;</span><br><span class="line">        dslice[i]++</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Println(<span class="string">&quot;array after&quot;</span>,darr)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/6FinudNf1k">Run in playground</a></p><p>在上述程序中，我们从数组的索引 2、3、4 创建 <code>dslice</code>。for 循环将这些索引的值增加 1。当我们在 for 循环后打印数组时，我们可以看到切片的变化已经反映在数组中。该程序的输出是</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">array before [57 89 90 82 100 78 67 69 59]</span><br><span class="line">array after [57 89 91 83 101 78 67 69 59]</span><br></pre></td></tr></table></figure><p>当一些切片共享同一个底层数组时，每个切片的变化都会反映在数组中。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    numa := [<span class="number">3</span>]<span class="type">int</span>&#123;<span class="number">78</span>, <span class="number">79</span> ,<span class="number">80</span>&#125;</span><br><span class="line">    nums1 := numa[:] <span class="comment">// 创建一个包含数组中所有元素的片断</span></span><br><span class="line">    nums2 := numa[:]</span><br><span class="line">    fmt.Println(<span class="string">&quot;array before change 1&quot;</span>,numa)</span><br><span class="line">    nums1[<span class="number">0</span>] = <span class="number">100</span></span><br><span class="line">    fmt.Println(<span class="string">&quot;array after modification to slice nums1&quot;</span>, numa)</span><br><span class="line">    nums2[<span class="number">1</span>] = <span class="number">101</span></span><br><span class="line">    fmt.Println(<span class="string">&quot;array after modification to slice nums2&quot;</span>, numa)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/mdNi4cs854">Run in playground</a></p><p>在 <code>numa[:]</code> 中，缺少开始和结束值。start 和 end 的默认值分别为 <code>0</code> 和 <code>len(numa)</code>。两个切片 <code>nums1</code> 和 <code>nums2</code> 共享同一个数组。该程序的输出是</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">array before change 1 [78 79 80]</span><br><span class="line">array after modification to slice nums1 [100 79 80]</span><br><span class="line">array after modification to slice nums2 [100 101 80]</span><br></pre></td></tr></table></figure><p>从输出结果来看，很明显，当切片共享同一个数组时。对切片所做的修改会反映在数组中。</p><h3 id="切片的长度和容量"><a href="#切片的长度和容量" class="headerlink" title="切片的长度和容量"></a>切片的长度和容量</h3><p>切片的长度是切片中元素的数量。<strong>切片的容量是指从创建分片的索引开始的底层数组中的元素数</strong>。</p><p>让我们写一些代码来更好地理解这一点。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    fruitarray := [...]<span class="type">string</span>&#123;<span class="string">&quot;apple&quot;</span>, <span class="string">&quot;orange&quot;</span>, <span class="string">&quot;grape&quot;</span>, <span class="string">&quot;mango&quot;</span>, <span class="string">&quot;water melon&quot;</span>, <span class="string">&quot;pine apple&quot;</span>, <span class="string">&quot;chikoo&quot;</span>&#125;</span><br><span class="line">    fruitslice := fruitarray[<span class="number">1</span>:<span class="number">3</span>]</span><br><span class="line">    fmt.Printf(<span class="string">&quot;length of slice %d capacity %d&quot;</span>, <span class="built_in">len</span>(fruitslice), <span class="built_in">cap</span>(fruitslice)) <span class="comment">// 切的长度为 2，容量为 6</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/a1WOcdv827">Run in playground</a></p><p>在上面的程序中，<code>fruitslice</code> 是由 <code>fruitarray</code> 的索引 1 和 2 创建的。因此，<code>fruitslice</code> 的长度是 2。</p><p><code>fruitarray</code> 的长度是 7。<code>fruiteslice</code> 是从 <code>fruitarray</code> 的索引 1 创建的。因此，<code>fruitslice</code> 的容量是 <code>fruitarray&#39; 中从索引 1 开始的元素数量，即从</code>orange<code> 开始，这个值是</code>6<code>。因此，</code>fruitslice&#96; 的容量是 6。<a href="https://play.golang.org/p/a1WOcdv827">程序</a> 打印出 <strong>length of slice 2 capacity 6</strong>。</p><p>一个切片可以被重新切割到它的容量。任何超过这个容量的部分都会导致程序出现运行时错误。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    fruitarray := [...]<span class="type">string</span>&#123;<span class="string">&quot;apple&quot;</span>, <span class="string">&quot;orange&quot;</span>, <span class="string">&quot;grape&quot;</span>, <span class="string">&quot;mango&quot;</span>, <span class="string">&quot;water melon&quot;</span>, <span class="string">&quot;pine apple&quot;</span>, <span class="string">&quot;chikoo&quot;</span>&#125;</span><br><span class="line">    fruitslice := fruitarray[<span class="number">1</span>:<span class="number">3</span>]</span><br><span class="line">    fmt.Printf(<span class="string">&quot;length of slice %d capacity %d\n&quot;</span>, <span class="built_in">len</span>(fruitslice), <span class="built_in">cap</span>(fruitslice)) <span class="comment">// 长度为 2，容量为 6</span></span><br><span class="line">    fruitslice = fruitslice[:<span class="built_in">cap</span>(fruitslice)] <span class="comment">// 重新分割切片，直到其容量。</span></span><br><span class="line">    fmt.Println(<span class="string">&quot;After re-slicing length is&quot;</span>,<span class="built_in">len</span>(fruitslice), <span class="string">&quot;and capacity is&quot;</span>,<span class="built_in">cap</span>(fruitslice))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/GcNzOOGicu">Run in playground</a></p><p>在上述程序中，<code>fruitslice</code> 被重新f分割成到其容量。上述程序的输出。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">length of slice 2 capacity 6</span><br><span class="line">After re-slicing length is 6 and capacity is 6</span><br></pre></td></tr></table></figure><h3 id="使用-make-创建切片"><a href="#使用-make-创建切片" class="headerlink" title="使用 make 创建切片"></a>使用 make 创建切片</h3><p><em>func make([]T, len, cap) []T</em> 可以通过传递类型、长度和容量来创建一个切片。容量参数是可选的，默认为长度。make 函数创建一个数组，并返回对它的切片的引用。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    i := <span class="built_in">make</span>([]<span class="type">int</span>, <span class="number">5</span>, <span class="number">5</span>)</span><br><span class="line">    fmt.Println(i)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/M4OqxzerxN">Run in playground</a></p><p>当使用 make 创建一个切片时，这些值默认为零。上述程序将输出 <code>[0 0 0 0]</code>。</p><blockquote><p>原文地址 <a href="https://golangbot.com/arrays-and-slices/">Arrays and Slices</a><br>原文作者：<a href="https://golangbot.com/about/">Naveen Ramanathan</a><br>译文出自：<a href="https://blog.zisheng.pro/categories/%E6%B4%9B%E7%AB%B9%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/">紫升翻译计划</a></p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/26829e2dd29f/</id>
    <link href="https://blog.zisheng.pro/posts/26829e2dd29f/"/>
    <published>2023-03-19T07:11:30.000Z</published>
    <summary>切片是数组上面的一个方便、灵活和强大的包装器。切片并不拥有自己的任何数据。它们只是对现有数组的引用。</summary>
    <title>Go 语言系列教程の切片</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/categories/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/categories/Golang/"/>
    <category term="Go 语言系列教程" scheme="https://blog.zisheng.pro/categories/Golang/Go-%E8%AF%AD%E8%A8%80%E7%B3%BB%E5%88%97%E6%95%99%E7%A8%8B/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/tags/Golang/"/>
    <category term="Go 语言系列教程" scheme="https://blog.zisheng.pro/tags/Go-%E8%AF%AD%E8%A8%80%E7%B3%BB%E5%88%97%E6%95%99%E7%A8%8B/"/>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/tags/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="Go 数组" scheme="https://blog.zisheng.pro/tags/Go-%E6%95%B0%E7%BB%84/"/>
    <content>
      <![CDATA[<p>数组是属于同一类型的元素的集合。 例如，整数 5、8、9、79、76 的集合构成一个数组。 Go 中不允许混合不同类型的值，例如同时包含字符串和整数的数组。</p><h2 id="数组声明"><a href="#数组声明" class="headerlink" title="数组声明"></a>数组声明</h2><p>数组属于类型 <code>[n]T</code>。 <code>n</code> 表示数组中元素的数量，<code>T</code> 表示每个元素的类型。 元素的数量 <code>n</code> 也是类型的一部分（我们将在稍后更详细地讨论这一点。）</p><p>声明数组有不同的方法。 让我们一一看看它们。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">var</span> a [<span class="number">3</span>]<span class="type">int</span> <span class="comment">// 长度为 3 的数组</span></span><br><span class="line">    fmt.Println(a)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/Zvgh82u0ej">Run in playground</a></p><p><code>var a[3]int</code> 声明了一个长度为 3 的整数数组。数组中的所有元素都会自动分配数组类型的零值。 在这种情况下，<code>a</code> 是一个整数数组，因此 <code>a</code> 的所有元素都分配给 <code>0</code>，即 int 的零值。 运行上面的程序会打印</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[0 0 0]</span><br></pre></td></tr></table></figure><p>数组的索引从 <code>0</code> 开始，到 <code>length - 1</code> 结束。 让我们为上面的数组分配一些值。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">var</span> a [<span class="number">3</span>]<span class="type">int</span> <span class="comment">// 长度为 3 的 int 数组</span></span><br><span class="line">    a[<span class="number">0</span>] = <span class="number">12</span> <span class="comment">// 数组索引从 0 开始</span></span><br><span class="line">    a[<span class="number">1</span>] = <span class="number">78</span></span><br><span class="line">    a[<span class="number">2</span>] = <span class="number">50</span></span><br><span class="line">    fmt.Println(a)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/WF0Uj8sv39">Run in playground</a></p><p>a[0] 为数组的第一个元素赋值。 该程序将打印</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[12 78 50]</span><br></pre></td></tr></table></figure><p>让我们使用 <strong>短声明</strong> 来创建相同的数组。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    a := [<span class="number">3</span>]<span class="type">int</span>&#123;<span class="number">12</span>, <span class="number">78</span>, <span class="number">50</span>&#125; <span class="comment">// 创建数组的短声明</span></span><br><span class="line">    fmt.Println(a)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/NKOV04zgI6">Run in playground</a></p><p>上面的程序将打印相同的输出</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[12 78 50]</span><br></pre></td></tr></table></figure><p>使用短声明时，不必为数组中的所有元素分配一个值。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    a := [<span class="number">3</span>]<span class="type">int</span>&#123;<span class="number">12</span>&#125;</span><br><span class="line">    fmt.Println(a)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/AdPH0kXRly">Run in playground</a></p><p>在上面的程序中，<code>a := [3]int{12}</code> 声明了一个长度为 3 的数组，但只提供了一个值 <code>12</code>。 其余 2 个元素自动分配为 <code>0</code>。 该程序将打印</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[12 0 0]</span><br></pre></td></tr></table></figure><p>你甚至可以忽略声明中数组的长度，并用 <code>...</code> 替换它，让编译器自动计算长度。 代码实现如下。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    a := [...]<span class="type">int</span>&#123;<span class="number">12</span>, <span class="number">78</span>, <span class="number">50</span>&#125; <span class="comment">// ... 让编译器决定数组长度</span></span><br><span class="line">    fmt.Println(a)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/_fVmr6KGDh">Run in playground</a></p><p><strong>数组的大小是类型的一部分。</strong> 因此 <code>[5]int</code> 和 <code>[25]int</code> 是不同的类型。 因此，无法调整数组大小。 不要担心这个限制，因为可以使用 <code>切片</code> 来突破这个限制。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    a := [<span class="number">3</span>]<span class="type">int</span>&#123;<span class="number">5</span>, <span class="number">78</span>, <span class="number">8</span>&#125;</span><br><span class="line">    <span class="keyword">var</span> b [<span class="number">5</span>]<span class="type">int</span></span><br><span class="line">    b = a <span class="comment">// 不可能成立，因为 `[3]int` 和 `[5]int` 是不同的类型</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/kBdot3pXSB">Run in playground</a></p><p>在上面的程序中，我们试图将类型为 <code>[3]int</code> 的变量分配给类型为 <code>[5]int</code> 的变量，这是不允许的，因此编译器将打印以下错误</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./prog.go:6:7: cannot use a (type [3]int) as type [5]int in assignment</span><br></pre></td></tr></table></figure><h2 id="数组是值类型"><a href="#数组是值类型" class="headerlink" title="数组是值类型"></a>数组是值类型</h2><p>Go 中的数组是值类型而不是引用类型。 这意味着当它们被分配给一个新变量时，原始数组的副本被分配给新变量。 如果对新变量进行了更改，它将不会反映在原始数组中。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    a := [...]<span class="type">string</span>&#123;<span class="string">&quot;USA&quot;</span>, <span class="string">&quot;China&quot;</span>, <span class="string">&quot;India&quot;</span>, <span class="string">&quot;Germany&quot;</span>, <span class="string">&quot;France&quot;</span>&#125;</span><br><span class="line">    b := a <span class="comment">// a 的副本被分配给 b</span></span><br><span class="line">    b[<span class="number">0</span>] = <span class="string">&quot;Singapore&quot;</span> <span class="comment">// b 的第一个元素被改变为 Singapore，这不会影响到 a</span></span><br><span class="line">    fmt.Println(<span class="string">&quot;a is &quot;</span>, a)</span><br><span class="line">    fmt.Println(<span class="string">&quot;b is &quot;</span>, b)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/-ncGk1mqPd">Run in playground</a></p><p>程序会打印出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">a is [USA China India Germany France]</span><br><span class="line">b is [Singapore China India Germany France]</span><br></pre></td></tr></table></figure><p>同样，当数组作为参数传递给函数时，它们是按值传递的，原始数组不变。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">changeLocal</span><span class="params">(num [5]<span class="type">int</span>)</span></span> &#123;</span><br><span class="line">    num[<span class="number">0</span>] = <span class="number">55</span></span><br><span class="line">    fmt.Println(<span class="string">&quot;inside function &quot;</span>, num)</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    num := [...]<span class="type">int</span>&#123;<span class="number">5</span>, <span class="number">6</span>, <span class="number">7</span>, <span class="number">8</span>, <span class="number">8</span>&#125;</span><br><span class="line">    fmt.Println(<span class="string">&quot;before passing to function &quot;</span>, num)</span><br><span class="line">    changeLocal(num) <span class="comment">// num 是按值传递的，因此不会因为函数调用而改变</span></span><br><span class="line">    fmt.Println(<span class="string">&quot;after passing to function &quot;</span>, num)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/e3U75Q8eUZ">Run in playground</a></p><p>该程序将打印：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">before passing to function  [5 6 7 8 8]</span><br><span class="line">inside function  [55 6 7 8 8]</span><br><span class="line">after passing to function  [5 6 7 8 8]</span><br></pre></td></tr></table></figure><h2 id="数组长度"><a href="#数组长度" class="headerlink" title="数组长度"></a>数组长度</h2><p>数组的长度是通过将数组作为参数传递给 <code>len</code> 函数来得到的。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    a := [...]<span class="type">float64</span>&#123;<span class="number">67.7</span>, <span class="number">89.8</span>, <span class="number">21</span>, <span class="number">78</span>&#125;</span><br><span class="line">    fmt.Println(<span class="string">&quot;length of a is&quot;</span>,<span class="built_in">len</span>(a))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/UrIeNlS0RN">Run in playground</a></p><p>上面的程序会打印出</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">length of a is 4</span><br></pre></td></tr></table></figure><h3 id="使用-range-迭代数组"><a href="#使用-range-迭代数组" class="headerlink" title="使用 range 迭代数组"></a>使用 range 迭代数组</h3><p><code>for</code> 循环可用于迭代数组的元素。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    a := [...]<span class="type">float64</span>&#123;<span class="number">67.7</span>, <span class="number">89.8</span>, <span class="number">21</span>, <span class="number">78</span>&#125;</span><br><span class="line">    <span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="built_in">len</span>(a); i++ &#123; <span class="comment">// 从 0 循环到数组的长度</span></span><br><span class="line">        fmt.Printf(<span class="string">&quot;%d th element of a is %.2f\n&quot;</span>, i, a[i])</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/80ejSTACO6">Run in playground</a></p><p>上面的程序使用一个 <code>for</code> 循环来迭代数组的元素，从索引 <code>0</code> 到 <code>length of the array - 1</code>。 这个程序可以工作并且会打印</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">0 th element of a is 67.70</span><br><span class="line">1 th element of a is 89.80</span><br><span class="line">2 th element of a is 21.00</span><br><span class="line">3 th element of a is 78.00</span><br></pre></td></tr></table></figure><p>Go 通过使用 <code>for</code> 循环的 <strong>range</strong> 形式提供了一种更好、更简洁的方法来迭代数组。 <code>range</code> 返回索引和该索引处的值。 让我们使用 <code>range</code> 重写上面的代码。 我们还将找到数组所有元素的总和。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    a := [...]<span class="type">float64</span>&#123;<span class="number">67.7</span>, <span class="number">89.8</span>, <span class="number">21</span>, <span class="number">78</span>&#125;</span><br><span class="line">    sum := <span class="type">float64</span>(<span class="number">0</span>)</span><br><span class="line">    <span class="keyword">for</span> i, v := <span class="keyword">range</span> a &#123; <span class="comment">// range 同时返回索引和值</span></span><br><span class="line">        fmt.Printf(<span class="string">&quot;%d the element of a is %.2f\n&quot;</span>, i, v)</span><br><span class="line">        sum += v</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Println(<span class="string">&quot;\nsum of all elements of a&quot;</span>,sum)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/Ji6FRon36m">Run in playground</a></p><p>我们打印值并计算数组 <code>a</code> 的所有元素的总和。 程序的<strong>输出</strong>是，</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">0 the element of a is 67.70</span><br><span class="line">1 the element of a is 89.80</span><br><span class="line">2 the element of a is 21.00</span><br><span class="line">3 the element of a is 78.00</span><br><span class="line"></span><br><span class="line">sum of all elements of a 256.5</span><br></pre></td></tr></table></figure><p>如果你只想要该值而想要忽略索引，您可以通过用 <code>_</code> 空白标识符替换索引来实现。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> _, v := <span class="keyword">range</span> a &#123; <span class="comment">// 忽略索引</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面的 for 循环忽略了索引。 同样，值也可以忽略。</p><h3 id="多维数组"><a href="#多维数组" class="headerlink" title="多维数组"></a>多维数组</h3><p>到目前为止我们创建的数组都是一维的。我们可以创建多维数组。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">printarray</span><span class="params">(a [3][2]<span class="type">string</span>)</span></span> &#123;</span><br><span class="line">    <span class="keyword">for</span> _, v1 := <span class="keyword">range</span> a &#123;</span><br><span class="line">        <span class="keyword">for</span> _, v2 := <span class="keyword">range</span> v1 &#123;</span><br><span class="line">            fmt.Printf(<span class="string">&quot;%s &quot;</span>, v2)</span><br><span class="line">        &#125;</span><br><span class="line">        fmt.Printf(<span class="string">&quot;\n&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    a := [<span class="number">3</span>][<span class="number">2</span>]<span class="type">string</span>&#123;</span><br><span class="line">        &#123;<span class="string">&quot;lion&quot;</span>, <span class="string">&quot;tiger&quot;</span>&#125;,</span><br><span class="line">        &#123;<span class="string">&quot;cat&quot;</span>, <span class="string">&quot;dog&quot;</span>&#125;,</span><br><span class="line">        &#123;<span class="string">&quot;pigeon&quot;</span>, <span class="string">&quot;peacock&quot;</span>&#125;, <span class="comment">// 这个逗号是必须的。 如果省略这个逗号，编译器会报错</span></span><br><span class="line">    &#125;</span><br><span class="line">    printarray(a)</span><br><span class="line">    <span class="keyword">var</span> b [<span class="number">3</span>][<span class="number">2</span>]<span class="type">string</span></span><br><span class="line">    b[<span class="number">0</span>][<span class="number">0</span>] = <span class="string">&quot;apple&quot;</span></span><br><span class="line">    b[<span class="number">0</span>][<span class="number">1</span>] = <span class="string">&quot;samsung&quot;</span></span><br><span class="line">    b[<span class="number">1</span>][<span class="number">0</span>] = <span class="string">&quot;microsoft&quot;</span></span><br><span class="line">    b[<span class="number">1</span>][<span class="number">1</span>] = <span class="string">&quot;google&quot;</span></span><br><span class="line">    b[<span class="number">2</span>][<span class="number">0</span>] = <span class="string">&quot;AT&amp;T&quot;</span></span><br><span class="line">    b[<span class="number">2</span>][<span class="number">1</span>] = <span class="string">&quot;T-Mobile&quot;</span></span><br><span class="line">    fmt.Printf(<span class="string">&quot;\n&quot;</span>)</span><br><span class="line">    printarray(b)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/InchXI4yY8">Run in playground</a></p><p>上面的程序会打印：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">lion tiger</span><br><span class="line">cat dog</span><br><span class="line">pigeon peacock</span><br><span class="line"></span><br><span class="line">apple samsung</span><br><span class="line">microsoft google</span><br><span class="line">AT&amp;T T-Mobile</span><br></pre></td></tr></table></figure><p>这就是数组。 尽管数组似乎足够灵活，但它们具有固定长度的限制。 无法增加数组的长度。而这正是 <strong>切片</strong> 擅长的。 事实上，在 Go 中，切片比传统数组更常见。</p><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>关注公众号<code>紫升早茶馆</code>，一个持续分享编程知识的地方。</p><ul><li><code>点赞</code>等于学会，<code>在看</code>等于精通</li><li>最后祝大家 2021 学习进步，升职加薪</li></ul><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a9752661d5474d8f8f4ae2584c288ac9~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><blockquote><p>原文地址 <a href="https://golangbot.com/arrays-and-slices/">Arrays and Slices</a><br>原文作者：<a href="https://golangbot.com/about/">Naveen Ramanathan</a><br>译文出自：<a href="https://blog.zisheng.pro/categories/%E6%B4%9B%E7%AB%B9%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/">紫升翻译计划</a></p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/e647c735eac5/</id>
    <link href="https://blog.zisheng.pro/posts/e647c735eac5/"/>
    <published>2023-03-19T07:11:00.000Z</published>
    <summary>数组是属于同一类型的元素的集合。 例如，整数 5、8、9、79、76 的集合构成一个数组。 Go 中不允许混合不同类型的值，例如同时包含字符串和整数的数组。</summary>
    <title>Go 语言系列教程の数组</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/categories/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/categories/Golang/"/>
    <category term="Go 语言系列教程" scheme="https://blog.zisheng.pro/categories/Golang/Go-%E8%AF%AD%E8%A8%80%E7%B3%BB%E5%88%97%E6%95%99%E7%A8%8B/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/tags/Golang/"/>
    <category term="Go 语言教程" scheme="https://blog.zisheng.pro/tags/Go-%E8%AF%AD%E8%A8%80%E6%95%99%E7%A8%8B/"/>
    <category term="Go 语言教程系列" scheme="https://blog.zisheng.pro/tags/Go-%E8%AF%AD%E8%A8%80%E6%95%99%E7%A8%8B%E7%B3%BB%E5%88%97/"/>
    <category term="Golang Switch" scheme="https://blog.zisheng.pro/tags/Golang-Switch/"/>
    <content>
      <![CDATA[<h2 id="switch-语句是什么？"><a href="#switch-语句是什么？" class="headerlink" title="switch 语句是什么？"></a>switch 语句是什么？</h2><p><code>switch</code> 是一个条件语句，它对表达式进行求值并将其与可能的匹配列表进行比较，并执行相应的代码块。可以将其视为替换复杂的 <code>if</code> 语句的惯用方式。</p><h2 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h2><p><code>Talk is cheap,show you the example</code>。让我们从一个简单的示例开始，该示例将一个手指编号作为输入并输出该手指的名称。 例如，1 是拇指，2 是食指，依此类推。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    finger := <span class="number">4</span></span><br><span class="line">    fmt.Printf(<span class="string">&quot;Finger %d is &quot;</span>, finger)</span><br><span class="line">    <span class="keyword">switch</span> finger &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="number">1</span>:</span><br><span class="line">        fmt.Println(<span class="string">&quot;Thumb&quot;</span>)</span><br><span class="line">    <span class="keyword">case</span> <span class="number">2</span>:</span><br><span class="line">        fmt.Println(<span class="string">&quot;Index&quot;</span>)</span><br><span class="line">    <span class="keyword">case</span> <span class="number">3</span>:</span><br><span class="line">        fmt.Println(<span class="string">&quot;Middle&quot;</span>)</span><br><span class="line">    <span class="keyword">case</span> <span class="number">4</span>:</span><br><span class="line">        fmt.Println(<span class="string">&quot;Ring&quot;</span>)</span><br><span class="line">    <span class="keyword">case</span> <span class="number">5</span>:</span><br><span class="line">        fmt.Println(<span class="string">&quot;Pinky&quot;</span>)</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/94ktmJWlUom">Run in playground</a></p><p>在上面第 10 行的程序 <code>switch finger</code> 中，将 <code>finger</code> 的值与每个 <code>case</code> 语句进行比较。从上到下判断条件，并执行与表达式匹配的第一个分支。 在这种情况下，<code>finger</code> 的值为 <code>4</code>，因此输出：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Finger 4 is Ring</span><br></pre></td></tr></table></figure><h2 id="不允许重复的分支"><a href="#不允许重复的分支" class="headerlink" title="不允许重复的分支"></a>不允许重复的分支</h2><p>不允许重复使用具有相同常量值的分支。如果你尝试运行以下程序，则编译器将抛错 <code>./prog.go:19:7: duplicate case 4 in switch previous case at ./prog.go:17:7</code>。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    finger := <span class="number">4</span></span><br><span class="line">    fmt.Printf(<span class="string">&quot;Finger %d is &quot;</span>, finger)</span><br><span class="line">    <span class="keyword">switch</span> finger &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="number">1</span>:</span><br><span class="line">        fmt.Println(<span class="string">&quot;Thumb&quot;</span>)</span><br><span class="line">    <span class="keyword">case</span> <span class="number">2</span>:</span><br><span class="line">        fmt.Println(<span class="string">&quot;Index&quot;</span>)</span><br><span class="line">    <span class="keyword">case</span> <span class="number">3</span>:</span><br><span class="line">        fmt.Println(<span class="string">&quot;Middle&quot;</span>)</span><br><span class="line">    <span class="keyword">case</span> <span class="number">4</span>:</span><br><span class="line">        fmt.Println(<span class="string">&quot;Ring&quot;</span>)</span><br><span class="line">    <span class="keyword">case</span> <span class="number">4</span>: <span class="comment">// 重复的分支</span></span><br><span class="line">        fmt.Println(<span class="string">&quot;Another Ring&quot;</span>)</span><br><span class="line">    <span class="keyword">case</span> <span class="number">5</span>:</span><br><span class="line">        fmt.Println(<span class="string">&quot;Pinky&quot;</span>)</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/7qrmR0hdvHH">Run in playground</a></p><h2 id="默认分支"><a href="#默认分支" class="headerlink" title="默认分支"></a>默认分支</h2><p>我们只有五个手指。如果输入不正确的手指编号会怎样？这时我们需要一个默认分支。当其他情况都不匹配时，将执行默认分支。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">switch</span> finger := <span class="number">8</span>; finger &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="number">1</span>:</span><br><span class="line">        fmt.Println(<span class="string">&quot;Thumb&quot;</span>)</span><br><span class="line">    <span class="keyword">case</span> <span class="number">2</span>:</span><br><span class="line">        fmt.Println(<span class="string">&quot;Index&quot;</span>)</span><br><span class="line">    <span class="keyword">case</span> <span class="number">3</span>:</span><br><span class="line">        fmt.Println(<span class="string">&quot;Middle&quot;</span>)</span><br><span class="line">    <span class="keyword">case</span> <span class="number">4</span>:</span><br><span class="line">        fmt.Println(<span class="string">&quot;Ring&quot;</span>)</span><br><span class="line">    <span class="keyword">case</span> <span class="number">5</span>:</span><br><span class="line">        fmt.Println(<span class="string">&quot;Pinky&quot;</span>)</span><br><span class="line">    <span class="keyword">default</span>: <span class="comment">// 默认分支</span></span><br><span class="line">        fmt.Println(<span class="string">&quot;incorrect finger number&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/Fq7U7SkHe1">Run in playground</a></p><p>在上面的程序中，<code>finger</code> 是 <code>8</code>，它与任何情况都不匹配，因此在默认情况下会打印 <code>incorrect finger number</code>。把 <code>default</code> 作为 <code>switch</code> 语句的最后一个分支是没有必要的。它可以存在于 <code>switch</code> 中的任何位置。</p><p>你可能还注意到 <code>finger</code> 声明中的微小变化。它在 <code>switch 语句中声明。 </code>switch<code>可以包含在计算表达式之前执行的可选语句。在第 8 行中，首先声明</code>finger<code>，然后在表达式中使用它。在这种情况下，</code>finger<code>的范围仅限于</code>switch&#96; 模块。</p><h2 id="多表达式分支"><a href="#多表达式分支" class="headerlink" title="多表达式分支"></a>多表达式分支</h2><p>通过用逗号将多个表达式分隔开，可以包含多个表达式。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    letter := <span class="string">&quot;i&quot;</span></span><br><span class="line">    fmt.Printf(<span class="string">&quot;Letter %s is a &quot;</span>, letter)</span><br><span class="line">    <span class="keyword">switch</span> letter &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&quot;a&quot;</span>, <span class="string">&quot;e&quot;</span>, <span class="string">&quot;i&quot;</span>, <span class="string">&quot;o&quot;</span>, <span class="string">&quot;u&quot;</span>: <span class="comment">// 分支中的多个表达式</span></span><br><span class="line">        fmt.Println(<span class="string">&quot;vowel&quot;</span>)</span><br><span class="line">    <span class="keyword">default</span>:</span><br><span class="line">        fmt.Println(<span class="string">&quot;not a vowel&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/AAVSQK76Me7">Run in playground</a></p><p>上面的程序判断 <code>letter</code> 是否是元音。第 11 行中的代码 <code>case &quot;a&quot;, &quot;e&quot;, &quot;i&quot;, &quot;o&quot;, &quot;u&quot;:</code> 与任何元音匹配。由于 <code>i</code> 是元音，因此该程序会打印：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Letter i is a vowel</span><br></pre></td></tr></table></figure><h2 id="无表达式分支"><a href="#无表达式分支" class="headerlink" title="无表达式分支"></a>无表达式分支</h2><p>switch 中的表达式是可选的，可以省略。如果省略该表达式，则认为该 <code>switch</code> 为 <code>switch true</code>，并且对每个 <code>case</code> 表达式进行求值，并执行相应的代码块。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    num := <span class="number">75</span></span><br><span class="line">    <span class="keyword">switch</span> &#123; <span class="comment">// 表达式被省略</span></span><br><span class="line">    <span class="keyword">case</span> num &gt;= <span class="number">0</span> &amp;&amp; num &lt;= <span class="number">50</span>:</span><br><span class="line">        fmt.Printf(<span class="string">&quot;%d is greater than 0 and less than 50&quot;</span>, num)</span><br><span class="line">    <span class="keyword">case</span> num &gt;= <span class="number">51</span> &amp;&amp; num &lt;= <span class="number">100</span>:</span><br><span class="line">        fmt.Printf(<span class="string">&quot;%d is greater than 51 and less than 100&quot;</span>, num)</span><br><span class="line">    <span class="keyword">case</span> num &gt;= <span class="number">101</span>:</span><br><span class="line">        fmt.Printf(<span class="string">&quot;%d is greater than 100&quot;</span>, num)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/KPkwK0VdXII">Run in playground</a></p><p>在上面的程序中，<code>switch</code> 中不存在表达式，因此将其视为真，并判断每种情况。 第12行的 <code>case num&gt; = 51 &amp;&amp; num &lt;= 100:</code> 为 <code>true</code>，程序将打印：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">75 is greater than 51 and less than 100</span><br></pre></td></tr></table></figure><p>这种类型的 <code>switch</code> 可以看作是多个 <code>if else</code> 语句句的替代方法。</p><h2 id="Fallthrough"><a href="#Fallthrough" class="headerlink" title="Fallthrough"></a>Fallthrough</h2><p>在 Go 中，在执行分支后，控制权立即从 <code>switch</code> 语句中释放出来。<code>fallthrough</code> 语句用于将控制权转移到该分支执行后立即出现的分支的第一条语句。</p><p>让我们编写一个程序来理解 <code>fallthrough</code>。我们的程序将检查输入的数字是否小于 50、100 或 200。例如，如果输入 75，则程序将打印 75 小于 100 和 200。我们将使用 <code>fallthrough</code> 来实现。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">number</span><span class="params">()</span></span> <span class="type">int</span> &#123;</span><br><span class="line">    num := <span class="number">15</span> * <span class="number">5</span></span><br><span class="line">    <span class="keyword">return</span> num</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">switch</span> num := number(); &#123; <span class="comment">// num 不是一个常量</span></span><br><span class="line">    <span class="keyword">case</span> num &lt; <span class="number">50</span>:</span><br><span class="line">        fmt.Printf(<span class="string">&quot;%d is lesser than 50\n&quot;</span>, num)</span><br><span class="line">        <span class="keyword">fallthrough</span></span><br><span class="line">    <span class="keyword">case</span> num &lt; <span class="number">100</span>:</span><br><span class="line">        fmt.Printf(<span class="string">&quot;%d is lesser than 100\n&quot;</span>, num)</span><br><span class="line">        <span class="keyword">fallthrough</span></span><br><span class="line">    <span class="keyword">case</span> num &lt; <span class="number">200</span>:</span><br><span class="line">        fmt.Printf(<span class="string">&quot;%d is lesser than 200&quot;</span>, num)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/svGJAiswQj">Run in playground</a></p><p><code>switch</code> 和 <code>case</code> 表达式只能是常量。它们也可以在运行时进行计算。 在上面的程序中，第 14 行的 <code>num</code> 被初始化为函数 <code>number()</code> 的返回值。控制权位于 <code>switch</code> 内部，并对分支进行判断。第 18 行中的 <code>case num &lt;100:</code> 是正确的，程序将输出 <code>75 is lesser than 100</code>。下一条语句是 <code>fallthrough</code>。 当遇到 <code>fallthrough</code> 时，控制权将移至下一种情况的第一个语句，并打印 <code>75 is lesser than 200</code>。该程序的输出是：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">75 is lesser than 100</span><br><span class="line">75 is lesser than 200</span><br></pre></td></tr></table></figure><p><code>fallthrough</code> 应该是 <code>case</code> 中的最后一条语句。如果它位于中间的某个位置，则编译器将报错 <code>fallthrough statement out of place</code>。</p><h2 id="即使分支被计算为假，也会发生-Fallthrough"><a href="#即使分支被计算为假，也会发生-Fallthrough" class="headerlink" title="即使分支被计算为假，也会发生 Fallthrough"></a>即使分支被计算为假，也会发生 Fallthrough</h2><p>当使用 <code>fallthrough</code> 时要考虑一些细微之处。即使分支被计算为假，也会发生 Fallthrough。</p><p>请考虑以下程序。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">switch</span> num := <span class="number">25</span>; &#123;</span><br><span class="line">    <span class="keyword">case</span> num &lt; <span class="number">50</span>:</span><br><span class="line">        fmt.Printf(<span class="string">&quot;%d is lesser than 50\n&quot;</span>, num)</span><br><span class="line">        <span class="keyword">fallthrough</span></span><br><span class="line">    <span class="keyword">case</span> num &gt; <span class="number">100</span>:</span><br><span class="line">        fmt.Printf(<span class="string">&quot;%d is greater than 100\n&quot;</span>, num)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/sjynQMXtnmY">Run in playground</a></p><p>在上述程序中，<code>num</code> 为 25，小于 50，因此第 9 行的情况为 <code>true</code>。第 11 行出现 <code>fallthrough</code>。行号中的下一个case <code>case num&gt; 100:</code>。由于 <code>num &lt; 100</code>，所以 12 是假的。但是 <code>fallthrough</code> 不考虑这一点。即使分支被计算为假，也会发生 Fallthrough。</p><p>上面的程序将打印：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">25 is lesser than 50</span><br><span class="line">25 is greater than 100</span><br></pre></td></tr></table></figure><p>因此，请确保你了解使用 <code>fallthrough</code> 时的操作。</p><p>还有一件事是，不能在 switch 的最后一种分支中下使用 <code>fallthrough</code>。 如果在最后一种情况下存在 <code>fallthrough</code>，则将导致以下编译错误。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cannot fallthrough final <span class="keyword">case</span> <span class="keyword">in</span> switch</span><br></pre></td></tr></table></figure><h2 id="switch-中的-switch"><a href="#switch-中的-switch" class="headerlink" title="switch 中的 switch"></a>switch 中的 switch</h2><p>可以使用 <code>break</code> 语句在 <code>switch</code> 完成之前提早终止它。 让我们将上面的示例修改为一个人为的示例，以了解 <code>break</code> 的工作原理。</p><p>让我们添加一个条件，如果 <code>num</code> 小于0，则 switch 应终止。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">switch</span> num := <span class="number">-5</span>; &#123;</span><br><span class="line">    <span class="keyword">case</span> num &lt; <span class="number">50</span>:</span><br><span class="line">        <span class="keyword">if</span> num &lt; <span class="number">0</span> &#123;</span><br><span class="line">            <span class="keyword">break</span></span><br><span class="line">        &#125;</span><br><span class="line">        fmt.Printf(<span class="string">&quot;%d is lesser than 50\n&quot;</span>, num)</span><br><span class="line">        <span class="keyword">fallthrough</span></span><br><span class="line">    <span class="keyword">case</span> num &lt; <span class="number">100</span>:</span><br><span class="line">        fmt.Printf(<span class="string">&quot;%d is lesser than 100\n&quot;</span>, num)</span><br><span class="line">        <span class="keyword">fallthrough</span></span><br><span class="line">    <span class="keyword">case</span> num &lt; <span class="number">200</span>:</span><br><span class="line">        fmt.Printf(<span class="string">&quot;%d is lesser than 200&quot;</span>, num)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/UHwBXPYLv1B">Run in playground</a></p><p>在上面的程序中，<code>num</code> 是 <code>-5</code>。当控件权到达第 10 行的 <code>if</code> 语句时，由于 <code>num &lt;0</code>，因此满足条件。<code>break</code> 语句在 <code>switch</code> 完成之前终止了，并且程序不输出任何内容。</p><h3 id="退出外部的循环"><a href="#退出外部的循环" class="headerlink" title="退出外部的循环"></a>退出外部的循环</h3><p>当 <code>switch case</code> 位于 <code>for</code> 循环内时，可能需要尽早终止 <code>for</code> 循环。 这可以通过标记 <code>for</code> 循环并使用 <code>switch</code> 语句内的该标记中断 <code>for</code> 循环来完成。让我们来看一个例子。</p><p>让我们编写一个程序来生成一个随机偶数。</p><p>我们将创建一个无限 <code>for</code> 循环，并使用 <code>switch</code> 条件来确定所生成的随机数是否为偶数。如果是偶数，将打印生成的数字，并使用其标签终止 <code>for</code> 循环。<code>rand</code> 包的 <a href="https://golang.org/pkg/math/rand/#Rand.Intn"><code>Intn</code></a> 函数用于生成非负伪随机数。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">    <span class="string">&quot;math/rand&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">randloop:</span><br><span class="line">    <span class="keyword">for</span> &#123;</span><br><span class="line">        <span class="keyword">switch</span> i := rand.Intn(<span class="number">100</span>); &#123;</span><br><span class="line">        <span class="keyword">case</span> i%<span class="number">2</span> == <span class="number">0</span>:</span><br><span class="line">            fmt.Printf(<span class="string">&quot;Generated even number %d&quot;</span>, i)</span><br><span class="line">            <span class="keyword">break</span> randloop</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/0bLYOgs2TUk">Run in playground</a></p><p>在上面的程序中，第 9 行中的 <code>for</code> 循环标记为 <code>randloop</code>。使用第 <code>11</code> 行的 <code>Intn</code> 函数在 0 到 99 之间生成一个随机数（不包括 100）。 如果生成的数字为偶数，则使用标签在第 14 行中断循环。</p><p>该程序打印：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Generated even number 18</span><br></pre></td></tr></table></figure><p>请注意，如果使用不带标签的 <code>break</code> 语句，则将仅中断 <code>switch</code> 语句，循环将继续运行。因此，标记循环并在 <code>switch</code> 内部的 <code>break</code> 语句中使用它对于中断外部 for 循环是必要的。</p><blockquote><p>原文地址 <a href="https://golangbot.com/switch/">Switch Statement</a><br>原文作者：<a href="https://golangbot.com/about/">Naveen Ramanathan</a><br>译文出自：<a href="https://blog.zisheng.pro/categories/%E6%B4%9B%E7%AB%B9%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/">紫升翻译计划</a></p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/b3d3163f4bf0/</id>
    <link href="https://blog.zisheng.pro/posts/b3d3163f4bf0/"/>
    <published>2023-03-19T07:10:00.000Z</published>
    <summary>switch 是一个条件语句，它对表达式进行求值并将其与可能的匹配列表进行比较，并执行相应的代码块。可以将其视为替换复杂的 if 语句的惯用方式。</summary>
    <title>Go 语言教程系列のSwitch 语句</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/categories/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/categories/Golang/"/>
    <category term="Go 语言系列教程" scheme="https://blog.zisheng.pro/categories/Golang/Go-%E8%AF%AD%E8%A8%80%E7%B3%BB%E5%88%97%E6%95%99%E7%A8%8B/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/tags/Golang/"/>
    <category term="Go 语言教程" scheme="https://blog.zisheng.pro/tags/Go-%E8%AF%AD%E8%A8%80%E6%95%99%E7%A8%8B/"/>
    <category term="Go 语言教程系列" scheme="https://blog.zisheng.pro/tags/Go-%E8%AF%AD%E8%A8%80%E6%95%99%E7%A8%8B%E7%B3%BB%E5%88%97/"/>
    <category term="Golang Loop" scheme="https://blog.zisheng.pro/tags/Golang-Loop/"/>
    <content>
      <![CDATA[<p>循环语句用于重复执行代码块。</p><p><code>for</code> 是 Go 中唯一可用的循环。Go 没有在其他语言（例如 C）中存在的<code>while</code> 或 <code>do while</code> 循环。</p><h2 id="for-循环语法"><a href="#for-循环语法" class="headerlink" title="for 循环语法"></a>for 循环语法</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> initialisation; condition; post &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>初始化语句将仅执行一次。循环初始化后，将检查条件。如果条件的计算结果为 <code>true</code>，则将执行 <code>{</code> <code>}</code> 内部的循环主体，然后执行 post 语句。 每次循环成功迭代后，将执行 post 语句。执行 post 语句后，将重新检查条件。如果为 <code>true</code>，则循环将继续执行，否则 <code>for</code> 循环终止。</p><p>Go 中的 for 循环的三个部分，即初始化，条件和发布都是可选的。让我们看一个示例，以更好地理解循环。</p><h2 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h2><p>让我们编写一个程序，该程序使用 <code>for</code> 循环打印从 1 到 10 的所有数字。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">for</span> i := <span class="number">1</span>; i &lt;= <span class="number">10</span>; i++ &#123;</span><br><span class="line">        fmt.Printf(<span class="string">&quot; %d&quot;</span>,i)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/mV6Zgcx2DY">Run in playground</a></p><p>在上面的程序中，<code>i</code> 被初始化为 1。条件语句将检查 <code>i &lt;= 10</code>。如果条件为真，则输出 <code>i</code> 的值，否则循环终止。在每次迭代结束时，post 语句将 <code>i</code> 递增 1。一旦 <code>i</code> 大于 10，则循环终止。</p><p>上面的程序将打印 <code>1 2 3 4 5 6 7 8 9 10</code></p><p>在 for 循环中声明的变量仅在循环范围内可用。因此，无法在主体外部进行循环访问 <code>i</code>。</p><h2 id="break"><a href="#break" class="headerlink" title="break"></a>break</h2><p><code>break</code> 语句用于在完成正常执行之前突然终止 <code>for</code> 循环，并将控制权移至 <code>for</code> 循环之后的代码行。</p><p>让我们编写一个使用 <code>break</code> 打印从 1 到 5 的数字的程序。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">for</span> i := <span class="number">1</span>; i &lt;= <span class="number">10</span>; i++ &#123;</span><br><span class="line">        <span class="keyword">if</span> i &gt; <span class="number">5</span> &#123;</span><br><span class="line">            <span class="keyword">break</span> <span class="comment">// 如果 i 大于 5，循环将终止</span></span><br><span class="line">        &#125;</span><br><span class="line">        fmt.Printf(<span class="string">&quot;%d &quot;</span>, i)</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Printf(<span class="string">&quot;\nline after for loop&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/sujKy92f--">Run in playground</a></p><p>在上面的程序中，在每次迭代过程中都会检查 <code>i</code> 值。如果 <code>i</code> 大于 5，则执行 <code>break</code> 并终止循环。然后在 <code>for</code> 循环之后执行 <code>print</code> 语句。上面的程序将输出：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">1 2 3 4 5</span><br><span class="line">line after <span class="keyword">for</span> loop</span><br></pre></td></tr></table></figure><h2 id="continue"><a href="#continue" class="headerlink" title="continue"></a>continue</h2><p><code>continue</code> 语句用于跳过 <code>for</code> 循环的当前迭代。在 <code>continue</code> 语句之后的 <code>for</code> 循环中存在的所有代码将不会针对当前迭代执行。循环将继续进行下一个迭代。</p><p>让我们编写一个程序，使用 <code>continue</code> 打印从 <code>1</code> 到 <code>10</code> 的所有奇数。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">for</span> i := <span class="number">1</span>; i &lt;= <span class="number">10</span>; i++ &#123;</span><br><span class="line">        <span class="keyword">if</span> i%<span class="number">2</span> == <span class="number">0</span> &#123;</span><br><span class="line">            <span class="keyword">continue</span></span><br><span class="line">        &#125;</span><br><span class="line">        fmt.Printf(<span class="string">&quot;%d &quot;</span>, i)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/DRLN2ZHwVS">Run in playground</a></p><p>在上面的程序中，<code>if i％2 == 0</code> 行检查将 <code>i</code> 除以 <code>2</code> 的提示是否为 0。如果为零，则数字为偶数，并执行 <code>continue</code> 语句，并且控制移至 循环的下一次迭代。 因此，<code>continue</code> 之后的 <code>print</code> 语句将不会被调用，并且循环会进行到下一个迭代。上面程序的输出是 <code>1 3 5 7 9</code>。</p><h2 id="嵌套循环"><a href="#嵌套循环" class="headerlink" title="嵌套循环"></a>嵌套循环</h2><p>一个内部有另一个 <code>for</code> 循环的 <code>for</code> 循环称为嵌套 <code>for</code> 循环。让我们通过编写一个打印以下序列的程序来了解嵌套的 <code>for</code> 循环。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">*</span><br><span class="line">**</span><br><span class="line">***</span><br><span class="line">****</span><br><span class="line">*****</span><br></pre></td></tr></table></figure><p>下面的程序使用嵌套的 <code>for</code> 循环来打印序列。第 8 行中的变量 <code>n</code> 存储序列中的行数。在我们的例子中是 5。外部 <code>for</code> 循环将 <code>i</code> 从 <code>0</code> 迭代到 <code>4</code>，内部 <code>for</code> 循环将 <code>j</code> 从 <code>0</code> 迭代到 <code>i</code> 的当前值。 内循环为每次迭代打印“ *”，而外循环在每次迭代结束时打印新行。 运行该程序，你会看到序列打印为输出。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    n := <span class="number">5</span></span><br><span class="line">    <span class="keyword">for</span> i := <span class="number">0</span>; i &lt; n; i++ &#123;</span><br><span class="line">        <span class="keyword">for</span> j := <span class="number">0</span>; j &lt;= i; j++ &#123;</span><br><span class="line">            fmt.Print(<span class="string">&quot;*&quot;</span>)</span><br><span class="line">        &#125;</span><br><span class="line">        fmt.Println()</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/0rq8fWjVDLb">Run in playground</a></p><h2 id="标签"><a href="#标签" class="headerlink" title="标签"></a>标签</h2><p>标签可用于从内部 <code>for</code> 循环中断外部循环。让我们通过一个简单的例子来理解我的意思。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">3</span>; i++ &#123;</span><br><span class="line">        <span class="keyword">for</span> j := <span class="number">1</span>; j &lt; <span class="number">4</span>; j++ &#123;</span><br><span class="line">            fmt.Printf(<span class="string">&quot;i = %d , j = %d\n&quot;</span>, i, j)</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/BnCKho2x5hM">Run in playground</a></p><p>上面的程序是不言自明的，它将打印：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">i = 0 , j = 1</span><br><span class="line">i = 0 , j = 2</span><br><span class="line">i = 0 , j = 3</span><br><span class="line">i = 1 , j = 1</span><br><span class="line">i = 1 , j = 2</span><br><span class="line">i = 1 , j = 3</span><br><span class="line">i = 2 , j = 1</span><br><span class="line">i = 2 , j = 2</span><br><span class="line">i = 2 , j = 3</span><br></pre></td></tr></table></figure><p>这没有什么特殊的！</p><p>如果我们想在 <code>i</code> 和 <code>j</code> 相等时停止打印该怎么办。为此，我们需要退出外部 <code>for</code> 循环。 当 <code>i</code> 和 <code>j</code> 相等时，在内部 <code>for</code> 循环中添加 <code>break</code> 只会退出内部 <code>for</code> 循环。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">3</span>; i++ &#123;</span><br><span class="line">        <span class="keyword">for</span> j := <span class="number">1</span>; j &lt; <span class="number">4</span>; j++ &#123;</span><br><span class="line">            fmt.Printf(<span class="string">&quot;i = %d , j = %d\n&quot;</span>, i, j)</span><br><span class="line">            <span class="keyword">if</span> i == j &#123;</span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/uMjbF8Ii41d">Run in playground</a></p><p>在上面的程序中，当第 10 行的 <code>i</code> 和 <code>j</code> 相等时，我在内部的 <code>for</code> 循环中添加了一个 <code>break</code>。这只会退出内部 <code>for</code> 循环，而外部循环将继续执行。该程序将打印：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">i = 0 , j = 1</span><br><span class="line">i = 0 , j = 2</span><br><span class="line">i = 0 , j = 3</span><br><span class="line">i = 1 , j = 1</span><br><span class="line">i = 2 , j = 1</span><br><span class="line">i = 2 , j = 2</span><br></pre></td></tr></table></figure><p>这不是预期的输出。当 <code>i</code> 和 <code>j</code> 相等时，即当它们等于 <code>1</code> 时，我们需要停止打印。标签可用于从外部循环中断开。让我们使用标签重写上面的程序：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">outer:</span><br><span class="line">    <span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">3</span>; i++ &#123;</span><br><span class="line">        <span class="keyword">for</span> j := <span class="number">1</span>; j &lt; <span class="number">4</span>; j++ &#123;</span><br><span class="line">            fmt.Printf(<span class="string">&quot;i = %d , j = %d\n&quot;</span>, i, j)</span><br><span class="line">            <span class="keyword">if</span> i == j &#123;</span><br><span class="line">                <span class="keyword">break</span> outer</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/BI10Rmp_Z3y">Run in playground</a></p><p>在上面的程序中，我们在外部 <code>for</code> 循环的第 8 行中添加了标签 <code>outer</code>，在第 13 行中，通过指定标签来中断外部 <code>for</code> 循环。当 <code>i</code> 和 <code>j</code> 相等时，该程序将停止打印。该程序将输出：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">i = 0 , j = 1</span><br><span class="line">i = 0 , j = 2</span><br><span class="line">i = 0 , j = 3</span><br><span class="line">i = 1 , j = 1</span><br></pre></td></tr></table></figure><h2 id="更多示例"><a href="#更多示例" class="headerlink" title="更多示例"></a>更多示例</h2><p>让我们写更多的代码来覆盖 <code>for</code> 循环的所有变体。下面的程序打印从 0 到 10 的所有偶数。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    i := <span class="number">0</span></span><br><span class="line">    <span class="keyword">for</span> ;i &lt;= <span class="number">10</span>; &#123; <span class="comment">// initialisation and post 被省略</span></span><br><span class="line">        fmt.Printf(<span class="string">&quot;%d &quot;</span>, i)</span><br><span class="line">        i += <span class="number">2</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/PNXliGINku">Run in playground</a></p><p>我们已经知道 <code>for</code> 循环的所有三个部分，即 <code>initialisation</code>、<code>condition</code> 和 <code>post</code> 都是可选的。在上述程序中，省略了<code>initialisation</code> 和 <code>post</code>。在 <code>for</code> 循环外，<code>i</code> 被初始化为 <code>0</code>。只要 <code>i &lt;= 10</code>，循环就会执行。我在 <code>for</code> 循环内增加了 <code>2</code>。上面的程序输出 <code>0 2 4 6 8 10</code>。</p><p>也可以省略上述程序的 <code>for</code> 循环中的分号。可以将这种格式视为 <code>while</code> 循环的替代方法。上面的程序可以改写为：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    i := <span class="number">0</span></span><br><span class="line">    <span class="keyword">for</span> i &lt;= <span class="number">10</span> &#123; <span class="comment">// 分号被省略，只剩下条件语句</span></span><br><span class="line">        fmt.Printf(<span class="string">&quot;%d &quot;</span>, i)</span><br><span class="line">        i += <span class="number">2</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/UYiz-Wtnoj">Run in playground</a></p><p>可以在 <code>for</code> 循环中声明多个变量并对其进行操作。让我们编写一个使用多个变量声明打印以下序列的程序。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">10 * 1 = 10</span><br><span class="line">11 * 2 = 22</span><br><span class="line">12 * 3 = 36</span><br><span class="line">13 * 4 = 52</span><br><span class="line">14 * 5 = 70</span><br><span class="line">15 * 6 = 90</span><br><span class="line">16 * 7 = 112</span><br><span class="line">17 * 8 = 136</span><br><span class="line">18 * 9 = 162</span><br><span class="line">19 * 10 = 190</span><br></pre></td></tr></table></figure><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">for</span> no, i := <span class="number">10</span>, <span class="number">1</span>; i &lt;= <span class="number">10</span> &amp;&amp; no &lt;= <span class="number">19</span>; i, no = i+<span class="number">1</span>, no+<span class="number">1</span> &#123;</span><br><span class="line">        fmt.Printf(<span class="string">&quot;%d * %d = %d\n&quot;</span>, no, i, no*i)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/e7Pf0UDjj0">Run in playground</a></p><p>在上面的程序中，<code>no</code> 和 <code>i</code> 被声明并分别初始化为 10 和 1。在每次迭代结束时，它们将增加 1。布尔运算符 <code>&amp;&amp;</code> 用于确保 <code>i</code> 小于或等于 10 以及 <code>no</code> 小于或等于 19 的条件成立。</p><h2 id="无限循环"><a href="#无限循环" class="headerlink" title="无限循环"></a>无限循环</h2><p>创建无限循环的语法是：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>以下程序将持续打印 <code>Hello World</code>，而不会终止。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">for</span> &#123;</span><br><span class="line">        fmt.Println(<span class="string">&quot;Hello World&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果尝试在 <a href="https://play.golang.org/p/kYQZw1AWT4">go park</a> 中运行上述程序，则会收到错误消息 <code>process took too long</code>。请尝试在你的本地系统中运行它以无限打印 <code>Hello World</code>。</p><p>还有一个范围 <strong>range</strong>可以在 <code>for</code> 循环中用于数组操作。当我们在后续教程中了解数组时，我们将进行介绍。</p><blockquote><p>原文地址 <a href="https://golangbot.com/loops/">loops</a><br>原文作者：<a href="https://golangbot.com/about/">Naveen Ramanathan</a><br>译文出自：<a href="https://blog.zisheng.pro/categories/%E6%B4%9B%E7%AB%B9%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/">紫升翻译计划</a></p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/1e53ff51d8a4/</id>
    <link href="https://blog.zisheng.pro/posts/1e53ff51d8a4/"/>
    <published>2023-03-19T07:09:00.000Z</published>
    <summary>Go 循环语句用于重复执行代码块。</summary>
    <title>Go 语言教程系列の循环</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/categories/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/categories/Golang/"/>
    <category term="Go 语言系列教程" scheme="https://blog.zisheng.pro/categories/Golang/Go-%E8%AF%AD%E8%A8%80%E7%B3%BB%E5%88%97%E6%95%99%E7%A8%8B/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/tags/Golang/"/>
    <category term="Go 语言教程" scheme="https://blog.zisheng.pro/tags/Go-%E8%AF%AD%E8%A8%80%E6%95%99%E7%A8%8B/"/>
    <category term="Go 语言教程系列" scheme="https://blog.zisheng.pro/tags/Go-%E8%AF%AD%E8%A8%80%E6%95%99%E7%A8%8B%E7%B3%BB%E5%88%97/"/>
    <category term="Golang if else" scheme="https://blog.zisheng.pro/tags/Golang-if-else/"/>
    <content>
      <![CDATA[<p><code>if</code> 是一个具有布尔条件的语句，如果该条件的值为 <code>true</code>，它将执行一个代码块。如果判定条件为 <code>false</code>，它将执行 <code>else</code> 代码块。在本教程中，我们将研究 <code>if</code> 语句的各种语法和使用方法。</p><h2 id="if-语句语法"><a href="#if-语句语法" class="headerlink" title="if 语句语法"></a><code>if</code> 语句语法</h2><p>下面的代码正是 <code>if</code> 语句的语法：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> condition &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果 <code>condition</code> 被判定为 <code>true</code>，大括号 <code>{</code> 和 <code>}</code> 之间的代码将被执行。</p><p>与其他语言（例如C、JavaScript）不同，Go 语言中大括号 <code>{}</code> 是必需的，即使大括号 <code>{}</code> 之间只有一行代码。</p><h2 id="Example"><a href="#Example" class="headerlink" title="Example"></a>Example</h2><p>让我们来写一个简单的程序来判断一个数字的奇偶性。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    num := <span class="number">10</span></span><br><span class="line">    <span class="keyword">if</span> num%<span class="number">2</span> == <span class="number">0</span> &#123; <span class="comment">// 检查数字是否是奇数</span></span><br><span class="line">        fmt.Println(<span class="string">&quot;The number&quot;</span>, num, <span class="string">&quot;is even&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Println(<span class="string">&quot;The number&quot;</span>, num, <span class="string">&quot;is odd&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/RRxkgK07ul4">Run in Playground</a></p><p>在上面的程序中，第9行的条件 <code>num％2</code> 确定 <code>num</code> 除以 <code>2</code> 的余数是否为零。由于在这种情况下为 <code>0</code>，所以将打印文本 <code>The number 10 is even</code>，然后程序退出。</p><h2 id="if-else-语句"><a href="#if-else-语句" class="headerlink" title="if...else... 语句"></a><code>if...else...</code> 语句</h2><p><code>if</code> 语句具有可选的 <code>else</code> 结构体，如果 <code>if</code> 语句中的条件被判定为 <code>false</code>，则将执行该结构体。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> condition &#123;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在上面的代码片段中，如果条件被判定为 <code>false</code>，那么将执行 <code>else {</code>和 <code>}</code> 之间的代码。</p><p>让我们重写程序以使用 <code>if else</code> 语句判断数字的奇偶性。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    num := <span class="number">11</span></span><br><span class="line">    <span class="keyword">if</span> num%<span class="number">2</span> == <span class="number">0</span> &#123; <span class="comment">// 检查数字是否是奇数</span></span><br><span class="line">        fmt.Println(<span class="string">&quot;the number&quot;</span>, num, <span class="string">&quot;is even&quot;</span>)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        fmt.Println(<span class="string">&quot;the number&quot;</span>, num, <span class="string">&quot;is odd&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/bMevwhJhguO">Run in playground</a></p><p>在上面的代码中，我们没有像上一节那样返回条件是否为 <code>true</code>，而是创建了 <code>else</code> 语句，如果条件为 <code>false</code> 则执行该语句。在这种情况下，由于 <code>11</code> 为奇数，因此 <code>if</code> 条件为 <code>false</code> 并执行 <code>else</code> 语句中的代码。上面的程序将打印。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">the number 11 is odd</span><br></pre></td></tr></table></figure><h2 id="if-else-if-else-语句"><a href="#if-else-if-else-语句" class="headerlink" title="if...else if...else... 语句"></a><code>if...else if...else...</code> 语句</h2><p><code>if</code> 语句还具有可选的 <code>else if</code> 和 <code>else</code> 组件。下面提供了相同的语法</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> condition1 &#123;</span><br><span class="line">...</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> condition2 &#123;</span><br><span class="line">...</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>该语法用来从上到下来查找问题的真相。</p><p>在上面的语句中，如果 <code>condition1</code> 为 <code>true</code>，则执行 <code>if condition1 {</code> 和右括号 <code>}</code> 中的代码。</p><p>如果 <code>condition1</code> 是 <code>false</code>，而 <code>condition2</code> 是 <code>true</code>，则执行 <code>condition2 {</code> 和下一个右括号 <code>}</code> 的其他代码。</p><p>如果 <code>condition1</code> 和 <code>condition2</code> 均为假，则执行 <code>else {</code> 和 <code>}</code> 之间 <code>else</code> 语句中的代码。</p><p>可以有任意数量的 <code>else if</code> 语句。</p><p>通常，无论条件是 <code>if</code> 还是 <code>else if</code>，都将执行相应的代码块。如果没有一个条件为真，则执行 <code>else</code> 块。</p><p>让我们编写一个使用 <code>else if</code> 的程序。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    num := <span class="number">99</span></span><br><span class="line">    <span class="keyword">if</span> num &lt;= <span class="number">50</span> &#123;</span><br><span class="line">        fmt.Println(num, <span class="string">&quot;is less than or equal to 50&quot;</span>)</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> num &gt;= <span class="number">51</span> &amp;&amp; num &lt;= <span class="number">100</span> &#123;</span><br><span class="line">        fmt.Println(num, <span class="string">&quot;is between 51 and 100&quot;</span>)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        fmt.Println(num, <span class="string">&quot;is greater than 100&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/VNPbCiK9eXT">Run in playground</a></p><p>在上面的程序中，第 11 行的条件 <code>else if num &gt;= 51 &amp;&amp; num &lt;= 100</code> 为 <code>true</code>，因此程序将打印：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">99 is between 51 and 100</span><br></pre></td></tr></table></figure><h2 id="带有赋值语句的-if"><a href="#带有赋值语句的-if" class="headerlink" title="带有赋值语句的 if"></a>带有赋值语句的 <code>if</code></h2><p><code>if</code> 的另一种变体，包括可选的短赋值语句，该语句在判定条件之前执行。它的语法是：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> assignment-statement; condition &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在上面的代码段中，在判定条件之前首先执行 <code>assignment-statement</code>。</p><p>让我们使用上面的语法重写判断数字奇偶性的程序。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">if</span> num := <span class="number">10</span>; num % <span class="number">2</span> == <span class="number">0</span> &#123; <span class="comment">//checks if number is even</span></span><br><span class="line">        fmt.Println(num,<span class="string">&quot;is even&quot;</span>)</span><br><span class="line">    &#125;  <span class="keyword">else</span> &#123;</span><br><span class="line">        fmt.Println(num,<span class="string">&quot;is odd&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/_X9q4MWr4s">Run in playground</a></p><p>在上面的程序中，在第 8 行的 <code>if</code> 语句中初始化了 <code>num</code>。需要注意的一件事是 <code>num</code> 仅可用于 <code>if</code> 和 <code>else</code> 内部的访问。即 <code>num</code> 的有效范围仅限于 <code>if</code> 块。 如果我们尝试从 <code>if</code> 或 <code>else</code> 外部访问 <code>num</code>，则编译器会报错。当我们仅出于 <code>if else</code> 构造的目的声明变量时，这种语法通常会派上用场。在这种情况下使用此语法可确保变量的范围仅在 <code>if else</code> 语句内。</p><h2 id="陷阱"><a href="#陷阱" class="headerlink" title="陷阱"></a>陷阱</h2><p><code>else</code> 语句应在 <code>if</code> 语句的大括号 <code>}</code> 之后的同一行开始。如果没有，编译器会抛错。</p><p>让我们通过一个程序来理解这一点。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    num := <span class="number">10</span></span><br><span class="line">    <span class="keyword">if</span> num % <span class="number">2</span> == <span class="number">0</span> &#123; <span class="comment">//checks if number is even</span></span><br><span class="line">        fmt.Println(<span class="string">&quot;the number is even&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span> &#123;</span><br><span class="line">        fmt.Println(<span class="string">&quot;the number is odd&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/RYNqZZO2F9">Run in playground</a></p><p>在上面的程序中，在第 11 行的 <code>if</code> 语句的结束符 <code>}</code> 之后，<code>else</code> 语句不在同一行开始。而是从下一行开始。 Go 不允许这样做。如果你运行此程序，则编译器将输出错误，</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./prog.go:12:5: syntax error: unexpected <span class="keyword">else</span>, expecting &#125;</span><br></pre></td></tr></table></figure><p>原因是因为 Go 自动插入分号的方式。你可以在 <a href="https://golang.org/ref/spec#Semicolons">Semicolons</a> 中阅读有关分号插入规则的信息。</p><p>在规则中，指定了在结束括号 <code>}</code> 之后插入分号（如果这是该行的最后一个标记）。因此，Go 编译器在第 11 行的 <code>if</code> 语句的右括号 <code>}</code> 之后自动插入了分号。</p><p>所以我们的程序实际上变成了：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line"><span class="keyword">if</span> num%<span class="number">2</span> == <span class="number">0</span> &#123;</span><br><span class="line">      fmt.Println(<span class="string">&quot;the number is even&quot;</span>)</span><br><span class="line">&#125;;  <span class="comment">// Go 编译器自动插入分号</span></span><br><span class="line"><span class="keyword">else</span> &#123;</span><br><span class="line">      fmt.Println(<span class="string">&quot;the number is odd&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>编译器将在以上代码片段的第 4 行中插入分号。</p><p>由于 <code>if {...} else {...}</code> 是 一个单独的语句，因此中间不应使用分号。因此，该程序无法编译。 在语法上需要将 <code>else</code> 放在 <code>if</code> 语句的右括号 <code>}</code> 之后的同一行中。</p><p>我已经通过在 <code>if</code> 语句的结束符号 <code>}</code> 之后移动 <code>else</code> 来重写程序，以防止自动分号插入。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    num := <span class="number">10</span></span><br><span class="line">    <span class="keyword">if</span> num%<span class="number">2</span> == <span class="number">0</span> &#123; <span class="comment">// 检查数字是否是奇数</span></span><br><span class="line">        fmt.Println(<span class="string">&quot;the number is even&quot;</span>)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        fmt.Println(<span class="string">&quot;the number is odd&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/hv_27vbIBC">Run in playground</a></p><p>现在编译器会很高兴，我们也很高兴。</p><h2 id="Go-惯例"><a href="#Go-惯例" class="headerlink" title="Go 惯例"></a>Go 惯例</h2><p>我们已经看到了各种 <code>if-else</code> 构造，并且实际上已经看到了编写同一程序的多种方法。例如，我们已经看到了编写程序的多种方法，该程序使用不同的 <code>if</code> 构造检查数字是偶数还是奇数。Go 中的惯用编码方式是哪一种？ 按照 Go 的哲学，最好避免不必要的分支和代码缩进。也可以理解为尽早返回。我已经从下面的上一节中提供了该程序，</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">if</span> num := <span class="number">10</span>; num % <span class="number">2</span> == <span class="number">0</span> &#123; <span class="comment">// 检查数字是否是奇数</span></span><br><span class="line">        fmt.Println(num,<span class="string">&quot;is even&quot;</span>)</span><br><span class="line">    &#125;  <span class="keyword">else</span> &#123;</span><br><span class="line">        fmt.Println(num,<span class="string">&quot;is odd&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/_X9q4MWr4s">Run in playground</a></p><p>按照 Go 的哲学，编写上述程序的惯用方式是避免 <code>else</code> 并在条件为 <code>true</code> 时从 <code>if</code> 返回。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    num := <span class="number">10</span>;</span><br><span class="line">    <span class="keyword">if</span> num%<span class="number">2</span> == <span class="number">0</span> &#123; <span class="comment">// 检查数字是否是奇数</span></span><br><span class="line">        fmt.Println(num, <span class="string">&quot;is even&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Println(num, <span class="string">&quot;is odd&quot;</span>)</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/N8A5tPLnVYr">Run in playground</a></p><p>在上面的程序中，一旦我们发现数字是偶数，我们将立即返回。这样可以避免不必要的 <code>else</code> 代码分支。这是 Go 中完成工作的方式。每当编写 Go 程序时，请记住这一点。</p><blockquote><p>原文地址 <a href="https://golangbot.com/if-statement/">If else statement</a><br>原文作者：<a href="https://golangbot.com/about/">Naveen Ramanathan</a><br>译文出自：<a href="https://blog.zisheng.pro/categories/%E6%B4%9B%E7%AB%B9%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/">紫升翻译计划</a></p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/4176036c2d35/</id>
    <link href="https://blog.zisheng.pro/posts/4176036c2d35/"/>
    <published>2023-03-19T07:08:00.000Z</published>
    <summary>if 是一个具有布尔条件的语句，如果该条件的值为 true，它将执行一个代码块。如果判定条件为 false，它将执行 else 代码块。在本教程中，我们将研究 if 语句的各种语法和使用方法。</summary>
    <title>Go 语言教程系列のif else 语句</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/categories/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/categories/Golang/"/>
    <category term="Go 语言系列教程" scheme="https://blog.zisheng.pro/categories/Golang/Go-%E8%AF%AD%E8%A8%80%E7%B3%BB%E5%88%97%E6%95%99%E7%A8%8B/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/tags/Golang/"/>
    <category term="Go 语言教程" scheme="https://blog.zisheng.pro/tags/Go-%E8%AF%AD%E8%A8%80%E6%95%99%E7%A8%8B/"/>
    <category term="Go 语言教程系列" scheme="https://blog.zisheng.pro/tags/Go-%E8%AF%AD%E8%A8%80%E6%95%99%E7%A8%8B%E7%B3%BB%E5%88%97/"/>
    <category term="Golang 包" scheme="https://blog.zisheng.pro/tags/Golang-%E5%8C%85/"/>
    <content>
      <![CDATA[<h2 id="什么是包（package），为什么使用它们？"><a href="#什么是包（package），为什么使用它们？" class="headerlink" title="什么是包（package），为什么使用它们？"></a>什么是包（package），为什么使用它们？</h2><p>到目前为止，我们已经看到 Go 程序只有一个带有 <code>main</code> 函数的文件，以及几个其他函数。在实际情况下，这种将所有源代码写入单个文件的方法是不可扩展的。重用和维护以此方式编写的代码几乎是不可能。这时就需要用到包的概念。</p><p><strong>包用于组织 Go 源代码，以提高可重用性和可读性。包是位于同一目录中的 Go 源文件的集合。包提供了代码分隔，因此我们可以轻松维护 Go 项目。</strong></p><p>例如，假设我们正在 Go 中编写财务应用程序，其中一些功能是单利计算、复利计算和贷款计算。组织此应用程序的一种简单方法是根据功能。我们可以创建包 <code>simpleinterest</code>、<code>compoundinterest</code> 和 <code>loan</code>。如果 <code>loan</code> 包需要计算单利，则可以通过导入 <code>simpleinterest</code> 包来简单地进行计算。这样，代码就可以重用。</p><p>我们将通过创建一个简单的应用程序来学习包，以确定给定的本金、利率和持续时间（以年为单位）的单利。</p><h2 id="main-函数和-main-包"><a href="#main-函数和-main-包" class="headerlink" title="main 函数和 main 包"></a>main 函数和 main 包</h2><p>每个可执行的 Go 应用程序都必须包含 <code>main</code> 函数。此函数是执行的入口点。<code>main</code> 函数应保留在主包中。</p><p><code>package packagename</code> 指定一个指向 <code>packagename</code> 包的特定源文件。这在每一个 go 源文件中都应该放在第一行。</p><p>接下来让我们从为应用创建 <code>main</code> 函数和 <code>main</code> 包开始吧。</p><p>执行下面的命令在 <code>Desktop</code> 文件夹下创建一个名为 <code>lernapackage</code> 的文件。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> ~/Desktop/learnpackage/</span><br></pre></td></tr></table></figure><p>在 <code>learnpackage</code> 目录下创建一个 <code>main.go</code> 文件，然后写入一下内容：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;Simple interest calculation&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>package main</code> 表示该文件属于 <code>main</code> 包，<code>import &quot;packagename&quot;</code> 语句被用来导入一个已经存在的包。<code>packagename.FunctionName()</code> 是调用包中函数的语法。</p><p>在第 3 行中，为了使用 <code>Println</code> 函数我们导入了 <code>fmt</code> 包。<code>fmt</code> 是一个 Go 的标准库并且是内置可用的。然后是打印 <code>Simple interest calculation</code> 的 <code>main</code> 函数。</p><p>进入 <code>learnpackage</code> 目录并使用下面的命令编译上面的项目：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> ~/Desktop/learnpackage/</span><br></pre></td></tr></table></figure><p>然后输入下面的初始化 <code>mod</code>：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">go mod init learnpackage</span><br></pre></td></tr></table></figure><p>然后尝试输入下面的命令：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">go install</span><br></pre></td></tr></table></figure><p>如果一切顺利，我们的二进制文件将被编译并准备执行。在终端中输入命令 <code>learnpackage</code>，你将看到以下输出：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Simple interest calculation</span><br></pre></td></tr></table></figure><h2 id="Go-模块（Module）"><a href="#Go-模块（Module）" class="headerlink" title="Go 模块（Module）"></a>Go 模块（Module）</h2><p>我们将以这样的方式构造代码，使所有与单利相关的功能都在 <code>simpleinterest</code> 包中。为此，我们需要创建一个自定义包 <code>simpleinterest</code>，其中将包含用于计算单利的函数。在创建自定义包之前，我们首先需要理解 Go 模块，因为创建自定义 packages 需要用到 <strong>Go 模块</strong>。</p><p><strong>Go 模块不过是 Go 软件包的集合。<strong>现在你可能会想到一个问题：为什么我们需要 Go 模块来创建自定义包？答案是</strong>我们创建的自定义包的导入路径是从 go 模块的名称获得的</strong>。除此之外，我们的应用程序使用的所有其他第三方软件包（例如来自 github 的 源代码）将与版本一起出现在 <code>go.mod</code> 文件中。这个 <code>go.mod</code> 文件是在我们创建一个新模块时创建的。在下一部分中，你将更好地理解这一点。</p><h2 id="创建一个单利自定义包"><a href="#创建一个单利自定义包" class="headerlink" title="创建一个单利自定义包"></a>创建一个单利自定义包</h2><p><strong>属于包的源文件应放置在各自的单独文件夹中。Go 中的惯例是使用与包相同的名称来命名此文件夹。</strong></p><p>让我们在 <code>learnpackage</code> 文件夹内创建一个名为 <code>simpleinterest</code> 的文件夹。<code>mkdir simpleinterest</code> 将为我们创建此文件夹。</p><p><code>simpleinterest</code> 文件夹中的所有文件均应以 <code>package simpleinterest</code> 开头，因为它们都属于 <code>simpleinterest</code> 软件包。</p><p>在 <code>simpleinterest</code> 文件夹内创建一个文件 <code>simpleinterest.go</code>。</p><p>以下将是我们应用程序的目录结构。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">├── learnpackage</span><br><span class="line">│   ├── go.mod</span><br><span class="line">│   ├── main.go</span><br><span class="line">│   └── simpleinterest</span><br><span class="line">│       └── simpleinterest.go</span><br></pre></td></tr></table></figure><p>添加下面的代码到 <code>simpleinterest.go</code> 文件中。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> simpleinterest</span><br><span class="line"></span><br><span class="line"><span class="comment">// 计算并返回本金 p 的单利，持续时间 t 年的利率 r</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Calculate</span><span class="params">(p <span class="type">float64</span>, r <span class="type">float64</span>, t <span class="type">float64</span>)</span></span> <span class="type">float64</span> &#123;</span><br><span class="line">    interest := p * (r / <span class="number">100</span>) * t</span><br><span class="line">    <span class="keyword">return</span> interest</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在上面的代码中，我们创建了一个函数 <code>Calculate</code>，该函数计算并返回单利。此功能不言自明，它计算并返回单利。</p><p>请注意，函数名称 <strong>Calculate</strong> 以大写字母开头。这是必不可少的，我们后边会解释为什么需要这样做。</p><h2 id="导入自定义包"><a href="#导入自定义包" class="headerlink" title="导入自定义包"></a>导入自定义包</h2><p>要使用自定义包，我们必须首先将其导入。导入路径是模块的名称，该名称后跟的是程序包的子目录和程序包名称。在我们的例子中，模块名称为 <code>learnpackage</code>，而软件包 <code>simpleinterest</code> 位于 <code>learnpackage</code> 下的 <code>simpleinterest</code> 文件夹中。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">├── learnpackage</span><br><span class="line">│   └── simpleinterest</span><br></pre></td></tr></table></figure><p>因此 <code>import &quot;learnpackage/simpleinterest&quot;</code> 这行代码将会引入 simpleinterest 包。</p><p>如果我们有这样的目录结构</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">learnpackage</span><br><span class="line">│   └── finance</span><br><span class="line">│       └── simpleinterest</span><br></pre></td></tr></table></figure><p>导入语句则应该是 <code>import &quot;learnpackage/finance/simpleinterest&quot;</code></p><p>在 <code>main.go</code> 文件中添加相应代码：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">    <span class="string">&quot;learnpackage/simpleinterest&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;Simple interest calculation&quot;</span>)</span><br><span class="line">    p := <span class="number">5000.0</span></span><br><span class="line">    r := <span class="number">10.0</span></span><br><span class="line">    t := <span class="number">1.0</span></span><br><span class="line">    si := simpleinterest.Calculate(p, r, t)</span><br><span class="line">    fmt.Println(<span class="string">&quot;Simple interest is&quot;</span>, si)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面的代码导入 <code>simpleinterest</code> 包，并使用 <code>Calculate</code> 函数计算单利。标准库中的软件包不需要模块名称前缀，因此 <code>fmt</code> 可以在没有模块前缀的情况下工作。当应用程序运行时，输出将是：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Simple interest calculation</span><br><span class="line">Simple interest is 500</span><br></pre></td></tr></table></figure><h2 id="导出名称"><a href="#导出名称" class="headerlink" title="导出名称"></a>导出名称</h2><p>我们在单利包中将函数 <code>Calculate</code> 大写。这在 Go 中具有特殊含义。 任何以大写字母开头的变量或函数都会在 go 中导出名称。在 Go 中只能从其他程序包访问导出的函数和变量。在我们的例子中，我们想从主程序包访问 <code>Calculate</code> 函数。 因此，这是大写的。</p><p>如果在 <code>simpleinterest.go</code> 中将函数名称从 <code>Calculate</code> 更改为 <code>calculate</code>，并且如果我们尝试在 <code>main.go</code> 中使用<code>simpleinterest.calculate(p，r，t)</code> 调用函数，则编译器将报错：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># learnpackage</span><br><span class="line">./main.go:13:8: undefined: simpleinterest.Calculate</span><br></pre></td></tr></table></figure><p>因此，如果要访问包外部的函数，应该将其大写。</p><h2 id="init-函数"><a href="#init-函数" class="headerlink" title="init 函数"></a><code>init</code> 函数</h2><p>Go 中的每个包都可以包含一个 <code>init</code> 函数。<code>init</code> 函数不能有任何返回类型，也不能有任何参数。在我们的源代码中不能显式调用 <code>init</code> 函数。程序包初始化时将自动调用它。初始化函数的语法：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">init</span><span class="params">()</span></span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>init</code> 函数可用于执行初始化任务，也可用于在执行开始之前验证程序的正确性。包的初始化顺序如下：</p><ol><li>首先对包级的变量进行初始化</li><li>接下来调用 <code>init</code> 函数。包可以具有多个 <code>init</code> 函数（在单个文件中或分布在多个文件中），并且按将其呈现给编译器的顺序进行调用。</li></ol><p>如果一个包导入了其他包，则首先初始化导入的软件包。</p><p>一个包即使从多个包中导入，也只会被初始化一次。</p><p>让我们对应用程序进行一些修改，以了解 <code>init</code> 函数。</p><p>首先，让我们将 <code>init</code> 函数添加到 <code>simpleinterest.go</code> 文件中。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> simpleinterest</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * 添加 init 函数</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">init</span><span class="params">()</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;Simple interest package initialized&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// Calculate 计算并返回本金 p 的单利，持续时间 t 年的利率 r</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Calculate</span><span class="params">(p <span class="type">float64</span>, r <span class="type">float64</span>, t <span class="type">float64</span>)</span></span> <span class="type">float64</span> &#123;</span><br><span class="line">    interest := p * (r / <span class="number">100</span>) * t</span><br><span class="line">    <span class="keyword">return</span> interest</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们添加了一个简单的 <code>init</code> 函数，该函数仅打印 已初始化的单利包</p><p>现在，让我们修改主程序包。我们知道，在计算单利时，本金、利率和持续时间应大于零。我们将使用 <code>init</code> 函数和 <code>main.go</code> 文件中的包级别变量定义此检查。</p><p>将 <code>main.go</code> 修改为以下内容：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">    <span class="string">&quot;learnpackage/simpleinterest&quot;</span> <span class="comment">// 导入自定义的包</span></span><br><span class="line">    <span class="string">&quot;log&quot;</span></span><br><span class="line">)</span><br><span class="line"><span class="keyword">var</span> p, r, t = <span class="number">5000.0</span>, <span class="number">10.0</span>, <span class="number">1.0</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">* init 函数检查 p, r 和 t 是否大于 0</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">init</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="built_in">println</span>(<span class="string">&quot;Main package initialized&quot;</span>)</span><br><span class="line">    <span class="keyword">if</span> p &lt; <span class="number">0</span> &#123;</span><br><span class="line">        log.Fatal(<span class="string">&quot;Principal is less than zero&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> r &lt; <span class="number">0</span> &#123;</span><br><span class="line">        log.Fatal(<span class="string">&quot;Rate of interest is less than zero&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> t &lt; <span class="number">0</span> &#123;</span><br><span class="line">        log.Fatal(<span class="string">&quot;Duration is less than zero&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    fmt.Println(<span class="string">&quot;Simple interest calculation&quot;</span>)</span><br><span class="line">    si := simpleinterest.Calculate(p, r, t)</span><br><span class="line">    fmt.Println(<span class="string">&quot;Simple interest is&quot;</span>, si)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>以下是对 <code>main.go</code> 所做的更改</p><ol><li><strong>p</strong>、<strong>r</strong> 和 ** t ** 变量从 <code>main</code> 函数级移至包级的。 2.增加了 <code>init</code> 函数。如果本金、利率或持续时间小于零，则使用 <code>init</code> 函数打印日志并终止程序执行。</li></ol><p>初始化的顺序如下：</p><ol><li>首先初始化导入的软件包。因此，首先初始化 <code>simpleinterest</code> 包，然后调用它的 <code>init</code> 方法。</li><li>接下来初始化包级别变量 <code>p</code>，<code>r</code> 和 <code>t</code>。</li><li><code>init</code> 函数在 <code>main</code> 中调用。 4.最后调用 <code>main</code> 函数。</li></ol><p>如果运行该程序，将得到以下输出：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Simple interest package initialized</span><br><span class="line">Main package initialized</span><br><span class="line">Simple interest calculation</span><br><span class="line">Simple interest is 500</span><br></pre></td></tr></table></figure><p>如预期的那样，首先调用 <code>simpleinterest</code> 包的 <code>init</code> 函数，然后初始化包级变量 <code>p</code>，<code>r</code> 和 <code>t</code>。接下来调用 <code>main</code> 包的 <code>init</code> 函数。它检查<code>p</code>、<code>r</code> 和 <code>t</code> 是否小于零，并在条件为真时终止。现在你可以假定 <code>if p &lt;0</code> 将检查 <code>p</code> 是否小于 <code>0</code>，如果小于<code>0</code>，则程序将终止。我们为 <code>r</code> 和 <code>t</code> 写了类似的条件。在这种情况下，所有这些条件都是假的，程序将继续执行。最后，调用 <code>main</code> 函数。</p><p>让我们对该程序进行一些修改以学习 <code>init</code> 函数的用法。修改 <code>main.go</code> 这一行</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> p, r, t = <span class="number">5000.0</span>, <span class="number">10.0</span>, <span class="number">1.0</span></span><br></pre></td></tr></table></figure><p>为</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> p, r, t = <span class="number">-5000.0</span>, <span class="number">10.0</span>, <span class="number">1.0</span></span><br></pre></td></tr></table></figure><p>我们已经将 <code>p</code> 初始化为负数。</p><p>现在，如果你运行该应用程序，你将看到</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Simple interest package initialized</span><br><span class="line">Main package initialized</span><br><span class="line">2020/02/15 21:25:12 Principal is less than zero</span><br></pre></td></tr></table></figure><p><code>p</code> 是负数。因此，当 <code>init</code> 函数运行时，程序在打印 <code>Principal is less than zero</code> 后终止。</p><h2 id="空白标识符的使用"><a href="#空白标识符的使用" class="headerlink" title="空白标识符的使用"></a>空白标识符的使用</h2><p>在 Go 中导入包却不使用是不合法的。如果这样做，编译器会抱怨。这样设计的原因是避免使未使用的包越来越多，这会大大增加编译时间。用以下内容替换 <code>main.go</code> 中的代码，</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;learnpackage/simpleinterest&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面的程序将会抛错：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&quot;learnpackage/simpleinterest&quot; imported but not used</span><br></pre></td></tr></table></figure><p>但是，在应用程序处于开发阶段中时，导入包并随后在代码中的某个位置（如果不是现在）使用它们是很常见的。在这种情况下，<code>_</code> 空白标识符可以为我们节省时间。</p><p>上面的程序中的错误可以通过以下代码消除：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">  <span class="string">&quot;learnpackage/simpleinterest&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> _ = simpleinterest.Calculate</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>var _ = simpleinterest.Calculate</code> 使错误静音了。如果不使用包，我们应该跟踪这些错误消音器，并在应用程序开发结束时删除它们，包括导入的包。因此，建议在 <code>import</code> 语句之后在包级别编写错误消音器。</p><p>有时，即使我们不需要使用包中的任何函数或变量，也需要导入包以确保初始化发生。例如，即使我们计划在代码中的任何地方都不使用该包，我们可能仍需要确保调用 <code>simpleinterest</code> 包的 <code>init</code> 函数。空白标识符 <code>_</code> 也可以在这种情况下使用，如下所示。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    _ <span class="string">&quot;learnpackage/simpleinterest&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>运行以上程序将输出 <code>Simple interest package initialized</code>。我们已经成功地初始化了 <code>simpleinterest</code> 包，即使在代码中的任何地方都没有使用它。</p><p>在第 13 行中，我们仅使用 <code>area</code>，而使用 <code>_</code> 标识符来丢弃 <code>perimeter</code>。</p><blockquote><p>原文地址 <a href="https://golangbot.com/go-packages/">Go Packages</a><br>原文作者：<a href="https://golangbot.com/about/">Naveen Ramanathan</a><br>译文出自：<a href="https://blog.zisheng.pro/categories/%E6%B4%9B%E7%AB%B9%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/">紫升翻译计划</a></p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/588813e2ac19/</id>
    <link href="https://blog.zisheng.pro/posts/588813e2ac19/"/>
    <published>2023-03-19T07:07:00.000Z</published>
    <summary>包用于组织 Go 源代码，以提高可重用性和可读性。包是位于同一目录中的 Go 源文件的集合。包提供了代码分隔，因此我们可以轻松维护 Go 项目。</summary>
    <title>Go 语言教程系列の包</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/categories/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/categories/Golang/"/>
    <category term="Go 语言系列教程" scheme="https://blog.zisheng.pro/categories/Golang/Go-%E8%AF%AD%E8%A8%80%E7%B3%BB%E5%88%97%E6%95%99%E7%A8%8B/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/tags/Golang/"/>
    <category term="Go 语言教程" scheme="https://blog.zisheng.pro/tags/Go-%E8%AF%AD%E8%A8%80%E6%95%99%E7%A8%8B/"/>
    <category term="Go 语言教程系列" scheme="https://blog.zisheng.pro/tags/Go-%E8%AF%AD%E8%A8%80%E6%95%99%E7%A8%8B%E7%B3%BB%E5%88%97/"/>
    <category term="Golang 函数" scheme="https://blog.zisheng.pro/tags/Golang-%E5%87%BD%E6%95%B0/"/>
    <content>
      <![CDATA[<h2 id="函数是什么？"><a href="#函数是什么？" class="headerlink" title="函数是什么？"></a>函数是什么？</h2><p>函数是执行特定任务的代码块。一个函数接受一个输入，对该输入执行一些计算，然后生成一个输出。</p><h2 id="函数声明"><a href="#函数声明" class="headerlink" title="函数声明"></a>函数声明</h2><p>Go 语言声明一个函数的语法是：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">functionname</span><span class="params">(parametername <span class="keyword">type</span>)</span></span> returntype &#123;</span><br><span class="line"> <span class="comment">// 函数体</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>函数声明以关键字 <code>func</code> 开头，后跟 <code>functionname</code>。参数在函数 <code>(</code> 和 <code>)</code> 之间指定，然后跟着的是 函数的 <code>returntype</code>。指定参数的语法是参数名称后跟类型。可以指定任意数量的参数，例如 <code>(parameter1 type，parameter2 type)</code>。然后在 <code>{</code> 和 <code>}</code> 之间有一个代码块，它是函数的主体。</p><p>参数和返回类型在函数中是可选的。因此，以下语法也是有效的函数声明。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">functionname</span><span class="params">()</span></span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h2><p>让我们编写一个函数，该函数将单个产品的价格和产品数量作为输入参数，并通过将这两个值相乘来计算总价格并返回输出。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">calculateBill</span><span class="params">(price <span class="type">int</span>, no <span class="type">int</span>)</span></span> <span class="type">int</span> &#123;</span><br><span class="line">    <span class="keyword">var</span> totalPrice = price * no</span><br><span class="line">    <span class="keyword">return</span> totalPrice</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面的函数有两个输入参数 <code>int</code> 类型的 <code>price</code> 和 <code>no</code>，并返回 <code>price</code> 和 <code>no</code> 的乘积 <code> totalPrice</code>。返回值也是 <code>int</code> 类型。</p><p><strong>如果连续参数具有相同的类型，我们可以不用重复书写该类型，事实上你只需要写一次就足够了。</strong> 因此，上述功能可以重写为：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">calculateBill</span><span class="params">(price, no <span class="type">int</span>)</span></span> <span class="type">int</span> &#123;</span><br><span class="line">    <span class="keyword">var</span> totalPrice = price * no</span><br><span class="line">    <span class="keyword">return</span> totalPrice</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>现在我们已经准备好一个函数，让我们从代码中的某个地方调用它。 调用函数的语法是 <code>functionname(parameters)</code>。现在我们可以使用代码来调用上述函数。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">calculateBill(<span class="number">10</span>, <span class="number">5</span>)</span><br></pre></td></tr></table></figure><p>这是使用上述函数并打印总价格的完整程序。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">calculateBill</span><span class="params">(price, no <span class="type">int</span>)</span></span> <span class="type">int</span> &#123;</span><br><span class="line">    <span class="keyword">var</span> totalPrice = price * no</span><br><span class="line">    <span class="keyword">return</span> totalPrice</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    price, no := <span class="number">90</span>, <span class="number">6</span></span><br><span class="line">    totalPrice := calculateBill(price, no)</span><br><span class="line">    fmt.Println(<span class="string">&quot;Total price is&quot;</span>, totalPrice)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/FtjhPcx3ySa">Run in playground</a></p><p>上面的程序将打印出以下内容：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Total price is 540</span><br></pre></td></tr></table></figure><h2 id="多个返回值"><a href="#多个返回值" class="headerlink" title="多个返回值"></a>多个返回值</h2><p>可以从一个函数返回多个值。让我们编写一个函数 <code>rectProps</code>，该函数接受矩形的 <code>length</code> 和 <code>width</code>，并返回矩形的 <code>area</code> 和 <code>perimeter</code>。矩形的面积是长度和宽度的乘积，周长是长度和宽度的总和的两倍。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">rectProps</span><span class="params">(length, width <span class="type">float64</span>)</span></span>(<span class="type">float64</span>, <span class="type">float64</span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> area = length * width</span><br><span class="line">    <span class="keyword">var</span> perimeter = (length + width) * <span class="number">2</span></span><br><span class="line">    <span class="keyword">return</span> area, perimeter</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    area, perimeter := rectProps(<span class="number">10.8</span>, <span class="number">5.6</span>)</span><br><span class="line">    fmt.Printf(<span class="string">&quot;Area %f Perimeter %f&quot;</span>, area, perimeter)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/qAftE_yke_">Run in playground</a></p><p>如果一个函数返回多个返回值，那么必须在 <code>(</code> 和 <code>)</code> 之间指定它们的类型。 <code>func rectProps(length, width float64)(float64,float64)</code> 具有两个<code>float64</code> 参数 <code>length</code> 和 <code>width</code>，并且还返回两个 <code>float64</code> 值。上面的程序会打印出：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Area 60.480000 Perimeter 32.800000</span><br></pre></td></tr></table></figure><h2 id="命名返回值"><a href="#命名返回值" class="headerlink" title="命名返回值"></a>命名返回值</h2><p>可以从函数返回命名值。如果已命名返回值，则可以将其视为在函数的第一行中声明为变量。</p><p>上面的 <code>rectProps</code> 函数可以使用命名的返回值重写为</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">rectProps</span><span class="params">(length, width <span class="type">float64</span>)</span></span>(area, perimeter <span class="type">float64</span>) &#123;</span><br><span class="line">    area = length * width</span><br><span class="line">    perimeter = (length + width) * <span class="number">2</span></span><br><span class="line">    <span class="keyword">return</span> <span class="comment">// 无需明确指定返回值</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>area</code> 和 <code>perimeter </code> 是上述函数中的命名返回值。请注意，函数中的<code>return</code> 语句不会显式返回任何值。由于在函数声明中将 <code>area</code> 和 <code> perimeter</code> 指定为返回值，因此当遇到 <code>return</code> 语句时，它们会自动从函数中返回。</p><h2 id="空白标识符"><a href="#空白标识符" class="headerlink" title="空白标识符"></a>空白标识符</h2><p><code>_</code> 是 Go 语言中的空白标识符。它可以代替任何类型的任何值。让我们看看这个空白标识符的用途。</p><p><code>rectProps</code> 函数返回矩形的面积和周长。如果我们只需要 <code>area</code> 而想要丢弃 <code>perimeter</code>，该怎么办。这时  <code>_</code> 就可以派上用场。</p><p>下面的程序仅使用从 <code>rectProps</code> 函数返回的 <code>area</code>。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">rectProps</span><span class="params">(length, width <span class="type">float64</span>)</span></span> (<span class="type">float64</span>, <span class="type">float64</span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> area = length * width</span><br><span class="line">    <span class="keyword">var</span> perimeter = (length + width) * <span class="number">2</span></span><br><span class="line">    <span class="keyword">return</span> area, perimeter</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    area, _ := rectProps(<span class="number">10.8</span>, <span class="number">5.6</span>) <span class="comment">// perimeter 被丢弃</span></span><br><span class="line">    fmt.Printf(<span class="string">&quot;Area %f &quot;</span>, area)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/IkugSH1jIt">Run in playground</a></p><p>在第 13 行中，我们仅使用 <code>area</code>，而使用 <code>_</code> 标识符来丢弃 <code>perimeter</code>。</p><blockquote><p>原文地址 <a href="https://golangbot.com/functions/">Functions</a><br>原文作者：<a href="https://golangbot.com/about/">Naveen Ramanathan</a><br>译文出自：<a href="https://blog.zisheng.pro/categories/%E6%B4%9B%E7%AB%B9%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/">紫升翻译计划</a></p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/e59d3fd533b9/</id>
    <link href="https://blog.zisheng.pro/posts/e59d3fd533b9/"/>
    <published>2023-03-19T07:06:00.000Z</published>
    <summary>函数是执行特定任务的代码块。一个函数接受一个输入，对该输入执行一些计算，然后生成一个输出。</summary>
    <title>Go 语言教程系列の函数</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/categories/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/categories/Golang/"/>
    <category term="Go 语言系列教程" scheme="https://blog.zisheng.pro/categories/Golang/Go-%E8%AF%AD%E8%A8%80%E7%B3%BB%E5%88%97%E6%95%99%E7%A8%8B/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/tags/Golang/"/>
    <category term="Go 语言教程" scheme="https://blog.zisheng.pro/tags/Go-%E8%AF%AD%E8%A8%80%E6%95%99%E7%A8%8B/"/>
    <category term="Go 语言教程系列" scheme="https://blog.zisheng.pro/tags/Go-%E8%AF%AD%E8%A8%80%E6%95%99%E7%A8%8B%E7%B3%BB%E5%88%97/"/>
    <category term="Go 常量" scheme="https://blog.zisheng.pro/tags/Go-%E5%B8%B8%E9%87%8F/"/>
    <content>
      <![CDATA[<h2 id="常量是什么？"><a href="#常量是什么？" class="headerlink" title="常量是什么？"></a>常量是什么？</h2><p>Go 中的常量一词用于表示固定值，例如：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">95</span><br><span class="line"><span class="string">&quot;I love Go&quot;</span></span><br><span class="line">67.89</span><br></pre></td></tr></table></figure><h2 id="声明一个常量"><a href="#声明一个常量" class="headerlink" title="声明一个常量"></a>声明一个常量</h2><p>关键字 <code>const</code> 用于声明常量。让我们看看如何使用 <code>const</code> 来声明一个常量。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">const</span> a = <span class="number">50</span></span><br><span class="line">    fmt.Println(a)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/mv3B-q3h0zh">Run in playground</a></p><p>上面的代码中，<code>a</code> 是一个常量并被赋值为 <code>50</code>。</p><h2 id="声明一组常量"><a href="#声明一组常量" class="headerlink" title="声明一组常量"></a>声明一组常量</h2><p>还有另一种语法可以使用单个语句定义一组常量。下面提供了使用此语法定义一组常量的示例。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">const</span> (</span><br><span class="line">        name = <span class="string">&quot;John&quot;</span></span><br><span class="line">        age = <span class="number">50</span></span><br><span class="line">        country = <span class="string">&quot;Canada&quot;</span></span><br><span class="line">    )</span><br><span class="line">    fmt.Println(name)</span><br><span class="line">    fmt.Println(age)</span><br><span class="line">    fmt.Println(country)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/KvZ6zNz4A04">Run in playground</a></p><p>在上面的程序中，我们声明了 3 个常量 <code>name</code>、<code>age</code> 和 <code>country</code>。 执行上面的程序会打印出以下内容：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">John</span><br><span class="line">50</span><br><span class="line">Canada</span><br></pre></td></tr></table></figure><p>顾名思义，常量不能再次重新分配任何其他值。在下面的程序中，我们试图将另一个值 <code>89</code> 分配给 <code>a</code>。这是不允许的，因为 <code>a</code> 是常量。 该程序将无法运行，并显示编译错误 <code>cannot assign to a</code>。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">const</span> a = <span class="number">55</span> <span class="comment">//allowed</span></span><br><span class="line">a = <span class="number">89</span> <span class="comment">//reassignment not allowed</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/b2J8_UQobb">Run in playground</a></p><p>常量的值应在编译时确定。因此，由于函数调用在运行时发生，因此不能将函数调用返回的值分配给常量。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;math&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">var</span> a = math.Sqrt(<span class="number">4</span>)   <span class="comment">//allowed</span></span><br><span class="line">    <span class="keyword">const</span> b = math.Sqrt(<span class="number">4</span>) <span class="comment">//not allowed</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/GBlQDcbqfn-">Run in playground</a></p><p>在上面的程序中，<code>a</code> 是一个变量，因此可以将函数 <code>math.Sqrt(4)</code> 的返回值分配给它（我们将在单独的教程中更详细地讨论函数）。</p><p><code>b</code> 是一个常量，在编译时必须确定 <code>b</code> 的值。而 <code>math.Sqrt(4)</code> 函数是在运行时确定返回值，因此 <code>const b = math.Sqrt(4)</code> 编译失败并显示错误：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./prog.go:9:8: const initializer math.Sqrt(4) is not a constant</span><br></pre></td></tr></table></figure><h2 id="字符串常量、有类型常量和无类型常量"><a href="#字符串常量、有类型常量和无类型常量" class="headerlink" title="字符串常量、有类型常量和无类型常量"></a>字符串常量、有类型常量和无类型常量</h2><p>在 Go 中，双引号之间包含的任何值都是字符串常量。 例如，<code>&quot;Hello World&quot;</code>，<code>&quot;Sam&quot;</code> 之类的字符串在 Go 中都是常量。</p><p>字符串常量属于什么类型？ 答案是它们是无类型的。</p><p>像 <code>&quot;Hello World&quot;</code> 这样的字符串常量没有任何类型。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> hello = <span class="string">&quot;Hello World&quot;</span></span><br></pre></td></tr></table></figure><p>在上面的代码行中，常量 <code>&quot;hello&quot;</code> 没有类型。</p><p>Go 是一种强类型语言。所有变量都需要显式类型。试想一下，以下程序将无类型的常量 <code>n</code> 的值分配给变量 <code>name</code> 会发生什么？</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">const</span> n = <span class="string">&quot;Sam&quot;</span></span><br><span class="line">    <span class="keyword">var</span> name = n</span><br><span class="line">    fmt.Printf(<span class="string">&quot;type %T value %v&quot;</span>, name, name)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/oFv_cFuEucL">Run in playground</a></p><p>答案是未类型的常量具有与之关联的默认类型，并且仅当一行代码需要它时，它们才提供默认类型。在 <code>var name = n</code> 的语句中，<code>name</code> 需要一个类型，它是从字符串常量 <code>n</code> 的默认类型（即字符串）中获取的。</p><p>有没有办法创建有类型的常量呢？答案是肯定的。下面的代码会创建一个有类型的常量。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> typedhello <span class="type">string</span> = <span class="string">&quot;Hello World&quot;</span></span><br></pre></td></tr></table></figure><p><strong>上面的代码中的 <code>typedhello</code> 是字符串类型的常量。</strong></p><p>Go 是一种强类型语言。分配期间不允许混合类型。让我们看看下面的程序来理解下这意味着什么。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">var</span> defaultName = <span class="string">&quot;Sam&quot;</span> <span class="comment">//allowed</span></span><br><span class="line">    <span class="keyword">type</span> myString <span class="type">string</span></span><br><span class="line">    <span class="keyword">var</span> customName myString = <span class="string">&quot;Sam&quot;</span> <span class="comment">//allowed</span></span><br><span class="line">    customName = defaultName <span class="comment">//not allowed</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/1Q-vudNn_9">Run in playground</a></p><p>在上面的代码中，我们首先创建一个变量 <code>defaultName</code>，并将常量 <code>Sam</code> 分配给它。<strong>常量 <code>Sam</code> 的默认类型是 <code>string</code>，因此 <code>defaultName</code> 赋值后的类型是 <code>string</code>。</strong>.</p><p>在下一行中，我们创建一个新类型 <code>myString</code>，它是字符串类型的别名。</p><p>然后，我们创建一个类型为 <code>myString</code> 的变量 <code>customName</code>，并为其分配常量 <code>&quot;Sam&quot;</code>。由于常量 <code>&quot;Sam&quot;</code> 是无类型的，因此可以将其分配给任何 <code>string</code> 变量。Go 允许这种分配，并且 <code>customName</code> 的类型为 <code>myString</code>。</p><p>现在，我们有一个字符串类型的变量 <code>defaultName</code> 和另一个 <code>myString</code> 类型的变量 <code>customName</code>。即使我们知道 <code>myString</code> 是字符串的别名，Go 的强类型策略也不允许将一种类型的变量分配给另一种类型。因此，不允许 <code>customName = defaultName</code> 这种分配，并且编译器将引发错误 <code>./prog.go:7:20: cannot use defaultName (type string) as type myString in assignment</code>。</p><h2 id="布尔常量"><a href="#布尔常量" class="headerlink" title="布尔常量"></a>布尔常量</h2><p>布尔常量与字符串常量没有什么不同。它们是两个无类型的常量 <code>true</code> 和 <code>false</code>。字符串常量的相同规则也适用于布尔值，因此在此不再赘述。以下是一个解释布尔常量的简单程序：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">const</span> trueConst = <span class="literal">true</span></span><br><span class="line">    <span class="keyword">type</span> myBool <span class="type">bool</span></span><br><span class="line">    <span class="keyword">var</span> defaultBool = trueConst <span class="comment">//allowed</span></span><br><span class="line">    <span class="keyword">var</span> customBool myBool = trueConst <span class="comment">//allowed</span></span><br><span class="line">    defaultBool = customBool <span class="comment">//not allowed</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/h9yzC6RxOR">Run in playground</a></p><p>上面的程序的意思是不言而喻的。</p><h2 id="数字常量"><a href="#数字常量" class="headerlink" title="数字常量"></a>数字常量</h2><p>数字常量包含整形、浮点型和复数常量。这些概念使得数字常量很容易混淆。让我们看一些示例以使事情变得清楚。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">const</span> a = <span class="number">5</span></span><br><span class="line">    <span class="keyword">var</span> intVar <span class="type">int</span> = a</span><br><span class="line">    <span class="keyword">var</span> int32Var <span class="type">int32</span> = a</span><br><span class="line">    <span class="keyword">var</span> float64Var <span class="type">float64</span> = a</span><br><span class="line">    <span class="keyword">var</span> complex64Var <span class="type">complex64</span> = a</span><br><span class="line">    fmt.Println(<span class="string">&quot;intVar&quot;</span>,intVar, <span class="string">&quot;\nint32Var&quot;</span>, int32Var, <span class="string">&quot;\nfloat64Var&quot;</span>, float64Var, <span class="string">&quot;\ncomplex64Var&quot;</span>,complex64Var)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/a8sxVNdU8M">Run in playground</a></p><p>在上面的程序中，常量 <code>a</code> 是无类型的，其值是 5。你可能想知道 <code>a</code> 的默认类型是什么，如果它确实有一个，那么我们如何将其分配给不同类型的变量。答案在于 <code>a</code> 的语法。以下程序将使事情更加清楚。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">var</span> i = <span class="number">5</span></span><br><span class="line">    <span class="keyword">var</span> f = <span class="number">5.6</span></span><br><span class="line">    <span class="keyword">var</span> c = <span class="number">5</span> + <span class="number">6i</span></span><br><span class="line">    fmt.Printf(<span class="string">&quot;i&#x27;s type is %T, f&#x27;s type is %T, c&#x27;s type is %T&quot;</span>, i, f, c)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/0-eVCbJ76B5">Run in playground</a></p><p>在上面的程序中，每个变量的类型由数字常量的语法确定。从语法上看，<code>5</code> 是整数，<code>5.6</code> 是浮点数，而 <code>5 + 6i</code> 是复数。当上面的程序运行时，它会打印：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">i<span class="string">&#x27;s type is int, f&#x27;</span>s <span class="built_in">type</span> is float64, c<span class="string">&#x27;s type is complex128</span></span><br></pre></td></tr></table></figure><p>有了这些知识，让我们尝试了解以下程序的运行原理。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">const</span> a = <span class="number">5</span></span><br><span class="line">    <span class="keyword">var</span> intVar <span class="type">int</span> = a</span><br><span class="line">    <span class="keyword">var</span> int32Var <span class="type">int32</span> = a</span><br><span class="line">    <span class="keyword">var</span> float64Var <span class="type">float64</span> = a</span><br><span class="line">    <span class="keyword">var</span> complex64Var <span class="type">complex64</span> = a</span><br><span class="line">    fmt.Println(<span class="string">&quot;intVar&quot;</span>,intVar, <span class="string">&quot;\nint32Var&quot;</span>, int32Var, <span class="string">&quot;\nfloat64Var&quot;</span>, float64Var, <span class="string">&quot;\ncomplex64Var&quot;</span>,complex64Var)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/_zu0iK-Hyj">Run in playground</a></p><p>在上面的程序中，<code>a</code> 的值是 <code>5</code>，而 <code>a</code> 的语法是通用的。它可以表示浮点数，整数或者是没有虚部的复数。 因此，可以将其分配给任何兼容类型。这些常量的默认类型可以被认为是根据上下文动态生成的。 <code>var complex64Var complex64 = a</code> 要求 <code>a</code> 是一个复数，因此它成为一个复数常数。</p><h2 id="数字表达式"><a href="#数字表达式" class="headerlink" title="数字表达式"></a>数字表达式</h2><p>数字常量可以在表达式中自由混合和匹配，并且仅当将它们分配给变量或在代码中需要类型的任何位置使用时，才需要类型。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">var</span> a = <span class="number">5.9</span> / <span class="number">8</span></span><br><span class="line">    fmt.Printf(<span class="string">&quot;a&#x27;s type is %T and value is %v&quot;</span>, a, a)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/Nsak9scUAWg">Run in playground</a></p><p>在上面的程序中，<code>5.9</code> 是语法上的浮点数，<code>8</code> 是语法上的整数。不过，仍允许使用 <code>5.9 / 8</code>，这是因为它们都是数字常量。除法的结果是<code>0.7375</code> 是 <code>float</code>，因此变量 <code>a</code> 是 <code>float</code> 类型。该程序的输出是：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">a<span class="string">&#x27;s type is float64 and value is 0.7375</span></span><br></pre></td></tr></table></figure><blockquote><p>原文地址 <a href="https://golangbot.com/constants/">Constants</a><br>原文作者：<a href="https://golangbot.com/about/">Naveen Ramanathan</a><br>译文出自：<a href="https://blog.zisheng.pro/categories/%E6%B4%9B%E7%AB%B9%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/">紫升翻译计划</a></p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/84e9c8841ae1/</id>
    <link href="https://blog.zisheng.pro/posts/84e9c8841ae1/"/>
    <published>2023-03-19T07:05:00.000Z</published>
    <summary>Go 中的常量一词用于表示固定值</summary>
    <title>Go 语言教程系列の常量</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/categories/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/categories/Golang/"/>
    <category term="Go 语言系列教程" scheme="https://blog.zisheng.pro/categories/Golang/Go-%E8%AF%AD%E8%A8%80%E7%B3%BB%E5%88%97%E6%95%99%E7%A8%8B/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/tags/Golang/"/>
    <category term="Go 语言教程" scheme="https://blog.zisheng.pro/tags/Go-%E8%AF%AD%E8%A8%80%E6%95%99%E7%A8%8B/"/>
    <category term="Go 语言教程系列" scheme="https://blog.zisheng.pro/tags/Go-%E8%AF%AD%E8%A8%80%E6%95%99%E7%A8%8B%E7%B3%BB%E5%88%97/"/>
    <category term="Go 数据类型" scheme="https://blog.zisheng.pro/tags/Go-%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B/"/>
    <content>
      <![CDATA[<p>下面是 Go 语言中可用的基础类型：</p><ul><li>bool</li><li>Numeric Types</li><li>int8, int16, int32, int64, int</li><li>uint8, uint16, uint32, uint64, uint</li><li>float32, float64</li><li>complex64, complex128</li><li>byte</li><li>rune</li><li>string</li></ul><h2 id="bool"><a href="#bool" class="headerlink" title="bool"></a>bool</h2><p><code>bool</code> 表示 <code>boolean</code> 类型，用来表达 <code>true</code> 或 <code>false</code> 的。</p><blockquote><p>生存还是毁灭，这是一个问题！</p></blockquote><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    a := <span class="literal">true</span></span><br><span class="line">    b := <span class="literal">false</span></span><br><span class="line">    fmt.Println(<span class="string">&quot;a:&quot;</span>, a, <span class="string">&quot;b:&quot;</span>, b)</span><br><span class="line">    c := a &amp;&amp; b</span><br><span class="line">    fmt.Println(<span class="string">&quot;c:&quot;</span>, c)</span><br><span class="line">    d := a || b</span><br><span class="line">    fmt.Println(<span class="string">&quot;d:&quot;</span>, d)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/v_W3HQ0MdY">Run in playground</a></p><p>上面的程序中，变量<code>a</code> 被分配了 <code>true</code>，变量<code>b</code> 被分配了 <code>false</code>。<code>c</code> 被分配了 <code>a &amp;&amp; b</code> 表达式的值。当 <code>a</code> 和 <code>b</code> 的值都为 <code>true</code> 时 <code>&amp;&amp;</code> 操作符返回 <code>true</code>。因此上面的变量 <code>c</code> 的值是 <code>false</code>。</p><p>当 <code>a</code> 或 <code>b</code> 的值为 <code>true</code> 时，<code>||</code> 操作符返回 <code>true</code>。在这个例子中，因为 <code>a</code> 的值是 <code>true</code>，所以变量 <code>d</code> 的值是 <code>true</code>。上面的程序执行后会输出以下内容：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">a: <span class="literal">true</span> b: <span class="literal">false</span></span><br><span class="line">c: <span class="literal">false</span></span><br><span class="line">d: <span class="literal">true</span></span><br></pre></td></tr></table></figure><h2 id="有符号整形"><a href="#有符号整形" class="headerlink" title="有符号整形"></a>有符号整形</h2><ul><li><strong>int8</strong>：表示 8 位有符号整数<ul><li><strong>size</strong>：8 位</li><li><strong>range</strong>：-128 到 127</li></ul></li><li><strong>int16</strong>：表示 16 位有符号整数<ul><li><strong>size</strong>：16  位</li><li><strong>range</strong>：-32768 到 32767</li></ul></li><li><strong>int32</strong>：表示 32 位有符号整数<ul><li><strong>size</strong>：32 位</li><li><strong>range</strong>：-2147483648 到 2147483647</li></ul></li><li><strong>int64</strong>：表示 64 位有符号整数<ul><li><strong>size</strong>：64 位</li><li><strong>range</strong>：-9223372036854775808 到 9223372036854775807</li></ul></li><li><strong>int</strong>：代表 32 或 64 位整数，具体取决于基础平台。除非需要使用特定大小的整数，否则通常应该使用 <code>int</code> 表示整数。<ul><li><strong>size</strong>：32 位操作系统中是 32 位，64 位操作系统中是 64 位。</li><li><strong>range</strong>：32 位操作系统中是 -2147483648 到 2147483647，64 位操作系统中是 -9223372036854775808 到 9223372036854775807</li></ul></li></ul><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">var</span> a <span class="type">int</span> = <span class="number">89</span></span><br><span class="line">    b := <span class="number">95</span></span><br><span class="line">    fmt.Println(<span class="string">&quot;value of a is&quot;</span>, a, <span class="string">&quot;and b is&quot;</span>, b)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/NyDPsjkma3">Run in playground</a></p><p>上面的程序会输出 <code>value of a is 89 and b is 95</code>。</p><p>在上面的程序中，<code>a</code> 是 <code>int</code> 类型，<code>b</code> 的类型是从分配给它的值中推断出来的（95）。如上所述，<code>int</code> 的大小在 32 位系统中为 32 位，在 64 位系统中为 64 位。让我们继续进行验证。</p><p>可以使用 <code>Printf</code> 函数中的 <code>％T</code> 格式说明符来打印变量的类型。Go 有一个 <a href="https://golang.org/pkg/unsafe/">unsafe</a> 包，该包拥有 <code>Sizeof</code> 函数，该函数以字节为单位返回传递给它的变量的大小。不安全的软件包应谨慎使用，因为使用它的代码可能存在可移植性问题，但是出于本教程的目的，我们可以使用它。</p><p>以下程序输出变量 <code>a</code> 和 <code>b</code> 的类型和大小。<code>％T</code> 是格式说明符，用于打印类型，而 <code>％d</code> 用于打印尺寸。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">  <span class="string">&quot;fmt&quot;</span></span><br><span class="line">  <span class="string">&quot;unsafe&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">  <span class="keyword">var</span> a <span class="type">int</span> = <span class="number">89</span></span><br><span class="line">  b := <span class="number">95</span></span><br><span class="line">  fmt.Println(<span class="string">&quot;value of a is&quot;</span>, a, <span class="string">&quot;and b is&quot;</span>, b)</span><br><span class="line">  fmt.Printf(<span class="string">&quot;type of a is %T, size of a is %d&quot;</span>, a, unsafe.Sizeof(a)) <span class="comment">//type and size of a</span></span><br><span class="line">  fmt.Printf(<span class="string">&quot;\ntype of b is %T, size of b is %d&quot;</span>, b, unsafe.Sizeof(b)) <span class="comment">//type and size of b</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/mFsmjVk5oc">Run in playground</a></p><p>上面的程序将输出以下内容：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">value of a is 89 and b is 95</span><br><span class="line"><span class="built_in">type</span> of a is int, size of a is 4</span><br><span class="line"><span class="built_in">type</span> of b is int, size of b is 4</span><br></pre></td></tr></table></figure><p>我们可以从上面的输出中推断出 <code>a</code> 和 <code>b</code> 的类型为 <code>int</code>，它们的大小为 32 位（4 个字节）。如果在 64 位系统上运行上述程序，输出将有所不同。 在 64 位系统中，<code>a</code> 和 <code>b</code> 占用 64 位（8 字节）。</p><h2 id="无符号整形"><a href="#无符号整形" class="headerlink" title="无符号整形"></a>无符号整形</h2><ul><li><strong>uint8</strong>：代表 8 位无符号整形<ul><li><strong>size</strong>：8 位</li><li><strong>range</strong>：0 到 255</li></ul></li><li><strong>uint16</strong>：代表 16 位无符号整形<ul><li><strong>size</strong>：16 位</li><li><strong>range</strong>：0 到 65535</li></ul></li><li><strong>uint32</strong>：代表 32 位无符号整形<ul><li><strong>size</strong>：32  位</li><li><strong>range</strong>：0 到 4294967295</li></ul></li><li><strong>uint64</strong>：代表 64 位无符号整形<ul><li><strong>size</strong>：64 位</li><li><strong>range</strong>：0 到 18446744073709551615</li></ul></li><li><strong>uint</strong>： 表示 32 或 64 位无符号整数，具体取决于基础平台。<ul><li><strong>size</strong>：在 32 位系统中为 32 位，在 64 位系统中为 64 位。</li><li><strong>range</strong>：在 32 位系统中是 0 到 4294967295，在 62 位操作系统中是 0 到 18446744073709551615</li></ul></li></ul><h2 id="浮点类型"><a href="#浮点类型" class="headerlink" title="浮点类型"></a>浮点类型</h2><ul><li><strong>float32</strong>：32 位浮点数</li><li><strong>float64</strong>：64 位浮点数</li></ul><p>下面是一个简单的程序，用于说明整数和浮点类型：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">  a, b := <span class="number">5.67</span>, <span class="number">8.97</span></span><br><span class="line">  fmt.Printf(<span class="string">&quot;type of a %T b %T\n&quot;</span>, a, b)</span><br><span class="line">  sum := a + b</span><br><span class="line">  diff := a - b</span><br><span class="line">  fmt.Println(<span class="string">&quot;sum&quot;</span>, sum, <span class="string">&quot;diff&quot;</span>, diff)</span><br><span class="line">  no1, no2 := <span class="number">56</span>, <span class="number">89</span></span><br><span class="line">  fmt.Println(<span class="string">&quot;sum&quot;</span>, no1+no2, <span class="string">&quot;diff&quot;</span>, no1-no2)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/upwUCprT-j">Run in playground</a></p><p><code>a</code> 和 <code>b</code> 的类型是从分配给它们的值推断出来的。在这种情况下，<code>a</code> 和 <code>b</code> 的类型为 <code>float64</code>。（<code>float64</code> 是浮点值的默认类型）。我们将 <code>a</code> 和 <code>b</code> 相加并将其分配给变量 <code>sum</code>。我们从 <code>a</code> 中减去 <code>b</code> 并将其分配给 <code>diff</code>。然后打印出 <code>sum</code> 和 <code>diff</code>。使用 <code>no1</code> 和 <code>no2</code> 进行类似的计算。上面的程序将打印：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">type</span> of a float64 b float64</span><br><span class="line"><span class="built_in">sum</span> 14.64 diff -3.3000000000000007</span><br><span class="line"><span class="built_in">sum</span> 145 diff -33</span><br></pre></td></tr></table></figure><h2 id="复数（complex）类型"><a href="#复数（complex）类型" class="headerlink" title="复数（complex）类型"></a>复数（complex）类型</h2><blockquote><p><a href="https://zh.wikipedia.org/wiki/%E5%A4%8D%E6%95%B0_(%E6%95%B0%E5%AD%A6)">复数</a>，为实数的延伸，它使任一多项式方程都有根。——维基百科</p></blockquote><ul><li><strong>complex64</strong>：具有 <code>float32</code> 实部和虚部的复数</li><li><strong>complex128</strong>：具有 <code>float64</code> 实部和虚部的复数</li></ul><p>内置函数 <code>complex</code> 用于构造具有实部和虚部的复数。<code>complex</code> 函数具有以下定义：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">complex</span><span class="params">(r, i FloatType)</span></span> ComplexType</span><br></pre></td></tr></table></figure><p>它以实部和虚部作为参数，并返回复数类型。实部和虚部都必须是同一类型。即 <code>float32</code> 或 <code>float64</code>。如果实部和虚部都为 <code>float32</code>，则此函数返回类型为 <code>complex64</code> 的复数值。如果实部和虚部都为 <code>float64</code> 类型，则此函数返回 <code>complex128</code> 类型的复数值：</p><p>复数可以使用短声明语法创建：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">c := <span class="number">6</span> + <span class="number">7i</span></span><br></pre></td></tr></table></figure><p>让我们来写一个小程序助于理解复数：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">  c1 := <span class="built_in">complex</span>(<span class="number">5</span>, <span class="number">7</span>)</span><br><span class="line">  c2 := <span class="number">8</span> + <span class="number">27i</span></span><br><span class="line">  cadd := c1 + c2</span><br><span class="line">  fmt.Println(<span class="string">&quot;sum:&quot;</span>, cadd)</span><br><span class="line">  cmul := c1 * c2</span><br><span class="line">  fmt.Println(<span class="string">&quot;product:&quot;</span>, cmul)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/kEz1uKCdKs">Run in playground</a></p><p>在上面的程序中，<code>c1</code> 和 <code>c2</code> 是两个复数。<code>c1</code> 的实部为 5，虚部为 7。 <code>c2</code> 具有实部 8 和虚部 27。<code>cadd</code> 被分配为 <code>c1</code> 和 <code>c2</code> 的和，而 <code>cmul</code> 被分配为 <code>c1</code> 和 <code>c2</code> 的乘积。该程序将输出：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sum</span>: (13+34i)</span><br><span class="line">product: (-149+191i)</span><br></pre></td></tr></table></figure><h2 id="其他数字类型"><a href="#其他数字类型" class="headerlink" title="其他数字类型"></a>其他数字类型</h2><ul><li><strong>byte</strong>：<code>uint8</code> 的别名</li><li><strong>rune</strong>：<code>int32</code> 的别名</li></ul><h2 id="字符串类型"><a href="#字符串类型" class="headerlink" title="字符串类型"></a>字符串类型</h2><p>在 Go 语言中，字符串是字节的集合。如果这个定义没有任何意义，那也没关系。现在，我们可以假设字符串是字符的集合。</p><p>让我们使用字符串类型来写一个程序：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">  first := <span class="string">&quot;Naveen&quot;</span></span><br><span class="line">  last := <span class="string">&quot;Ramanathan&quot;</span></span><br><span class="line">  name := first +<span class="string">&quot; &quot;</span>+ last</span><br><span class="line">  fmt.Println(<span class="string">&quot;My name is&quot;</span>,name)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/CI6phwSVel">Run in playground</a></p><p>在上面的程序中，首先分配了字符串 <code>Naveen</code>，最后分配了字符串 <code>Ramanathan</code>。可以使用 <code>+</code> 运算符将字符串连接在一起。为 <code>name</code> 分配了 <code>first</code> 的值，该值连接到一个空格，后跟一个 <code>last</code>。上面的程序将输出 <code>My name is Naveen Ramanathan</code>。</p><p>还有一些可以对字符串执行的操作。我们将在单独的教程中介绍这些内容。</p><h2 id="类型转换"><a href="#类型转换" class="headerlink" title="类型转换"></a>类型转换</h2><p>Go 对于类型明确非常严格。没有自动类型提升或转换。让我们看一个例子来理解这意味着什么：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">  i := <span class="number">55</span> <span class="comment">//int</span></span><br><span class="line">  j := <span class="number">67.8</span> <span class="comment">//float64</span></span><br><span class="line">  sum := i + j <span class="comment">//int + float64 not allowed</span></span><br><span class="line">  fmt.Println(sum)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/m-TMRpmmnm">Run in playground</a></p><p>上面的代码在 C 语言中是完全合法的。但是在使用 go 的情况下，这将行不通。 <code>i</code> 是 <code>int</code> 类型，<code>j</code> 是 <code>float64</code> 类型。我们正在尝试添加 2 个不同类型的数字，这是不允许的。运行程序时，你将得到 <code>main.go：10:invalid operation:i + j(mismatched types int and float64)</code> 的错误。</p><p>要解决该错误，<code>i</code> 和 <code>j</code> 应该具有相同的类型。让我们将 <code>j</code> 转换为 <code>int</code>。<code>T(v)</code> 是将值 <code>v</code> 转换为类型 <code>T</code> 的语法：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">  i := <span class="number">55</span> <span class="comment">//int</span></span><br><span class="line">  j := <span class="number">67.8</span> <span class="comment">//float64</span></span><br><span class="line">  sum := i + <span class="type">int</span>(j) <span class="comment">//j is converted to int</span></span><br><span class="line">  fmt.Println(sum)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/mweu3n3jMy">Run in playground</a></p><p>现在，当你运行上述程序时，你会看到 <code>122</code> 作为输出。</p><p>分配也是如此。需要进行显式类型转换才能将一种类型的变量分配给另一种类型。以下程序能够证实这一点：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">  i := <span class="number">10</span></span><br><span class="line">  <span class="keyword">var</span> j <span class="type">float64</span> = <span class="type">float64</span>(i) <span class="comment">//this statement will not work without explicit conversion</span></span><br><span class="line">  fmt.Println(<span class="string">&quot;j&quot;</span>, j)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/Y2uSYYr46c">Run in playground</a></p><p>在第 9 行中，<code>i</code> 转换为 <code>float64</code>，然后被分配给 <code>j</code>。当你尝试将 <code>i</code> 分配给 <code>j</code> 而不进行任何类型转换时，编译器将引发错误。</p><blockquote><p>原文地址 <a href="https://golangbot.com/types/">Types</a><br>原文作者：<a href="https://golangbot.com/about/">Naveen Ramanathan</a><br>译文出自：<a href="https://blog.zisheng.pro/categories/%E6%B4%9B%E7%AB%B9%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/">紫升翻译计划</a></p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/6fecb073913f/</id>
    <link href="https://blog.zisheng.pro/posts/6fecb073913f/"/>
    <published>2023-03-19T07:04:00.000Z</published>
    <summary>本文介绍了 Go 的数据类型。</summary>
    <title>Go 语言教程系列の数据类型</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/categories/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/categories/Golang/"/>
    <category term="Go 语言系列教程" scheme="https://blog.zisheng.pro/categories/Golang/Go-%E8%AF%AD%E8%A8%80%E7%B3%BB%E5%88%97%E6%95%99%E7%A8%8B/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/tags/Golang/"/>
    <category term="Go 语言教程" scheme="https://blog.zisheng.pro/tags/Go-%E8%AF%AD%E8%A8%80%E6%95%99%E7%A8%8B/"/>
    <category term="Go 语言教程系列" scheme="https://blog.zisheng.pro/tags/Go-%E8%AF%AD%E8%A8%80%E6%95%99%E7%A8%8B%E7%B3%BB%E5%88%97/"/>
    <category term="Golang 变量" scheme="https://blog.zisheng.pro/tags/Golang-%E5%8F%98%E9%87%8F/"/>
    <content>
      <![CDATA[<p>这是我们 Golang 教程系列中的第三篇教程，它涉及 Golang 中的变量。</p><p>你可以先阅读 <a href="https://juejin.cn/post/6942876429625327653">《Go 语言教程系列》Hello World | Go 主题月</a> 来了解关于配置 Go 和运行 hello world 程序的信息。</p><h2 id="变量是什么？"><a href="#变量是什么？" class="headerlink" title="变量是什么？"></a>变量是什么？</h2><p>变量是为内存地址指定的名称，用于存储特定类型的值。Go 中有多种语法来声明变量。让我们一一看一下。</p><h2 id="声明单个变量"><a href="#声明单个变量" class="headerlink" title="声明单个变量"></a>声明单个变量</h2><p><strong>var name type</strong> 是声明单个变量的语法。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">  <span class="keyword">var</span> age <span class="type">int</span> <span class="comment">// 变量声明</span></span><br><span class="line">  fmt.Println(<span class="string">&quot;My age is&quot;</span>, age)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/lvKWH29bwke">Run in playground</a></p><p>语句 <code>var age int</code> 声明了一个类型为 <code>int</code> 的名为 <code>age</code> 的变量。我们没有为该变量赋任何值。如果未为变量分配任何值，则 Go 会使用变量类型的“零值”自动将其初始化。在这种情况下，为年龄分配了值 <code>0</code>，即 <code>int</code>的零值。如果运行此程序，则可以看到以下输出。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">My age is <span class="number">0</span></span><br></pre></td></tr></table></figure><p>可以为变量分配给其类型的任何值。在上面的程序中，可以为 <code>age</code> 分配任何整数值。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">var</span> age <span class="type">int</span> <span class="comment">// 变量声明</span></span><br><span class="line">    fmt.Println(<span class="string">&quot;My age is&quot;</span>, age)</span><br><span class="line">    age = <span class="number">29</span> <span class="comment">// 分配</span></span><br><span class="line">    fmt.Println(<span class="string">&quot;My age is&quot;</span>, age)</span><br><span class="line">    age = <span class="number">54</span> <span class="comment">// 分配</span></span><br><span class="line">    fmt.Println(<span class="string">&quot;My new age is&quot;</span>, age)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/hSsWbF-3lQU">Run in playground</a></p><p>上面的程序将打印以下输出：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">My age is  <span class="number">0</span></span><br><span class="line">My age is <span class="number">29</span></span><br><span class="line">My <span class="built_in">new</span> age is <span class="number">54</span></span><br></pre></td></tr></table></figure><h2 id="声明一个具有初始值的变量"><a href="#声明一个具有初始值的变量" class="headerlink" title="声明一个具有初始值的变量"></a>声明一个具有初始值的变量</h2><p>声明变量时，还可以为其提供初始值。以下是声明具有初始值的变量的语法。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> name <span class="keyword">type</span> = initialvalue</span><br></pre></td></tr></table></figure><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">var</span> age <span class="type">int</span> = <span class="number">29</span> <span class="comment">// 带有初始值的变量声明</span></span><br><span class="line"></span><br><span class="line">    fmt.Println(<span class="string">&quot;My age is&quot;</span>, age)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/NAbtaE0pjZ8">Run in playground</a></p><p>在上面的程序中，<code>age</code>是类型为 <code>int</code> 的变量，其初始值为 <code>29</code>。上面的程序将输出以下内容：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">My age is 29</span><br></pre></td></tr></table></figure><p>它显示年龄已经用值 29 初始化。</p><h2 id="类型推断"><a href="#类型推断" class="headerlink" title="类型推断"></a>类型推断</h2><p>如果变量具有初始值，Go 将自动使用该初始值来推断该变量的类型。 因此，如果变量具有初始值，则可以删除变量声明中的 <code>type</code>。</p><p>如果使用以下语法声明了变量</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> name = initialvalue</span><br></pre></td></tr></table></figure><p>Go 会自动从初始值推断出该变量的类型。</p><p>在下面的示例中，我们可以看到变量 <code>age</code> 的类型 <code>int</code> 已在第 6 行中删除。由于变量的初始值为 29，因此 Go 可以推断出它的类型为 <code>int</code>。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">  <span class="keyword">var</span> age = <span class="number">29</span> <span class="comment">// 类型将被推断为 int</span></span><br><span class="line">  fmt.Println(<span class="string">&quot;My age is&quot;</span>, age)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/meyJPveRLrK">Run in playground</a></p><h2 id="多变量声明"><a href="#多变量声明" class="headerlink" title="多变量声明"></a>多变量声明</h2><p>多变量声明可以使用单行语句来声明多个变量。</p><p><code>var name1, name2 type = initialvalue1, initialvalue2</code> 是多变量声明的语法。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">var</span> width, height <span class="type">int</span> = <span class="number">100</span>, <span class="number">50</span> <span class="comment">// 声明多变量</span></span><br><span class="line"></span><br><span class="line">    fmt.Println(<span class="string">&quot;width is&quot;</span>, width, <span class="string">&quot;height is&quot;</span>, height)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/4aOQyt55ah">Run in playground</a></p><p>如果变量具有初始值，则可以删除该类型。 由于上述程序具有变量的初始值，因此可以删除 <code>int</code> 类型。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">var</span> width, height = <span class="number">100</span>, <span class="number">50</span> <span class="comment">// int 被移除了</span></span><br><span class="line"></span><br><span class="line">    fmt.Println(<span class="string">&quot;width is&quot;</span>, width, <span class="string">&quot;height is&quot;</span>, height)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/f7deGqYFNEL">Run in playground</a></p><p>上面的程序将以 <code>print width is 100 height is 50</code> 作为输出。</p><p>就像你现在可能已经猜到的那样，如果没有为 <code>width</code> 和 <code>height</code> 指定初始值，则它们的初始值将被指定为 <code>0</code>。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">var</span> width, height <span class="type">int</span></span><br><span class="line">    fmt.Println(<span class="string">&quot;width is&quot;</span>, width, <span class="string">&quot;height is&quot;</span>, height)</span><br><span class="line">    width = <span class="number">100</span></span><br><span class="line">    height = <span class="number">50</span></span><br><span class="line">    fmt.Println(<span class="string">&quot;new width is&quot;</span>, width, <span class="string">&quot;new height is&quot;</span>, height)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/-4CnqDgWOKG">Run in playground</a></p><p>上面的程序会打印如下内容。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">width is 0 height is 0</span><br><span class="line">new width is 100 new height is 50</span><br></pre></td></tr></table></figure><p>在某些情况下，我们可能希望在单个语句中声明属于不同类型的变量。这样做的语法是：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> (</span><br><span class="line">    name1 = initialvalue1</span><br><span class="line">    name2 = initialvalue2</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>以下程序使用上述语法声明不同类型的变量。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">var</span> (</span><br><span class="line">        name   = <span class="string">&quot;naveen&quot;</span></span><br><span class="line">        age    = <span class="number">29</span></span><br><span class="line">        height <span class="type">int</span></span><br><span class="line">    )</span><br><span class="line">    fmt.Println(<span class="string">&quot;my name is&quot;</span>, name, <span class="string">&quot;, age is&quot;</span>, age, <span class="string">&quot;and height is&quot;</span>, height)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/7pkp74h_9L">Run in playground</a></p><p>在这里，我们声明了一个 <code>string</code> 类型的变量 <code>name</code>，类型为 <code>age</code> 的变量和 <code>height</code> 为 int 的变量。</p><p>运行以上程序将打印：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">my name is naveen , age is 29 and height is 0</span><br></pre></td></tr></table></figure><h2 id="短声明"><a href="#短声明" class="headerlink" title="短声明"></a>短声明</h2><p>Go 还提供了另一种简洁的声明变量的方法。这被称为短声明，它使用<code>:=</code> 运算符。</p><p><code>name := initialvalue</code> 是声明变量的简写语法。</p><p>以下程序使用简写形式的语法来声明变量 <code>count</code>，并将其初始化为 <code> 10</code>。Go 会自动推断 <code>count</code> 是 <code>int</code> 类型的，因为它已经用整数值 <code>10</code>初始化了。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    count := <span class="number">10</span></span><br><span class="line">    fmt.Println(<span class="string">&quot;Count =&quot;</span>,count)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/L5_8aru7VQM">Run in playground</a></p><p>以上程序将打印：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Count = 10</span><br></pre></td></tr></table></figure><p>It is also possible to declare multiple variables in a single line using short hand syntax.</p><p>你也可以使用短声明在一行内声明多个变量：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">  name, age := <span class="string">&quot;naveen&quot;</span>, <span class="number">29</span> <span class="comment">// short hand declaration</span></span><br><span class="line"></span><br><span class="line">  fmt.Println(<span class="string">&quot;my name is&quot;</span>, name, <span class="string">&quot;age is&quot;</span>, age)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/ctqgw4w6kx">Run in playground</a></p><p>上面的程序声明了两个变量，分别是类型为 <code>string</code> 的 <code>name</code> 和类型为 <code>int</code> 的 <code>age</code>。</p><p>如果运行上述程序，则可以看到 <code>my name is naveen age is 29</code> 被打印出来。</p><p>短声明要求赋值左侧所有变量的初始值。以下程序将显示错误 <code>assignment mismatch: 2 variables but 1 values</code>。这是因为 <code>age</code> 尚未分配值。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">  name, age := <span class="string">&quot;naveen&quot;</span> <span class="comment">//error</span></span><br><span class="line"></span><br><span class="line">  fmt.Println(<span class="string">&quot;my name is&quot;</span>, name, <span class="string">&quot;age is&quot;</span>, age)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/wZd2HmDvqw">Run in playground</a></p><p>仅当 <code>:=</code> 左侧至少有一个新声明的变量时，才可以使用简写语法。注意以下程序：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    a, b := <span class="number">20</span>, <span class="number">30</span> <span class="comment">// declare variables a and b</span></span><br><span class="line">    fmt.Println(<span class="string">&quot;a is&quot;</span>, a, <span class="string">&quot;b is&quot;</span>, b)</span><br><span class="line">    b, c := <span class="number">40</span>, <span class="number">50</span> <span class="comment">// b is already declared but c is new</span></span><br><span class="line">    fmt.Println(<span class="string">&quot;b is&quot;</span>, b, <span class="string">&quot;c is&quot;</span>, c)</span><br><span class="line">    b, c = <span class="number">80</span>, <span class="number">90</span> <span class="comment">// assign new values to already declared variables b and c</span></span><br><span class="line">    fmt.Println(<span class="string">&quot;changed b is&quot;</span>, b, <span class="string">&quot;c is&quot;</span>, c)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/MSUYR8vazB">Run in playground</a></p><p>在上面的程序中，在第 8 行中，已经声明了<strong>b</strong>，但是新声明了<strong>c</strong>，因此它可以工作并输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">a is 20 b is 30</span><br><span class="line">b is 40 c is 50</span><br><span class="line">changed b is 80 c is 90</span><br></pre></td></tr></table></figure><p>而如果我们运行以下程序，</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    a, b := <span class="number">20</span>, <span class="number">30</span> <span class="comment">//a and b declared</span></span><br><span class="line">    fmt.Println(<span class="string">&quot;a is&quot;</span>, a, <span class="string">&quot;b is&quot;</span>, b)</span><br><span class="line">    a, b := <span class="number">40</span>, <span class="number">50</span> <span class="comment">//error, no new variables</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/EYTtRnlDu3">Run in playground</a></p><p>它将显示错误 <code>/prog.go:8:10: no new variables on left side of :=</code> ，这是因为变量 <strong>a</strong> 和 <strong>b</strong> 已经声明，并且 <code>:=</code> 左侧没有新声明的变量。</p><p>还可以为变量分配在运行时计算的值。注意以下程序，</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">    <span class="string">&quot;math&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    a, b := <span class="number">145.8</span>, <span class="number">543.8</span></span><br><span class="line">    c := math.Min(a, b)</span><br><span class="line">    fmt.Println(<span class="string">&quot;Minimum value is&quot;</span>, c)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/Kk84pOyFgQB">Run in playground</a></p><p>在上述程序中，<a href="https://golang.org/pkg/math/">math</a> 是一个程序包，<a href="https://golang.org/pkg/math/#Min">Min</a> 是该程序包中的函数 。现在不用担心，我们将在后续的教程中详细讨论软件包和功能。我们需要知道的是，<code>c</code> 的值是在运行时计算出来的，它是 <code>a</code> 和 <code>b</code> 的最小值。 上面的程序将打印，</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Minimum value is  145.8</span><br></pre></td></tr></table></figure><p>由于 Go 是强类型的，因此不能将声明为属于一种类型的变量分配给另一种类型的值。以下程序将打印错误 <code>cannot use &quot;naveen&quot; (type string) as type int in assignment</code>，因为 <code>age</code> 被声明为类型 <code>int</code>，并且我们正在尝试为其分配 <code>string</code> 类型的值。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    age := <span class="number">29</span>      <span class="comment">// age is int</span></span><br><span class="line">    age = <span class="string">&quot;naveen&quot;</span> <span class="comment">// error since we are trying to assign a string to a variable of type int</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a href="https://play.golang.org/p/K5rz4gxjPj">Run in playground</a></p><p>谢谢阅读。 请在评论部分留下你的反馈和查询。<br>贵的反馈和意见。谢谢阅读。</p><blockquote><p>原文地址 <a href="https://golangbot.com/variables/">Variables</a><br>原文作者：<a href="https://golangbot.com/about/">Naveen Ramanathan</a><br>译文出自：<a href="https://blog.zisheng.pro/categories/%E6%B4%9B%E7%AB%B9%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/">紫升翻译计划</a></p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/019671ed10fd/</id>
    <link href="https://blog.zisheng.pro/posts/019671ed10fd/"/>
    <published>2023-03-19T07:03:00.000Z</published>
    <summary>这是我们 Golang 教程系列中的第三篇教程，它涉及 Golang 中的变量。</summary>
    <title>Go 语言教程系列の变量</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/categories/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/categories/Golang/"/>
    <category term="Go 语言系列教程" scheme="https://blog.zisheng.pro/categories/Golang/Go-%E8%AF%AD%E8%A8%80%E7%B3%BB%E5%88%97%E6%95%99%E7%A8%8B/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/tags/Golang/"/>
    <category term="Go 语言教程" scheme="https://blog.zisheng.pro/tags/Go-%E8%AF%AD%E8%A8%80%E6%95%99%E7%A8%8B/"/>
    <category term="Go 语言教程系列" scheme="https://blog.zisheng.pro/tags/Go-%E8%AF%AD%E8%A8%80%E6%95%99%E7%A8%8B%E7%B3%BB%E5%88%97/"/>
    <content>
      <![CDATA[<p>这是《Golang 教程系列》的第二篇。如果还没有，你可能需要先看一下我们的上一篇教程 <a href="https://juejin.cn/post/6942492190291525662">Golang 介绍和环境安装</a> 以了解一下 Golang 是什么以及如何下载 Golang。</p><p>这篇教程是基于 Go 1.16 以上的版本。</p><p>学习编程语言没有比让自己满手沾满代码的血🩸更有效的方法了。让我们一起开始第一个 Go 程序吧。（First Blood）</p><h2 id="设置开发环境"><a href="#设置开发环境" class="headerlink" title="设置开发环境"></a>设置开发环境</h2><p>让我们先创建一个编写 hello world 程序的文件夹。请打开命令行然后运行下面的命令：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> ~/Desktop/learngo/</span><br></pre></td></tr></table></figure><p>上面的命令将会在用户桌面创建一个名为 <code>learngo</code> 的文件夹（译者开发环境为 Macbook Pro）。你可以在任意位置创建目录编写代码。</p><h2 id="Hello-World"><a href="#Hello-World" class="headerlink" title="Hello World"></a>Hello World</h2><p>使用你喜爱的文本编辑器在 <code>learngo</code> 文件夹中创建一个名为 <code>main.go</code> 的文件并写入以下内容：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">  fmt.Println(<span class="string">&quot;Hello World&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 Go 中将包含 <code>main</code> 函数的文件命名为 <code>main.go</code> 是约定俗称的，但是其他名称也是可以使用。</p><h2 id="运行一个程序"><a href="#运行一个程序" class="headerlink" title="运行一个程序"></a>运行一个程序</h2><p>有几种不同的方式来运行 Go 程序。让我们一一看一下。</p><h3 id="1-go-install"><a href="#1-go-install" class="headerlink" title="1. go install"></a>1. <code>go install</code></h3><p>第一个运行 Go 程序的方法是使用 <code>go install</code> 命令。让我们来使用 <code>cd</code> 命令进入刚创建的 <code>learngo</code> 目录</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> ~/Desktop/learngo/</span><br></pre></td></tr></table></figure><p>接着运行下面的命令。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">go install</span><br></pre></td></tr></table></figure><p>上面的命令将编译当前程序并将其安装（拷贝）二进制可执行文件到 <code>~/go/bin</code> 目录。二进制可执行文件的名字是包含 <code>main.go</code> 文件的文件夹名。在我们示例中，它将被命名为 <code>learngo</code>。</p><p>当你尝试安装程序时，你可能会遇到以下错误。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">go: cannot find main module; see <span class="string">&#x27;go help modules&#x27;</span></span><br></pre></td></tr></table></figure><p>上面的错误实际上意味着，<code>go install</code> 无法找到 main 模块，这是因为我们没有初始化 go modules，我们使用以下命令初始化模块：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">go mod init github.com/youngjuning/learngo</span><br></pre></td></tr></table></figure><p>上面的命令会在 <code>learngo</code> 目录下创建一个 <code>go.mod</code> 文件，该文件是程序模块定义的地方，作用类似于 Node 的 <code>package.json</code> 文件。然后我再执行 <code>go install</code> 便可以成功。</p><p>你可以在命令行输入 <code>ls -al ~/go/bin/learngo</code>，然后你会发现 <code>go install</code> 实际上是把二进制可执行文件放在了 <code>~/go/bin</code> 中。</p><p>现在让我们运行编译后的二进制可执行文件。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">~/go/bin/learngo</span><br></pre></td></tr></table></figure><p>上面的命令将运行 <code>learngo</code> 并打印出以下内容：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Hello World</span><br></pre></td></tr></table></figure><p>恭喜你！你已经成功地运行了你的第一个 Go 程序。</p><p>如果你不想每次都输入完整的 <code>~/go/bin/learngo</code> 路径来运行程序，你可以添加 <code>~/go/bin/</code> 到你的 <code>PATH</code> 中。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> GOPATH=~/.go</span><br><span class="line"><span class="built_in">export</span> PATH=<span class="variable">$&#123;PATH&#125;</span>:<span class="variable">$GOPATH</span>/bin</span><br></pre></td></tr></table></figure><p>现在你可以在命令行中只输入 <code>learngo</code> 来运行程序。</p><p>你可能想知道，当 <code>learngo</code> 目录包含多个 Go 文件而不只是只有 <code>main.go</code> 时会发生什么。在这种情况下，<code>go install</code> 将如何工作？ 请继续往下看，我们将在了解软件包和 Go 模块时讨论这些内容。</p><h3 id="2-go-build"><a href="#2-go-build" class="headerlink" title="2. go build"></a>2. go build</h3><p>运行程序的第二个选项是使用 <code>go build</code>。<code>go build</code> 与 <code>go install</code> 非常相似，不同之处在于它不会将编译的二进制文件安装（拷贝）到路径 <code>~/go/bin/</code>，而是在 <code>go build</code> 所在的文件夹下创建二进制文件：</p><p>在命令行输入以下命令来切换当前目录到 <code>learngo</code>：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> ~/Desktop/learngo/</span><br></pre></td></tr></table></figure><p>然后输入下面的命令：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">go build</span><br></pre></td></tr></table></figure><p>上面的命令将会在当前目录下创建一个名为 <code>learngo</code> 的二进制可执行文件。<code>ls -al</code> 命令可以证实名为 <code>learngo</code> 的文件被创建了。</p><p>输入 <code>./learngo</code> 来运行程序，将会输入和前面一样的内容：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Hello World</span><br></pre></td></tr></table></figure><p>到此，我们用 <code>go build</code> 也成功地运行了我们的第一个 Go 程序 😁</p><h3 id="3-go-run"><a href="#3-go-run" class="headerlink" title="3. go run"></a>3. go run</h3><p>第三个运行程序的方法是使用 <code>go run</code> 命令。</p><p>在命令行输入 <code>cd ~/Desktop/learngo</code> 命令来改变当前目录为 <code>learngo</code>。</p><p>然后输入以下命令。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">go run main.go</span><br></pre></td></tr></table></figure><p>输入以上命令后，我们也可以看到一样的输出：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Hello World</span><br></pre></td></tr></table></figure><p><code>go run</code> 命令和 <code>go build</code> 或 <code>go install</code> 命令之间的一个细微差别是，<code>go run</code> 要求使用 <code>.go</code> 文件的名称作为参数。</p><p>在引擎盖下，<code>go run</code> 的工作原理与 <code>go build</code> 非常相似。无需将程序编译并安装到当前目录，而是将文件编译到一个临时位置并从该位置运行文件。如果你想知道 <code>go run</code> 将文件编译到的位置，请使用 <code>--work</code> 参数运行 <code>go run</code>。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">go run --work main.go</span><br></pre></td></tr></table></figure><p>在我的场景中，运行以上命令会输出下面的内容：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">WORK=/var/folders/mf/_fk8g5jn23gcw970pypqlv4m0000gn/T/go-build3519209434</span><br><span class="line">Hello World</span><br></pre></td></tr></table></figure><p><code>WORK</code> 的值表示程序将被编译到的一个临时位置。</p><p>就我的场景而言，程序被编译到 <code>/var/folders/mf/_fk8g5jn23gcw970pypqlv4m0000gn/T/go-build3519209434</code> 。这可能因你的情况而异 😁</p><h3 id="4-Go-Playground"><a href="#4-Go-Playground" class="headerlink" title="4. Go Playground"></a>4. Go Playground</h3><p>运行程序的最后一种方法是使用 go playground。尽管此方法有一些限制，但由于我们可以使用浏览器并且不需要在本地本地安装 Go：我已经为 Hello World 程序创建了一个 playground。 <a href="https://play.golang.org/p/oXGayDtoLPh">点击此处</a> 以在线运行该程序。</p><p>你还可以使用 Go Playground 与他人分享你的源代码。</p><p>既然我们知道4种不同的方式来运行程序，那么你可能会很困惑该使用哪种方法。答案是，当我想快速检查逻辑或找出标准库函数如何工作时，通常使用 <a href="https://play.golang.org/">playground</a>。在大多数其他情况下，我更喜欢 <code>go install</code>，因为它为我提供了从终端中任何目录运行程序的选项，因为它将所有程序编译到标准的 <code>~/go/bin/</code> 路径。</p><h2 id="对-Hello-World-程序的简短解析"><a href="#对-Hello-World-程序的简短解析" class="headerlink" title="对 Hello World 程序的简短解析"></a>对 Hello World 程序的简短解析</h2><p>这是我们刚刚创建的简单的 hello world 程序：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">  fmt.Println(<span class="string">&quot;Hello World&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们将简要讨论该程序的每一行的作用。在接下来的教程中，我们将深入研究程序的每个部分。</p><p><strong>package main</strong> - 每个 go 文件都必须以 <code>package name</code> 开始。Packages 用于提供代码分隔和可重用性。此处使用包名称 <code>main</code>。主要功能应始终保留在 main package 中。</p><p><strong>import “fmt”</strong> - <code>import</code> 语句用于导入其他软件包。在我们的例子中，<code>fmt</code> 包被导入，它将在 <code>main</code> 函数中用于将文本打印到标准输出。</p><p><strong>func main()</strong> - <code>func</code> 关键字标记函数的开始。<code>main</code> 是一个特殊函数。程序从 <code>main</code> 函数开始执行。大括号 <code>{</code> 和 <code>}</code> 表示 <code>main</code> 函数的开始和结束。</p><p><strong>fmt.Println(“Hello World”)</strong> - <code>fmt</code> 软件包的 <code>PrintIn</code> 函数用于将文本写入标准输出。<code>package.function()</code> 是在包中调用函数的语法。</p><p>本文的实例代码可以在 <a href="https://github.com/golangbot/hello">github</a> 下载。</p><blockquote><p>原文地址 <a href="https://golangbot.com/hello-world-gomod/">Hello World</a><br>原文作者：<a href="https://golangbot.com/about/">Naveen Ramanathan</a><br>译文出自：<a href="https://blog.zisheng.pro/categories/%E6%B4%9B%E7%AB%B9%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/">紫升翻译计划</a></p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/6cbf2167b038/</id>
    <link href="https://blog.zisheng.pro/posts/6cbf2167b038/"/>
    <published>2023-03-19T07:02:00.000Z</published>
    <summary>学习编程语言没有比让自己满手沾满代码的血🩸更有效的方法了。让我们一起开始第一个 Go 程序吧。</summary>
    <title>Go 语言教程系列のHello World</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/categories/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/categories/Golang/"/>
    <category term="Go 语言系列教程" scheme="https://blog.zisheng.pro/categories/Golang/Go-%E8%AF%AD%E8%A8%80%E7%B3%BB%E5%88%97%E6%95%99%E7%A8%8B/"/>
    <category term="swiper" scheme="https://blog.zisheng.pro/tags/swiper/"/>
    <category term="Golang" scheme="https://blog.zisheng.pro/tags/Golang/"/>
    <category term="Go 语言教程" scheme="https://blog.zisheng.pro/tags/Go-%E8%AF%AD%E8%A8%80%E6%95%99%E7%A8%8B/"/>
    <category term="Go 语言教程系列" scheme="https://blog.zisheng.pro/tags/Go-%E8%AF%AD%E8%A8%80%E6%95%99%E7%A8%8B%E7%B3%BB%E5%88%97/"/>
    <category term="Go 安装教程" scheme="https://blog.zisheng.pro/tags/Go-%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B/"/>
    <content>
      <![CDATA[<p>这是我们 Golang 教程系列中的第一篇教程。本教程介绍了 Go，并讨论了选择 Go 而不是其他编程语言的原因。我们还将学习如何在 Mac OS，Windows 和 Linux 中安装 Go。</p><h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>Go 也被称为 Golang，是 Google 开发的一种开源、编译和静态类型的编程语言。Go 创作背后的关键人物是 Rob Pike，Ken Thompson 和Robert Griesemer。Go 于 2009 年 11 月公开发布。</p><p>Go 是一种通用的编程语言，语法简单并具有强大的标准库作为后盾。Go 大放异彩的关键领域之一是创建高可用性和可伸缩性的 Web 应用程序。Go 还可以用于创建命令行应用程序、桌面应用程序甚至移动应用程序。</p><h2 id="Go-的优势"><a href="#Go-的优势" class="headerlink" title="Go 的优势"></a>Go 的优势</h2><p>当大量其他语言（例如 python，ruby，nodejs …）可以实现相同需求时，为什么选择 Go 作为服务器端编程语言。</p><p>这里是我选择 Go 时发现的一些优点。</p><h3 id="简单的语法"><a href="#简单的语法" class="headerlink" title="简单的语法"></a>简单的语法</h3><p>语法简单明了，并且该语言不会因不必要的功能而显得臃肿。这使得编写可读性和可维护性的代码变得容易。</p><h3 id="易于编写并发程序"><a href="#易于编写并发程序" class="headerlink" title="易于编写并发程序"></a>易于编写并发程序</h3><p>并发是语言的固有部分。直接的影响是编写多线程程序变得简直小菜一碟。这是通过 Goroutines 和管道实现的，我们将在接下来的教程中对其进行讨论。</p><h3 id="编译语言"><a href="#编译语言" class="headerlink" title="编译语言"></a>编译语言</h3><p>Go是一种编译语言。源代码被编译为本地二进制文件。 解释语言（例如，nodejs 中使用的 JavaScript 和 Python）缺少此功能。</p><h3 id="快速编译"><a href="#快速编译" class="headerlink" title="快速编译"></a>快速编译</h3><p>Go编译器非常神奇，并且从一开始就被设计为快速的。</p><h3 id="静态链接"><a href="#静态链接" class="headerlink" title="静态链接"></a>静态链接</h3><p>Go 编译器支持静态链接。整个 Go 项目可以静态链接到一个大型的二进制文件中，并且可以轻松部署在云服务器中，而不必担心依赖关系。</p><h3 id="Go-工具链"><a href="#Go-工具链" class="headerlink" title="Go 工具链"></a>Go 工具链</h3><p>工具在 Go 中值得特别提及。Go 附带了功能强大的工具，可以帮助开发人员编写更好的代码。常用的工具有：</p><ul><li><code>gofmt</code>：<code>gofmt</code> 是用于自动格式化 go 源代码的。它使用制表符进行缩进，并使用空格进行对齐。</li><li><code>vet</code>：<code>vet</code> 分析 go 源代码并报告可能的可疑代码。vet 报告的所有内容都不是真正的问题，但具有捕获编译器未报告的错误的能力，例如使用 <code>Printf</code> 时格式说明符不正确。</li><li><code>golint</code>：golint 用于识别代码中的样式问题。</li></ul><h3 id="垃圾回收"><a href="#垃圾回收" class="headerlink" title="垃圾回收"></a>垃圾回收</h3><p>Go 使用垃圾回收，因此内存管理几乎会自动执行，开发人员无需担心内存管理问题。 这也有助于轻松地编写并发程序。</p><h3 id="简单语言规范"><a href="#简单语言规范" class="headerlink" title="简单语言规范"></a>简单语言规范</h3><p>语言规范非常简单。 整个规范只有一个页面，你甚至可以使用它来编写自己的编译器 :)</p><h3 id="开源"><a href="#开源" class="headerlink" title="开源"></a>开源</h3><p>最后但并非最不重要的一点是，Go 是一个开源项目。 你可以参与 Go 项目并为该项目做出贡献。</p><h2 id="使用-Go-构建的热门产品"><a href="#使用-Go-构建的热门产品" class="headerlink" title="使用 Go 构建的热门产品"></a>使用 Go 构建的热门产品</h2><p>以下是一些使用 Go 构建的流行产品。</p><ul><li>Google 使用 Go 开发了 Kubernetes using。</li><li>Docker, 世界上最著名的容器平台是使用 Go 开发的</li><li>Dropbox 已将其性能敏感组件从 Python 迁移到 Go。</li><li>Infoblox 的 下一代网络产品是使用 Go 开发的。</li></ul><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><p>Go 可以安装在 Mac，Windows 和 Linux 这三个平台上。你可以从<a href="https://golang.org/dl/">https://golang.org/dl/</a> 下载对应平台的二进制文件。</p><h3 id="Mac-OS"><a href="#Mac-OS" class="headerlink" title="Mac OS"></a>Mac OS</h3><p>从 <a href="https://golang.org/dl/">https://golang.org/dl/</a> 下载 Mac OS 安装程序。双击开始安装。按照提示进行操作，这会将 Golang 安装在 <code>/usr/local/go</code> 中，并且还将文件夹 <code>/usr/local/go/bin</code> 添加到 <code>PATH</code> 环境变量中。</p><h3 id="Windows"><a href="#Windows" class="headerlink" title="Windows"></a>Windows</h3><p>从 <a href="https://golang.org/dl/">https://golang.org/dl/</a> 下载 MSI 安装程序。双击开始安装，然后按照提示进行操作。 这会将Go安装在位置 <code>c:\Go</code>，还将目录 <code>c:\Go\bin</code> 添加到你的路径环境变量中。</p><h3 id="Linux"><a href="#Linux" class="headerlink" title="Linux"></a>Linux</h3><p>从 <a href="https://golang.org/dl/">https://golang.org/dl/</a> 下载 tar 文件并将其解压缩到 <code>/usr/local</code>。</p><p>将 <code>/usr/local/go/bin</code> 添加到 <code>PATH</code> 环境变量中。这将在 Linux 中安装 Go。</p><h2 id="验证安装"><a href="#验证安装" class="headerlink" title="验证安装"></a>验证安装</h2><p>要验证 Go 安装是否成功，请在终端中键入命令 <code>go version</code>，它将输出已安装的 Go 版本。这是我终端的输出。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ go version</span><br><span class="line">go version go1.16 darwin/amd64</span><br></pre></td></tr></table></figure><p>在编写本教程时，1.16 是 Go 的最新版本。这证明 Go 已成功安装。在下一个教程中，我们将在 Go 中编写第一个 Hello World 程序 :)</p><p>请提供你宝贵的反馈和意见。谢谢阅读。</p><blockquote><p>原文地址 <a href="https://golangbot.com/golang-tutorial-part-1-introduction-and-installation/">Part 1: Introduction and Installation</a><br>原文作者：<a href="https://golangbot.com/about/">Naveen Ramanathan</a><br>译文出自：<a href="https://blog.zisheng.pro/categories/%E6%B4%9B%E7%AB%B9%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/">紫升翻译计划</a></p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/f6093e483bf0/</id>
    <link href="https://blog.zisheng.pro/posts/f6093e483bf0/"/>
    <published>2023-03-19T07:01:00.000Z</published>
    <summary>这是我们 Golang 教程系列中的第一篇教程。本教程介绍了 Go，并讨论了选择 Go 而不是其他编程语言的原因。我们还将学习如何在 Mac OS，Windows 和 Linux 中安装 Go。</summary>
    <title>Go 语言教程系列の介绍和环境安装</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/categories/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="人工智能" scheme="https://blog.zisheng.pro/categories/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/"/>
    <category term="AI" scheme="https://blog.zisheng.pro/tags/AI/"/>
    <category term="人工智能" scheme="https://blog.zisheng.pro/tags/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/"/>
    <category term="GPT-4" scheme="https://blog.zisheng.pro/tags/GPT-4/"/>
    <category term="ChatGpt" scheme="https://blog.zisheng.pro/tags/ChatGpt/"/>
    <content>
      <![CDATA[<p>科技世界正在庆祝最新版本的 ChatGPT-4。</p><div class="note info modern"><p>如果您还没有注册 ChatGpt，可以参考 <a href="https://blog.zisheng.pro/d744d1d3d1e8/">OpenAI 推出超神 ChatGPT 注册攻略来了</a> 快速注册一个。</p></div><p>“Chat GPT已经长大了，” 哥伦比亚大学工程教授霍德•利普森说道。</p><p>作为 AI 聊天机器人的 ChatGPT 于 2022 年 11 月诞生，现在已经走向成年，其创造者微软资助的 Open AI 已经第四次更新技术，使这个神奇的机器人变得更加智能、快速和有趣。</p><p><strong>以下是一个 ChatGP T的笑话：</strong></p><ul><li>Madonna 为什么要过马路？<blockquote><p>去马路的另一边摆 POSE（strike a pose）。</p></blockquote></li></ul><p>GPT-3 能够写诗、解决问题和总结文本，但人们对其准确性存在担忧。而 GPT-4 解决了这些问题。事实上，它在 GRE、MCAT、LSAT 和 SAT 等多项测试中表现优异，甚至还可以理解图片。</p><p>“有人试过这个功能，他们给 ChatGPT 展示了我的冰箱照片，问我晚餐可以做什么？” Lipson 说。“看到酸奶和草莓后，ChatGPT 建议制作一份完美的比萨饼。这对于 AI 来说是一个重大突破。”</p><p>它可以翻译、计算和编码。对于没有医生资源的人来说，ChatGPT 的知识和专业可以拯救生命。但是，尽管 ChatGPT 带来了很多好处，技术专家们承认这种原始智能是一把双刃剑，如果被恶意利用，可能会造成不可预测的后果。</p><p>“你可以创建垃圾邮件，你可以创建非常令人信服的电子邮件，你可以欺骗人们。”Lipson 警告说。“我认为你可以使用这项技术来设计一些东西，也许我们不应该去设计和制造。”</p><p>ChatGPT-4 版本的训练速度更快，并且新的升级将不断推出。尽管非常先进，AI 仍不能理解并回应肢体语言、八卦或什么都没谈的闲聊。但是ChatGPT 4.0 版本显示，AI 的演化是指数级的。</p><p>“这既令人兴奋又令人恐惧，我必须承认。” Lipson 说。</p><blockquote><p>原文地址：<a href="https://www.fox5ny.com/news/chat-gpt-4-is-here-will-it-change-our-lives">Chat GPT-4 is here - Will it change our lives?</a><br>原文作者：<a href="https://www.fox5ny.com/person/r/ashlie-rodriguez">Ashlie Rodriguez</a><br>译文出自：<a href="https://blog.zisheng.pro/categories/%E6%B4%9B%E7%AB%B9%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/">紫升翻译计划</a></p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/197cfff33155/</id>
    <link href="https://blog.zisheng.pro/posts/197cfff33155/"/>
    <published>2023-03-17T06:14:20.000Z</published>
    <summary>Chat GPT-4 被誉为改变游戏规则的技术，从许多方面来看它确实是。它在难题标准测试中击败人类，但它和其他 AI 工具将如何影响我们的生活呢？ FOX 5 纽约的 Ashlie Rodriguez 采访了专家，以了解一些情况。</summary>
    <title>Chat GPT-4 已经问世——它会改变我们的生活吗？</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="Google" scheme="https://blog.zisheng.pro/categories/Google/"/>
    <category term="大陆" scheme="https://blog.zisheng.pro/tags/%E5%A4%A7%E9%99%86/"/>
    <category term="Google Account" scheme="https://blog.zisheng.pro/tags/Google-Account/"/>
    <category term="科学上网" scheme="https://blog.zisheng.pro/tags/%E7%A7%91%E5%AD%A6%E4%B8%8A%E7%BD%91/"/>
    <category term="谷歌账号" scheme="https://blog.zisheng.pro/tags/%E8%B0%B7%E6%AD%8C%E8%B4%A6%E5%8F%B7/"/>
    <content>
      <![CDATA[<p>最近注册 Google 账号卡在了验证手机号码的步骤，应该是 Google 更新了他们的合规政策，具体报错如下图：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1678987015915.png" alt="此电话号码无法用于进行验证"></p><h1 id="切换-VPN-到美国"><a href="#切换-VPN-到美国" class="headerlink" title="切换 VPN 到美国"></a>切换 VPN 到美国</h1><p>为了模拟网络 IP，我们最好将 VPN 节点切到 Google 的总部所在国美国。紫升收藏了一些好用的 <a href="/vpn/">科学上网</a> 工具，有需要可以试用。</p><h1 id="切换浏览器时区"><a href="#切换浏览器时区" class="headerlink" title="切换浏览器时区"></a>切换浏览器时区</h1><p>只切换 VPN 节点，我们还是无法成功验证手机号，必须将时区设置成和 VPN 节点一致才可以，使用 <a href="https://whoer.net/">whoer.net</a> 可以看到当前时区不一致：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1678987547217.png" alt="紫升"></p><p>使用浏览器扩展 <a href="https://chrome.google.com/webstore/detail/change-timezone-time-shif/nbofeaabhknfdcpoddmfckpokmncimpj?utm_source=chrome-ntp-icon">Change Timezone (Time Shift)</a> 我们可以很方便地切换时区：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1678987441408.png" alt="紫升"></p><h1 id="修改浏览器语言"><a href="#修改浏览器语言" class="headerlink" title="修改浏览器语言"></a>修改浏览器语言</h1><p>将浏览器的语言设置成 英语（美国），然后删除其他语言：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1678988750775.png" alt="紫升"></p><p>然后再用 <a href="https://whoer.net/">whoer.net</a> 检查一下：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1678987676196.png" alt="紫升"></p><p>这个时候你就可以成功注册 Google 了。</p><h1 id="跨浏览器测试平台"><a href="#跨浏览器测试平台" class="headerlink" title="跨浏览器测试平台"></a>跨浏览器测试平台</h1><p>如果以上还不行，那就是 VPN 节点不够纯净，可以使用 <a href="https://saucelabs.com/">sauce labs</a> 开一个浏览器注册。</p><h1 id="扩展知识"><a href="#扩展知识" class="headerlink" title="扩展知识"></a>扩展知识</h1><p>以前无知，以为 ip 地址改了身份就变了，实际能标识一个用户的方面有很多，比如：</p><ul><li>浏览器 Cookie</li><li>浏览器默认 Track</li><li>浏览器历史记录</li><li>浏览器搜索历史</li><li>电脑设置</li><li>电脑分辨率</li><li>浏览器语言</li></ul><p>校验严格的网站会禁止禁用 Cookie，不带 Cookie 或者网络是匿名的都会认为是异常用户，另外批量处理很容易触发封号，最近互关 Twitter，手动点的多了就被锁账号了。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/382c92b68823/</id>
    <link href="https://blog.zisheng.pro/posts/382c92b68823/"/>
    <published>2023-03-16T16:50:37.000Z</published>
    <summary>本文介绍了 2023 年大陆人如何成功创建一个新的 Google 账户，完美解决“此电话号码无法用于进行验证”。</summary>
    <title>2023 大陆人如何成功创建 Google 账户</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="Mac" scheme="https://blog.zisheng.pro/categories/Mac/"/>
    <category term="Mac" scheme="https://blog.zisheng.pro/tags/Mac/"/>
    <category term="VPN" scheme="https://blog.zisheng.pro/tags/VPN/"/>
    <category term="卸载软件" scheme="https://blog.zisheng.pro/tags/%E5%8D%B8%E8%BD%BD%E8%BD%AF%E4%BB%B6/"/>
    <category term="终端" scheme="https://blog.zisheng.pro/tags/%E7%BB%88%E7%AB%AF/"/>
    <category term="苹果电脑" scheme="https://blog.zisheng.pro/tags/%E8%8B%B9%E6%9E%9C%E7%94%B5%E8%84%91/"/>
    <content>
      <![CDATA[<p>公司用的 VPN 在前一段时间强制给电脑添加了全屏水印，但是我的个人电脑为了随时支持，也安装了内网 VPN，这个水印是不能去掉的，所以我就想着卸载这个软件，但是卸载的时候就报错了，不能完成此操作，xxx 已锁定，这个时候我们就需要使用终端来卸载这些软件了。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1678980050589.png" alt="不能完成此操作,xxx 已锁定"></p><h1 id="第一步"><a href="#第一步" class="headerlink" title="第一步"></a>第一步</h1><blockquote><p>注意：请替换 xxx 为要删除的 app</p></blockquote><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/bin/ls -dleO@ /Applications/xxx.app</span><br></pre></td></tr></table></figure><h1 id="第二步"><a href="#第二步" class="headerlink" title="第二步"></a>第二步</h1><blockquote><p>注意：请替换 xxx 为要删除的 app</p></blockquote><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo /usr/bin/chflags -R noschg /Applications/xxx.app</span><br></pre></td></tr></table></figure><p>这两行命令执行完后，发现 logo 上的小锁消失了，于是开开心心的单击右键，移除，解决。</p><h1 id="第三步"><a href="#第三步" class="headerlink" title="第三步"></a>第三步</h1><p>卸载了之后，这流氓软件还是会存在一个杀不死的 xxxAgent 进程，真是醉了。脑残的安全部门，不能给个一键卸载的选项吗？</p><p>参考 <a href="https://www.jianshu.com/p/68014c7e5210">mac 解决kill无法强制杀死后台程序的问题</a>，直接删除文件夹，恼人的水印就没有了。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo <span class="built_in">rm</span> -rf /opt/.yunshu/</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/a4996cdebca4/</id>
    <link href="https://blog.zisheng.pro/posts/a4996cdebca4/"/>
    <published>2023-03-16T15:15:12.000Z</published>
    <summary>很多公司的内网 VPN 软件都是需要管理员权限才能安装的，但是有时候我们需要卸载这些软件，但是卸载的时候会报错，不能完成此操作，xxx 已锁定，这个时候我们就需要使用终端来卸载这些软件了。</summary>
    <title>「已解决」Mac 电脑删除软件报 “不能完成此操作,xxx 已锁定”</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="Google" scheme="https://blog.zisheng.pro/categories/Google/"/>
    <category term="Adsense" scheme="https://blog.zisheng.pro/categories/Google/Adsense/"/>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/categories/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="站点运营" scheme="https://blog.zisheng.pro/categories/%E7%AB%99%E7%82%B9%E8%BF%90%E8%90%A5/"/>
    <category term="广告" scheme="https://blog.zisheng.pro/categories/%E7%AB%99%E7%82%B9%E8%BF%90%E8%90%A5/%E5%B9%BF%E5%91%8A/"/>
    <category term="AdSenseCTR" scheme="https://blog.zisheng.pro/tags/AdSenseCTR/"/>
    <category term="Google AdSense" scheme="https://blog.zisheng.pro/tags/Google-AdSense/"/>
    <category term="点击率" scheme="https://blog.zisheng.pro/tags/%E7%82%B9%E5%87%BB%E7%8E%87/"/>
    <category term="广告发布商" scheme="https://blog.zisheng.pro/tags/%E5%B9%BF%E5%91%8A%E5%8F%91%E5%B8%83%E5%95%86/"/>
    <category term="广告联盟" scheme="https://blog.zisheng.pro/tags/%E5%B9%BF%E5%91%8A%E8%81%94%E7%9B%9F/"/>
    <content>
      <![CDATA[<p>在互联网营销领域, 点击率对广告商和发布者都非常重要。广告商需要创建广告图形或销售线路，从而获得更高的点击率 (CTR)，而发布者则需要以增加 CTR 的方式放置广告。</p><p>我看到很多人在各个论坛上询问这个问题，所以本篇文章将有助于许多不了解点击率是什么的读者。</p><p>从博客&#x2F;出版商的角度来看，CTR是一项在赚取金钱和留住广告商方面发挥重要作用的因素之一。如果您无法给足够的点击，您的广告商就会离开。那么，让我们深入了解CTR并了解它是什么。</p><h1 id="什么是-CTR"><a href="#什么是-CTR" class="headerlink" title="什么是 CTR"></a>什么是 CTR</h1><p>CTR 代表点击率（Click through rate）。简而言之，它的意思是广告被点击的次数除以广告、广告单元或包含广告的页面被查看的次数。</p><ul><li>CTR &#x3D; 点击数 &#x2F; 展示次数</li></ul><p>简单来说，如果您发布了一则广告，并被 100 人看到，但只得到了 5 次点击，那么您的 CTR 为 5%。需要注意的是，CTR百分比越高，你将获得更好的广告效果。因此，建议在易于点击和可见的地方放置广告。现在，最常见的问题之一是什么是良好的 CTR 或者我的 Adsense 有足够的点击量，但我的收入非常低怎么办。</p><p>让我来揭开关于 Adsense 点击率的迷思，过多地点击 AdSense 广告永远不能保证良好的收入，关键在于针对能给予高带权值广告的点击。这样的广告，“即使很少点击也会产生比 100 次点击更多的收益”。我将继续发布更多有关 Google Adsense 的主题，让您的 Adsense 体验更佳。如果您想不错过任何内容，请订阅 <a href="https://blog.zisheng.pro/atom.xml">紫升的博客</a> 。</p><blockquote><p>原文地址：<a href="https://www.shoutmeloud.com/what-is-ctr-in-google-adsense.html">What is CTR in Google Adsense?</a><br>原文作者：<a href="https://www.shoutmeloud.com/author/admin">Harsh Agrawal</a><br>译文出自：<a href="https://blog.zisheng.pro/categories/%E6%B4%9B%E7%AB%B9%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/">紫升翻译计划</a></p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/e7eb0384452b/</id>
    <link href="https://blog.zisheng.pro/posts/e7eb0384452b/"/>
    <published>2023-03-16T12:38:10.000Z</published>
    <summary>CTR代表点击率（Click Though rate）。简单来说，它表示广告被点击的次数除以广告被浏览的次数。</summary>
    <title>Google Adsense 的 CTR 是什么？</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="issues" scheme="https://blog.zisheng.pro/categories/issues/"/>
    <category term="antd" scheme="https://blog.zisheng.pro/categories/issues/antd/"/>
    <category term="React" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React/"/>
    <category term="前端开发" scheme="https://blog.zisheng.pro/tags/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    <category term="antd" scheme="https://blog.zisheng.pro/tags/antd/"/>
    <category term="descriptions" scheme="https://blog.zisheng.pro/tags/descriptions/"/>
    <category term="antd descriptions" scheme="https://blog.zisheng.pro/tags/antd-descriptions/"/>
    <category term="Descriptions" scheme="https://blog.zisheng.pro/tags/Descriptions/"/>
    <content>
      <![CDATA[<h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><p>在使用 antd 的 Descriptions 组件时，发现 label 后面总是有一个冒号，如下图所示：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1678952578255.png" alt="antd descriptions label"></p><p>代码如下：</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;<span class="title class_">Descriptions</span> title=<span class="string">&#x27;类型统计&#x27;</span> layout=<span class="string">&quot;vertical&quot;</span> column=&#123;<span class="number">4</span>&#125;&gt;</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">Descriptions.Item</span> <span class="attr">label</span>=<span class="string">&quot;点类型统计&quot;</span>&gt;</span>73<span class="tag">&lt;/<span class="name">Descriptions.Item</span>&gt;</span></span></span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">Descriptions.Item</span> <span class="attr">label</span>=<span class="string">&quot;边类型统计&quot;</span>&gt;</span>29<span class="tag">&lt;/<span class="name">Descriptions.Item</span>&gt;</span></span></span><br><span class="line">&lt;/<span class="title class_">Descriptions</span>&gt;</span><br></pre></td></tr></table></figure><p>正常来说是没问题的，但是 UI 看着不顺眼，走查的时候要求去掉，但是发现没有相关属性可以配置。</p><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><p>直接改 antd 的 css 样式来隐藏即可：</p><figure class="highlight less"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">:<span class="selector-tag">global</span> &#123;</span><br><span class="line">  <span class="selector-class">.ant-descriptions-item-label</span><span class="selector-pseudo">::after</span> &#123;</span><br><span class="line">    <span class="attribute">content</span>: <span class="string">&#x27;&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="扩展"><a href="#扩展" class="headerlink" title="扩展"></a>扩展</h2><h3 id="自定义-label-和-content-的-padding"><a href="#自定义-label-和-content-的-padding" class="headerlink" title="自定义 label 和 content 的 padding"></a>自定义 label 和 content 的 padding</h3><p>同样是因为 UI 走查看不顺眼，要求把 label 和 content 的 padding 值改为 4px，可以通过修改 antd 的样式来实现：</p><figure class="highlight less"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.ant-descriptions-row</span> &gt; <span class="selector-tag">th</span>, <span class="selector-class">.ant-descriptions-row</span> &gt; <span class="selector-tag">td</span> &#123;</span><br><span class="line">  <span class="attribute">padding-bottom</span>: <span class="number">4px</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/6ff432235df3/</id>
    <link href="https://blog.zisheng.pro/posts/6ff432235df3/"/>
    <published>2023-03-16T07:41:41.000Z</published>
    <summary>本文介绍了如何隐藏 antd Descriptions 组件 label 后面的冒号。</summary>
    <title>Antd Descriptions 组件隐藏 label 的冒号</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="Eslint" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/Eslint/"/>
    <category term="Eslint" scheme="https://blog.zisheng.pro/tags/Eslint/"/>
    <category term="Typescript" scheme="https://blog.zisheng.pro/tags/Typescript/"/>
    <category term="import/no-unresolved" scheme="https://blog.zisheng.pro/tags/import-no-unresolved/"/>
    <content>
      <![CDATA[<p>我在使用 eslint 检查 typescript 代码时，遇到了一个问题，就是无法解析路径到模块（Unable to resolve path to module）：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> app <span class="keyword">from</span> <span class="string">&#x27;./app&#x27;</span>;</span><br></pre></td></tr></table></figure><p>报错：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">2:17  error  Unable to resolve path to module <span class="string">&#x27;./app&#x27;</span>  import/no-unresolved</span><br></pre></td></tr></table></figure><p>这个问题的解决步骤如下：</p><p>1、设置 parserOptions：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// .eslintrc.json</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;parser&quot;</span>: <span class="string">&quot;@typescript-eslint/parser&quot;</span>,</span><br><span class="line">  <span class="string">&quot;parserOptions&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;project&quot;</span>: <span class="string">&quot;./tsconfig.json&quot;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>2、设置 <code>import/resolver</code>：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// .eslintrc.json</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;settings&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;import/resolver&quot;</span>: &#123;</span><br><span class="line">      <span class="string">&quot;node&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;extensions&quot;</span>: [<span class="string">&quot;.js&quot;</span>, <span class="string">&quot;.jsx&quot;</span>, <span class="string">&quot;.ts&quot;</span>, <span class="string">&quot;.tsx&quot;</span>]</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/a9cf5ec2f94c/</id>
    <link href="https://blog.zisheng.pro/posts/a9cf5ec2f94c/"/>
    <published>2023-03-14T06:18:48.000Z</published>
    <summary>我在使用 eslint 检查 typescript 代码时，遇到了一个问题，就是无法解析路径到模块（Unable to resolve path to module）</summary>
    <title>「已解决」Using eslint with typescript - Unable to resolve path to module</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="测试" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/%E6%B5%8B%E8%AF%95/"/>
    <category term="前端测试" scheme="https://blog.zisheng.pro/tags/%E5%89%8D%E7%AB%AF%E6%B5%8B%E8%AF%95/"/>
    <category term="跨浏览器测试" scheme="https://blog.zisheng.pro/tags/%E8%B7%A8%E6%B5%8F%E8%A7%88%E5%99%A8%E6%B5%8B%E8%AF%95/"/>
    <category term="cross browser testing" scheme="https://blog.zisheng.pro/tags/cross-browser-testing/"/>
    <category term="browserstack" scheme="https://blog.zisheng.pro/tags/browserstack/"/>
    <category term="saucelabs" scheme="https://blog.zisheng.pro/tags/saucelabs/"/>
    <category term="testcafe" scheme="https://blog.zisheng.pro/tags/testcafe/"/>
    <category term="selenium" scheme="https://blog.zisheng.pro/tags/selenium/"/>
    <content>
      <![CDATA[<h2 id="Lambdatest"><a href="#Lambdatest" class="headerlink" title="Lambdatest"></a>Lambdatest</h2><p>Lambdatest 是一种基于云的交叉浏览器测试平台，可以同时在不同操作系统和浏览器上执行自动化和手动测试。该平台支持多种编程语言和集成工具，提供实时屏幕共享和错误日志，以帮助开发人员和测试人员加快产品交付速度和质量。此外，Lambdatest 还提供用户友好的界面和灵活的付费计划，以适应各种规模和类型的项目需求。</p><h3 id="功能"><a href="#功能" class="headerlink" title="功能"></a>功能</h3><ol><li>跨浏览器测试：LambdaTest允许用户在不同的操作系统、浏览器和设备上进行测试。这使得用户可以确保他们的应用程序能够在不同的环境中正常运行。</li><li>实时测试：LambdaTest的实时测试功能允许用户在真实浏览器和实际设备上测试他们的应用程序，而不是使用模拟器或虚拟机。</li><li>自动化测试：LambdaTest集成了多个自动化测试框架，包括Selenium和Appium。这使得用户可以更快速地执行测试和获得更好的覆盖率。</li><li>特性测试：LambdaTest的特性测试功能允许用户手动、自动化地测试Web应用程序的各种特性，例如像登陆、下拉菜单、滚动等方面 。</li><li>并行测试：LambdaTest提供了并行执行测试用例的功能，使用户可以在较短的时间内完成更多的测试。</li><li>Bug 捕获工具：LambdaTest配备了一套强大的Bug记录和管理工具，帮助用户轻松捕获和管理其测试中发现的所有Bug以便追溯，解决问题。</li><li>高级安全功能：LambdaTest提供高级安全功能来保护用户敏感数据和测试结果的安全。它还包括私人网络和VPN，以确保用户的数据得到保护和安全传输。</li></ol><h3 id="优势"><a href="#优势" class="headerlink" title="优势"></a>优势</h3><ul><li>适用于各种浏览器和操作系统：Lambdatest支持超过2000种不同类型的浏览器和操作系统，您可以在任何设备上使用该平台进行测试。</li><li>实施方便：使用Lambdatest进行测试非常容易。只需上传您想要测试的代码和测试脚本，并选择需要测试的配置，Lambdatest将处理其余部分。</li><li>高度安全：Lambdatest采用SSL &#x2F; TLS协议来保护用户敏感数据，确保最高级别的数据安全性。</li><li>支持自动化测试：您可以使用Lambdatest执行自动化测试，它与各种自动化框架和库兼容。</li><li>提供实时测试环境：Lambdatest提供实时测试环境，让您可以立即查看测试结果。这意味着您可以即时检测到错误并加以纠正，并可从开始就更好地了解应用程序的表现。</li><li>管理测试项目的方便：Lambdatest提供一个简单而直观的用户界面，使您能够轻松管理测试项目。您可以随时查看测试进度并分享测试结果。</li><li>提供竞争力价值：Lambdatest价格合理，为小型企业和初创公司提供了测试功能。</li></ul><h2 id="SauceLabs"><a href="#SauceLabs" class="headerlink" title="SauceLabs"></a>SauceLabs</h2><p>SauceLabs 是一种基于云平台的 Web 和 移动应用自动化测试工具，它提供了一种简单有效的方法来测试 web 应用和移动应用在不同操作系统、浏览器和设备上的表现。SauceLabs 平台可以在不同的环境中执行不同的自动化测试，例如独立脚本的支持、多个浏览器的同时支持等等。在 SauceLabs 中，您只需编写一次自动化脚本即可实现在 iOS、Android、Windows 或 Mac 操作系统上运行的浏览器中进行测试。</p><h3 id="功能-1"><a href="#功能-1" class="headerlink" title="功能"></a>功能</h3><ol><li>Cross Browser 测试：SauceLabs 允许用户同时在多个真实的浏览器和版本中运行测试，确保 Web 应用程序的兼容性。</li><li>Parallel Testing：SauceLabs 的分布式体系结构允许用户并发运行大量测试用例以获得更快的测试结果。</li><li>操作系统覆盖：SauceLabs 支持 Web 和移动应用程序测试中的所有流行操作系统 (OS) 平台。</li><li>设备模拟器和仿真：Sauce Labs 集成了模拟或模拟真实移动设备和桌面浏览器。</li><li>设备暴露：用户可以使用 SauceLabs 的套件将真实的物理 iOS 和 Android 设备连接到测试过程中。</li><li>CI&#x2F;CD 整合: SauceLabs 与常用的 CI&#x2F;CD 工具集成，包括 Jenkins、Travis CI、CircleCI 等等。</li><li>Debugging tools：SauceLabs 提供了一组调试工具帮助开发人员诊断问题，如截屏、日志、视频录制等。</li></ol><h3 id="优势-1"><a href="#优势-1" class="headerlink" title="优势"></a>优势</h3><p>SauceLabs 相比其他测试平台，并提供以下优势：</p><ul><li>可扩展性：随着公司团队的扩大，测试需要跟上发展的规模。SauceLabs支持可伸缩的云体系结构，允许以适合各种规模的任何一个团队或需求。</li><li>跨平台：企业对不同平台的支持要求不断增加（Windows、macOS、Linux、iOS、Android，甚至 IoT 设备），而不同的平台走向不同的趋势使得测试任务变得复杂。Sauce Labs 可以在各种浏览器和操作系统平台上执行测试，整合多个软硬件环境。</li><li>时效性：SauceLabs 可以启动无限的浏览器会话，在不需等待时间的情况下，立即在平行板上执行自动化测试，以便追求最短的周期时间（cycle time）。</li><li>成本节约：传统的广泛测试需要先从公司的测试设施或云提供商购买硬件和设备，然后运行测试来修复错误。 SauceLabs 的云测试服务减少了购买，部署，配置以及其相关管理费用。</li><li>安全性：企业级客户通常对代码库拥有完全访问权限，这意味着代码可能受到各种威胁。Sauce Labs 为每个客户专门提供虚拟机，并用于隔离和安全管控原产生的风险，确保数据和安全性。</li><li>运营可见性：Sauce Labs 提供内置的报告，运营数据和自定义仪表板来跟踪测试性能和缺陷趋势，以便全面评估测试资料的质量水平。</li></ul><h2 id="套餐对比"><a href="#套餐对比" class="headerlink" title="套餐对比"></a>套餐对比</h2><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1678943419739.png" alt="跨浏览器测试平台套餐对比"></p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/0e5e9e31e73b/</id>
    <link href="https://blog.zisheng.pro/posts/0e5e9e31e73b/"/>
    <published>2023-03-14T05:41:49.000Z</published>
    <summary>本文推荐了十个 2023 年最好用的跨浏览器测试工具</summary>
    <title>2023 年最好用的两大跨浏览器测试工具</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="TypeScript" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/TypeScript/"/>
    <category term="EsLint" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/EsLint/"/>
    <category term="React" scheme="https://blog.zisheng.pro/tags/React/"/>
    <category term="TypeScript" scheme="https://blog.zisheng.pro/tags/TypeScript/"/>
    <category term="Eslint" scheme="https://blog.zisheng.pro/tags/Eslint/"/>
    <category term="airbnb" scheme="https://blog.zisheng.pro/tags/airbnb/"/>
    <category term="enum" scheme="https://blog.zisheng.pro/tags/enum/"/>
    <content>
      <![CDATA[<h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><p>我正在使用 TypeScript 和 React 进行开发。这个项目是通过 CRA 生成的，并且我在 <code>.eslintrc.json</code> 中使用了 react-app 的配置。然而，我决定使用 <code>eslint-config-airbnb</code>，因此我添加了它并更改了我的 <code>.eslintrc.json</code> 文件。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;extends&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;airbnb&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>但是，我当我定义一个 <code>enum</code> 时，Eslint 抛出了 <code>Parsing error: The keyword enum is reserved</code> 的错误。</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">`Parsing error: The keyword &#x27;enum&#x27; is reserved eslint`</span></span><br></pre></td></tr></table></figure><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><p>当你使用 <code>eslint-config-airbnb</code> 时，你需要安装 <code>@typescript-eslint/parser</code> 和 <code>@typescript-eslint/eslint-plugin</code>。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn add @typescript-eslint/parser -D</span><br></pre></td></tr></table></figure><p>然后，你需要在 <code>.eslintrc.json</code> 中添加以下内容：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;parser&quot;</span><span class="punctuation">:</span> <span class="string">&quot;@typescript-eslint/parser&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;plugins&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;@typescript-eslint&quot;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>此外你可能还需要安装 <code>eslint-plugin-import</code>、<code>eslint-plugin-react</code>、<code>eslint-plugin-jsx-a11y</code></p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn add eslint-plugin-import eslint-plugin-react eslint-plugin-jsx-a11y -D</span><br></pre></td></tr></table></figure><p>然后，你需要在 <code>.eslintrc.json</code> 中添加以下内容：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;plugins&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;react&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;jsx-a11y&quot;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="推荐方案"><a href="#推荐方案" class="headerlink" title="推荐方案"></a>推荐方案</h2><p>直接使用 <a href="https://www.npmjs.com/package/eslint-config-airbnb-typescript">eslint-config-airbnb-typescript</a> 快速配置。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/2b120c3058c6/</id>
    <link href="https://blog.zisheng.pro/posts/2b120c3058c6/"/>
    <published>2023-03-14T04:10:32.000Z</published>
    <summary>本文介绍了如何解决 Eslint 警告 Parsing error: The keyword 'enum' is reserved</summary>
    <title>「已解决」Parsing error: The keyword 'enum' is reserved</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/categories/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="React" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React/"/>
    <category term="React" scheme="https://blog.zisheng.pro/tags/React/"/>
    <category term="React Router" scheme="https://blog.zisheng.pro/tags/React-Router/"/>
    <category term="SPA" scheme="https://blog.zisheng.pro/tags/SPA/"/>
    <category term="单页应用程序" scheme="https://blog.zisheng.pro/tags/%E5%8D%95%E9%A1%B5%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F/"/>
    <content>
      <![CDATA[<!-- DRAFT 使用 React Router 优化单页应用程序（SPAs）  --><p>React 通常用于构建单页面应用程序（SPA）。 SPA 往往具有多个页面视图。当从一个页面视图导航到另一个页面视图时，重新加载整个页面视图是一项繁琐且不那么高效的任务。实际上，它会削弱 SPA 的优势。为了正常工作，SPA 必须在需要时呈现部分视图，而不是重新加载整个页面。</p><p>在单页应用程序（SPA）中从一个页面导航到另一个页面时，路由起着重要作用。路由可以分为静态和动态两种。SPA应用程序采用动态路由方式。<strong>在这个教程中，我们将会讨论一种流行的和React应用程序一起使用的路由库，它被称为 <a href="https://reacttraining.com/react-router/web/guides/quick-start">React Router</a></strong>。</p><details class="toggle" ><summary class="toggle-button" style="">原文</summary><div class="toggle-content"><p>React is often used for building single page applications (SPAs). SPAs tend to have multiple page views. When navigating from one-page view to another, reloading the entire page view is a tedious and not so efficient task. In fact, it diminishes the benefits of a SPA. To work as it should, a SPA must render parts of views when required instead of reloading the entire page.</p><p>Routing comes into the picture when navigating from one page to another in a SPA app. Routing can be categorized in two ways. Static and dynamic. SPAs follow dynamic approach. <strong>In this tutorial, we will discuss a popular routing library used with React applications known as <a href="https://reacttraining.com/react-router/web/guides/quick-start">React Router</a></strong>.</p><h1 id="Requirements"><a href="#Requirements" class="headerlink" title="Requirements"></a>Requirements</h1><ul><li>NodeJS v8.x.x or higher installed along with npm&#x2F;yarn</li><li>create-react-app installed globally to on your local dev machine generate a new React project</li></ul><p>Bonus: You can also, use npx to generate a new React project without installing create-react-app.</p><h1 id="Getting-Started"><a href="#Getting-Started" class="headerlink" title="Getting Started"></a>Getting Started</h1><p>To create a new React project run the following command at the desired location on your local machine.</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">create-react-app react-router-v4-demo</span><br></pre></td></tr></table></figure><p>Once the project is generated, traverse inside the newly created directory. This strategy is the default way to generate a new React app.</p><p>React Router as a library contains three different npm packages.</p><ul><li>react-router</li><li>react-router-dom</li><li>react-router-native</li></ul><p>Each of the packages has a different use case. The first one, <code>react-router</code> is the core package and is used with the next two packages listed above. The <code>react-router-dom</code> has to be used when building a web application. This is what we are going to use in this tutorial. The last one, <code>react-router-native</code> tends to be used in a <a href="https://medium.com/crowdbotics/how-to-build-a-real-time-logo-detection-app-with-react-native-google-vision-api-and-crowdbotics-9ed65fbcd15">React Native</a> application.</p><p>To add React Router in the React app, execute the following command from a terminal window.</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn add react-router-dom</span><br></pre></td></tr></table></figure><p>Please note that, for the rest of the tutorial, we will be using yarn as the JavaScript package manager to add or remove dependencies. If you wish to use npm, there is no one stopping you.</p><p>To run the React app, go to the terminal window and execute the command npm start. This will start the development server. You will be welcomed by the default boilerplate project screen in a web browser window on URL <code>http://localhost:3000/</code>.</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1678720760813.png" alt="Edit src&#x2F;App.js and save to reload"></p><h1 id="First-Route-with-React-Router"><a href="#First-Route-with-React-Router" class="headerlink" title="First Route with React Router"></a>First Route with React Router</h1><p>To create the first route in the React app, let us import BrowserRouter from react-router library.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&quot;react&quot;</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">BrowserRouter</span> <span class="keyword">as</span> <span class="title class_">Router</span>, <span class="title class_">Route</span> &#125; <span class="keyword">from</span> <span class="string">&quot;react-router-dom&quot;</span></span><br></pre></td></tr></table></figure><p>To create a route, we use <code>&lt;Route&gt;</code> from the react-router-dom. This is where the logic of routing is placed. It renders the UI of a component. A <code>&lt;Route&gt;</code> has a prop called path which is always matched with the current location of the app. On the basis of this prop, the desired component gets rendered. When the component is not getting rendered, Routereturns null. The component name is also passed as prop component. Look at the below snippet.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">Router</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">&#x27;/&#x27;</span> <span class="attr">component</span>=<span class="string">&#123;Home&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">Router</span>&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>There is the functional App component that returns a BrowserRouter which holds the very first Route component. The path is currently pointing towards the Home component which has the following UI logic.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Home</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">        <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">h1</span>&gt;</span>Home Component<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">    )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Now, visit the URL on port 3000 and you will see the Home component being rendered right now.</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1678721815386.png" alt="紫升"></p><p>This is a bare minimum example. Now let us add another route with the same props like the Home. Call this route About with a similar rendering logic as Home.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">About</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h1</span>&gt;</span>About Component<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Now add this functional component as the second route, below the Home route. Also, add both routes inside a div element. A router component can hold a single child element and adding a div solves this problem and allows the router component to have as many children as we want.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">Router</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">&#x27;/&#x27;</span> <span class="attr">component</span>=<span class="string">&#123;Home&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">&#x27;/about&#x27;</span> <span class="attr">component</span>=<span class="string">&#123;About&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">Router</span>&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Try visiting the URL <code>http://localhost:3000/about</code>. You will notice that both the components are being rendered right now on the path <code>/about</code>.</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1678721636017.png" alt="紫升"></p><p>The reason for this is that regular expression engine that React Router uses internally considers both the routes that are being started with a forward slash &#x2F; equal. To solve this issue, we can use another essential prop on the Home route called exact.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&lt;<span class="title class_">Router</span>&gt;</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">&#x27;/&#x27;</span> <span class="attr">exact</span> <span class="attr">component</span>=<span class="string">&#123;Home&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">&#x27;/about&#x27;</span> <span class="attr">component</span>=<span class="string">&#123;About&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">&lt;/<span class="title class_">Router</span>&gt;</span><br></pre></td></tr></table></figure><p>This exact is also known as a qualifier which states that the path must match the exactly the &#x2F; and nothing after it, such as <code>/about</code> Now, if you visit the browser window at the URL <code>http://localhost:3000/about</code> you will notice that only the about component is getting rendered this time.</p><h1 id="What-is-BrowserRouter"><a href="#What-is-BrowserRouter" class="headerlink" title="What is BrowserRouter?"></a>What is BrowserRouter?</h1><p>Do you remember reading earlier about that <code>react-router-dom</code> is used only in case of web applications? Well, <code>react-router-dom</code> library holds two types of routers API for a React application to use. One is called BrowserRouter that you have seen in action in the previous section. The other one is called HashRouter.</p><p>A BrowserRouter will always listen to URLs like: <code>http://localhost:3000/about</code> whereas a HashRouter will have <code>http://localhost:3000/#/about</code>, as the name suggests, uses a hash # in between. So why did we use the BrowserRouter?</p><p>BrowserRouter is a popular choice among modern day web applications. The main reason behind is that it uses HTML5 History API to keep track of the router history of your React app. The HashRouter has a use case for legacy browsers where window.location.hash is still being used to keep a track of routes in a SPA.</p><h2 id="DIY-Exercise-👇"><a href="#DIY-Exercise-👇" class="headerlink" title="DIY Exercise 👇"></a>DIY Exercise 👇</h2><p>Here is a small task for you. Modify directory structure like below screenshot and separate the two functional components Home and about in their own component files such that, in future, if they grow with more JSX to render.</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1678721953570.png" alt="紫升"></p><p>You can totally skip this if you want and move on to the next section. But performing this small task will benefit you to have an understanding of the above concepts.</p><h1 id="The-Link-Component"><a href="#The-Link-Component" class="headerlink" title="The Link Component"></a>The Link Component</h1><p>To navigate between to web pages in HTML, there is an <code>&lt;a href=&quot;&quot;&gt;&lt;/a&gt;</code> anchor tag available. However, using this traditional approach will lead to a browser refresh. In order to overcome this, React Router API offers a Link component that can be used to navigate to a particular URL or a component.</p><p>Let us try to create a navigation menu with this new knowledge. Import Link from <code>react-router-dom</code> in <code>App.js</code> file. Here is the modified snippet of App component.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// App.js</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&quot;react&quot;</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">BrowserRouter</span> <span class="keyword">as</span> <span class="title class_">Router</span>, <span class="title class_">Route</span>, <span class="title class_">Link</span> &#125; <span class="keyword">from</span> <span class="string">&quot;react-router-dom&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Home</span> <span class="keyword">from</span> <span class="string">&quot;./components/Home&quot;</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">About</span> <span class="keyword">from</span> <span class="string">&quot;./components/About&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">Router</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">nav</span> <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">margin:</span> <span class="attr">10</span> &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Link</span> <span class="attr">to</span>=<span class="string">&#x27;/&#x27;</span> <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">padding:</span> <span class="attr">10</span> &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">            Home</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;/<span class="name">Link</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Link</span> <span class="attr">to</span>=<span class="string">&#x27;/about&#x27;</span> <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">padding:</span> <span class="attr">10</span> &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">            About</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;/<span class="name">Link</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">nav</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">&#x27;/&#x27;</span> <span class="attr">exact</span> <span class="attr">component</span>=<span class="string">&#123;Home&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">&#x27;/about&#x27;</span> <span class="attr">component</span>=<span class="string">&#123;About&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">Router</span>&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span></span><br></pre></td></tr></table></figure><p>In the above snippet, notice that all the Links are being added before all the Route components. The styling attributes inside style are optional for now. Start the development server and visit the browser window and you will notice a navigation menu pops up at the top. Try clicking links to navigate between different components.</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1678722050699.png" alt="紫升"></p><p>Wherever a <code>Link</code> is rendered in a React project, an anchor <code>&lt;a&gt;</code> will be rendered in the application’s HTML.</p><h1 id="Active-Links-with-NavLink"><a href="#Active-Links-with-NavLink" class="headerlink" title="Active Links with NavLink"></a>Active Links with NavLink</h1><p>In React Router API, NavLink is the extended version of the <code>Link</code> component. You can say that is a special type of the Link that can style itself as to represent the active state when matches the current route.</p><p>To demonstrate this, first, let us replace all the <code>Link</code> tags with NavLink in <code>App.js</code> file.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// App.js</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&quot;react&quot;</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">BrowserRouter</span> <span class="keyword">as</span> <span class="title class_">Router</span>, <span class="title class_">Route</span>, <span class="title class_">NavLink</span> &#125; <span class="keyword">from</span> <span class="string">&quot;react-router-dom&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Home</span> <span class="keyword">from</span> <span class="string">&quot;./components/Home&quot;</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">About</span> <span class="keyword">from</span> <span class="string">&quot;./components/About&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">Router</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">nav</span> <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">margin:</span> <span class="attr">10</span> &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">NavLink</span> <span class="attr">to</span>=<span class="string">&#x27;/&#x27;</span> <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">padding:</span> <span class="attr">10</span> &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">            Home</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;/<span class="name">NavLink</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">NavLink</span> <span class="attr">to</span>=<span class="string">&#x27;/about&#x27;</span> <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">padding:</span> <span class="attr">10</span> &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">            About</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;/<span class="name">NavLink</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">nav</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">&#x27;/&#x27;</span> <span class="attr">exact</span> <span class="attr">component</span>=<span class="string">&#123;Home&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">&#x27;/about&#x27;</span> <span class="attr">component</span>=<span class="string">&#123;About&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">Router</span>&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span></span><br></pre></td></tr></table></figure><p>At this point, each NavLink link is going to behave like an ordinary Link component which means there is no change so far. To make a link active, add an activeClassName prop to that. Such as below.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;<span class="title class_">NavLink</span> to=<span class="string">&#x27;/&#x27;</span> style=&#123;&#123; <span class="attr">padding</span>: <span class="number">10</span> &#125;&#125; activeClassName=<span class="string">&#x27;active&#x27;</span>&gt;</span><br></pre></td></tr></table></figure><p>To set up the corresponding CSS for this to work, open <code>App.css</code> file and add the below.</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">a</span> &#123;</span><br><span class="line">  <span class="attribute">padding</span>: <span class="number">10px</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-tag">a</span>,</span><br><span class="line"><span class="selector-tag">a</span><span class="selector-pseudo">:visited</span> &#123;</span><br><span class="line">  <span class="attribute">color</span>: blue;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-tag">a</span><span class="selector-class">.active</span> &#123;</span><br><span class="line">  <span class="attribute">color</span>: red;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Do not forget to import this file inside <code>App.js</code>. Also, modify the about route to have an activeClassName.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">&quot;./App.css&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> (</span><br><span class="line">  &#123;<span class="comment">/* */</span>&#125;</span><br><span class="line">  &lt;nav style=&#123;&#123; <span class="attr">margin</span>: <span class="number">10</span> &#125;&#125;&gt;</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">NavLink</span> <span class="attr">to</span>=<span class="string">&#x27;/&#x27;</span> <span class="attr">activeClassName</span>=<span class="string">&#x27;active&#x27;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          Home</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">NavLink</span>&gt;</span></span></span><br><span class="line"></span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">NavLink</span> <span class="attr">to</span>=<span class="string">&#x27;/about&#x27;</span> <span class="attr">activeClassName</span>=<span class="string">&#x27;active&#x27;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          About</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">NavLink</span>&gt;</span></span></span><br><span class="line">  &lt;/nav&gt;</span><br><span class="line"></span><br><span class="line"> &#123;<span class="comment">/* */</span>&#125;</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>Go back to the browser, open develop tools like below and you will notice, at first, the Home route has a class name active.</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1678722233048.png" alt="紫升"></p><p>Try navigating to the About route and see what happens.</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1678722243666.png" alt="紫升"></p><p>On navigating to About route did you notice that the active class name is also added to the corresponding route? However, the Home route still has the active class even though the URL matches the <code>/about</code>. Why?</p><p>The way NavLink works is almost similar to Route component in React Router API. To make sure that only one route has the class active state, try modifying the home route in the navigation menu, as below.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//App.js</span></span><br><span class="line"></span><br><span class="line">&lt;<span class="title class_">NavLink</span> to=<span class="string">&#x27;/&#x27;</span> exact activeClassName=<span class="string">&#x27;active&#x27;</span>&gt;</span><br><span class="line">  <span class="title class_">Home</span></span><br><span class="line">&lt;/<span class="title class_">NavLink</span>&gt;</span><br></pre></td></tr></table></figure><p>You will get the desired output this time.</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1678722287934.png" alt="紫升"></p><h1 id="Adding-Parameters-to-the-Routes"><a href="#Adding-Parameters-to-the-Routes" class="headerlink" title="Adding Parameters to the Routes"></a>Adding Parameters to the Routes</h1><p>In this section, you will learn how to create and manage dynamic routes based on a query parameter such as <code>:id</code>. We start by creating a static array in <code>App.js</code> file that will serve as the mock data.</p><p>The idea is to demonstrate a route as <code>/posts</code> which displays all the posts that are coming from the array. However, each post in the array will be having an id or a unique identifier. Using that unique identifier, you will be approaching the concept of dynamic content rendering by writing the logic for URLs such as <code>/posts/:id</code> where :id will be represented by the specific id of a post.</p><p>To start, let us add a bunch of mock posts in the state inside a new component file called <code>components/posts.js</code>.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Posts.js</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&quot;react&quot;</span></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;../App.css&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Posts</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">posts</span>: [</span><br><span class="line">      &#123; <span class="attr">id</span>: <span class="number">1</span>, <span class="attr">title</span>: <span class="string">&quot;Hello Blog World!&quot;</span> &#125;,</span><br><span class="line">      &#123; <span class="attr">id</span>: <span class="number">2</span>, <span class="attr">title</span>: <span class="string">&quot;My second post&quot;</span> &#125;,</span><br><span class="line">      &#123; <span class="attr">id</span>: <span class="number">3</span>, <span class="attr">title</span>: <span class="string">&quot;What is React Router?&quot;</span> &#125;</span><br><span class="line">    ]</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">&#x27;posts&#x27;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">h1</span>&gt;</span>Posts List<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">    )</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Posts</span></span><br></pre></td></tr></table></figure><p>The corresponding styles to the above are added in <code>App.css</code> file for brevity.</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.posts</span> <span class="selector-tag">ul</span> &#123;</span><br><span class="line">  <span class="attribute">list-style</span>: none;</span><br><span class="line">  <span class="attribute">margin</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">margin-bottom</span>: <span class="number">20px</span>;</span><br><span class="line">  <span class="attribute">padding</span>: <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.posts</span> <span class="selector-tag">ul</span> <span class="selector-tag">li</span> &#123;</span><br><span class="line">  <span class="attribute">padding</span>: <span class="number">10px</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.posts</span> <span class="selector-tag">a</span> &#123;</span><br><span class="line">  <span class="attribute">text-decoration</span>: none;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Now, import the newly created component inside App.js where other routes already exist.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//App.js</span></span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Posts</span> <span class="keyword">from</span> <span class="string">&quot;./components/Posts&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">Router</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">nav</span> <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">margin:</span> <span class="attr">10</span> &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">NavLink</span> <span class="attr">to</span>=<span class="string">&#x27;/&#x27;</span> <span class="attr">exact</span> <span class="attr">activeClassName</span>=<span class="string">&#x27;active&#x27;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            Home</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;/<span class="name">NavLink</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">NavLink</span> <span class="attr">to</span>=<span class="string">&#x27;/about&#x27;</span> <span class="attr">activeClassName</span>=<span class="string">&#x27;active&#x27;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            About</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;/<span class="name">NavLink</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">NavLink</span> <span class="attr">to</span>=<span class="string">&#x27;/posts&#x27;</span> <span class="attr">activeClassName</span>=<span class="string">&#x27;active&#x27;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            Posts</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;/<span class="name">NavLink</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">nav</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">&#x27;/&#x27;</span> <span class="attr">exact</span> <span class="attr">component</span>=<span class="string">&#123;Home&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">&#x27;/about&#x27;</span> <span class="attr">component</span>=<span class="string">&#123;About&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">&#x27;/posts&#x27;</span> <span class="attr">component</span>=<span class="string">&#123;Posts&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">Router</span>&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span></span><br></pre></td></tr></table></figure><p>The existing navigation menu has a new route and its called Posts.</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1678722701566.png" alt="紫升"></p><p>Open Posts.js to rend er the list of Posts and display them as a list whenever the current location in the web browser matches <code>/posts</code>.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&quot;react&quot;</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Link</span>, <span class="title class_">Route</span> &#125; <span class="keyword">from</span> <span class="string">&quot;react-router-dom&quot;</span></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;../App.css&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Child</span>(<span class="params">&#123; match &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h3</span>&gt;</span>ID: &#123;match.params.id&#125;<span class="tag">&lt;/<span class="name">h3</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Posts</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">posts</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">id</span>: <span class="number">1</span>,</span><br><span class="line">        <span class="attr">title</span>: <span class="string">&quot;Hello Blog World!&quot;</span></span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">id</span>: <span class="number">2</span>,</span><br><span class="line">        <span class="attr">title</span>: <span class="string">&quot;My second post&quot;</span></span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">id</span>: <span class="number">3</span>,</span><br><span class="line">        <span class="attr">title</span>: <span class="string">&quot;What is React Router?&quot;</span></span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; posts &#125; = <span class="variable language_">this</span>.<span class="property">state</span></span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">&#x27;posts&#x27;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">h1</span>&gt;</span>Posts List<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">ul</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          &#123;posts.map(post =&gt; (</span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">li</span> <span class="attr">key</span>=<span class="string">&#123;post.id&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">              <span class="tag">&lt;<span class="name">Link</span> <span class="attr">to</span>=<span class="string">&#123;</span>`/<span class="attr">posts</span>/$&#123;<span class="attr">post.id</span>&#125;`&#125;&gt;</span>&#123;post.title&#125;<span class="tag">&lt;/<span class="name">Link</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          ))&#125;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">&#x27;/posts/:id&#x27;</span> <span class="attr">component</span>=<span class="string">&#123;Child&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">    )</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Posts</span></span><br></pre></td></tr></table></figure><p>Also, the Child component reads anything coming from the URL parameters, such as, in the above case, the id of each post. A match object contains information about how a <code>&lt;Route path&gt;</code> matched the URL, thus, in our case, the id of each post.</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1678722789204.png" alt="紫升"></p><h1 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h1><p>Hopefully, by now, you are familiar with the basic concepts of how React Router library works. It is a powerful library that helps you build better React apps. If you want to learn more about React Router visit its official documentation here.</p><p>You can find the complete for the tutorial at this Github repo.</p></div></details><blockquote><p>原文地址：<a href="https://www.crowdbotics.com/blog/introduction-to-react-router-v4-with-reactjs">https://www.crowdbotics.com/blog/introduction-to-react-router-v4-with-reactjs</a><br>原文作者：<a href="https://www.crowdbotics.com/author/amanhimself">amanhimself</a><br>译文出自：<a href="https://blog.zisheng.pro/categories/%E6%B4%9B%E7%AB%B9%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/">紫升翻译计划</a></p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/3c335a370ab4/</id>
    <link href="https://blog.zisheng.pro/posts/3c335a370ab4/"/>
    <published>2023-03-13T15:12:00.000Z</published>
    <summary>React 通常用于构建单页面应用程序（SPA）。 SPA 往往具有多个页面视图。当从一个页面视图导航到另一个页面视图时，重新加载整个页面视图是一项繁琐且不那么高效的任务。实际上，它会削弱 SPA 的优势。为了正常工作，SPA 必须在需要时呈现部分视图，而不是重新加载整个页面。</summary>
    <title>使用 React Router 优化单页应用程序（SPAs）</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/categories/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="issues" scheme="https://blog.zisheng.pro/categories/issues/"/>
    <category term="NodeJs" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/NodeJs/"/>
    <category term="Node" scheme="https://blog.zisheng.pro/tags/Node/"/>
    <category term="EADDRINUSE" scheme="https://blog.zisheng.pro/tags/EADDRINUSE/"/>
    <category term="address already in use" scheme="https://blog.zisheng.pro/tags/address-already-in-use/"/>
    <category term="进程" scheme="https://blog.zisheng.pro/tags/%E8%BF%9B%E7%A8%8B/"/>
    <content>
      <![CDATA[<h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><p>当你尝试重启一个 Node 应用时，上一个应用没有正确关闭，你可能会看到一个“listen EADDRINUSE: address already in use”错误，例如：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">⇒  npm start</span><br><span class="line">&gt; react-app-es6-jest@0.0.1 start /Users/admin/Code/WorkSpace/react-app-es6-jest</span><br><span class="line">&gt; concurrently <span class="string">&quot;npm run start:server&quot;</span> <span class="string">&quot;npm run start:client&quot;</span></span><br><span class="line"></span><br><span class="line">[0]</span><br><span class="line">[0] &gt; react-app-es6-jest@0.0.1 start:server /Users/admin/Code/WorkSpace/react-app-es6-jest</span><br><span class="line">[0] &gt; nodemon src/server/index.js --watch src/server -e js --<span class="built_in">exec</span> babel-node</span><br><span class="line">[0]</span><br><span class="line">[0] [nodemon] 2.0.1</span><br><span class="line">[0] [nodemon] to restart at any time, enter `rs`</span><br><span class="line">[0] [nodemon] watching <span class="built_in">dir</span>(s): src/server/**/*</span><br><span class="line">[0] [nodemon] watching extensions: js</span><br><span class="line">[0] [nodemon] starting `babel-node src/server/index.js`</span><br><span class="line">[0] events.js:174</span><br><span class="line">[0]       throw er; // Unhandled <span class="string">&#x27;error&#x27;</span> event</span><br><span class="line">[0]       ^</span><br><span class="line">[0]</span><br><span class="line">[0] Error: listen EADDRINUSE: address already <span class="keyword">in</span> use :::3000</span><br><span class="line">[0]     at Server.setupListenHandle [as _listen2] (net.js:1279:14)</span><br><span class="line">[0]     at listenInCluster (net.js:1327:12)</span><br><span class="line">[0]     at Server.listen (net.js:1414:7)</span><br><span class="line">[0]     at Function.listen (/Users/admin/Code/WorkSpace/react-app-es6-jest/node_modules/express/lib/application.js:618:24)</span><br><span class="line">[0] [nodemon] app crashed - waiting <span class="keyword">for</span> file changes before starting...</span><br></pre></td></tr></table></figure><h2 id="问题背后的原因"><a href="#问题背后的原因" class="headerlink" title="问题背后的原因"></a>问题背后的原因</h2><p><code>process.on(&#39;exit&#39;, ...)</code> 不会在进程崩溃或被杀死时调用。它只在事件循环结束时调用，因为 <code>server.close()</code> 会结束事件循环（它仍然必须等待当前运行的堆栈），所以将其放在 exit 事件中是没有意义的。</p><blockquote><p>译者注：有时候直接关闭 vscode 会导致进程没有正确关闭，这时候也可能会出现这个问题。</p></blockquote><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><p>该应用程序的正确修复是</p><ul><li>在 <code>server.listen()</code> 之前检查端口是否已经被占用</li><li>在崩溃时，可以使用 <code>process.on(&#39;uncaughtException&#39;, ..)</code> 进行处理。</li><li>在 <code>kill</code> 操作上，可以使用 <code>process.on(&#39;SIGTERM&#39;, ..)</code> 进行处理。</li></ul><p>当出现 EADDRINUSE 问题时，为了解决它，你需要手动终止该进程。为此，你需要找到进程的进程 ID（PID）。</p><h2 id="手动杀死进程"><a href="#手动杀死进程" class="headerlink" title="手动杀死进程"></a>手动杀死进程</h2><h3 id="Mac-Linux"><a href="#Mac-Linux" class="headerlink" title="Mac&#x2F;Linux"></a>Mac&#x2F;Linux</h3><p>找到与该端口关联的进程 ID（PID）</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ lsof -i tcp:3000</span><br><span class="line">COMMAND PID   USER  FD  TYPE DEVICE             SIZE/OFF NODE NAME</span><br><span class="line">node    44475 chen5 31u IPv4 0x8b1721168764e4bf 0t0 TCP *:strexec-s (LISTEN)</span><br></pre></td></tr></table></figure><p>这将返回使用该端口的任何进程的详细信息，包括PID。然后可以使用以下命令杀死该进程：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">kill</span> -9 44475</span><br></pre></td></tr></table></figure><p>请注意，<code>-9</code> 选项强制终止进程而不考虑其当前状态，并立即使其退出。当需要强制关闭一个已经挂起或占据端口的进程时，通常需要使用此选项。</p><p>如果你遇到权限问题，请尝试在命令前加上sudo。例如：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo <span class="built_in">kill</span> -9 44475</span><br></pre></td></tr></table></figure><h3 id="Windows"><a href="#Windows" class="headerlink" title="Windows"></a>Windows</h3><h4 id="解决方案-1：任务管理器"><a href="#解决方案-1：任务管理器" class="headerlink" title="解决方案 1：任务管理器"></a>解决方案 1：任务管理器</h4><p>打开“任务管理器”应用程序 (taskman.exe)，从进程或服务选项卡根据 PID 列进行排序。要显示 PID 列，请右键单击标头行并从列表中选择 PID。右键单击要停止的进程，然后选择“结束任务”</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1678416348850.png" alt="example in Windows, source from Internet"></p><h4 id="解决方案-2：命令行"><a href="#解决方案-2：命令行" class="headerlink" title="解决方案 2：命令行"></a>解决方案 2：命令行</h4><p>以管理员身份打开CMD（命令提示符）窗口，通过导航到 <code>开始 &gt; 运行 &gt; 输入cmd &gt; 右键单击“命令提示符”</code>，然后选择“以管理员身份运行”打开 。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1678416450947.png" alt="source from Google Search"></p><p>可以使用命令提示符来查找与特定端口相关的进程ID。在管理员模式下打开CMD窗口，然后使用” netstat “命令来获取所有处于活动状态的端口以及占用者的进程ID。以下是要使用的完整命令:</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ netstat -ano|findstr <span class="string">&quot;PID :3000&quot;</span></span><br><span class="line">Proto Local Address Foreign Address State PID</span><br><span class="line">TCP 0.0.0.0:3000 0.0.0.0:0 LISTENING 18264</span><br></pre></td></tr></table></figure><p>其中 <code>-a</code> 选项显示所有正在使用的端口（而不仅仅是与当前用户相关的端口）。<code>-n</code> 选项停止主机名查询（这需要很长时间）。<code>-o</code> 选项列出负责端口活动的进程 ID。最后，<code>findstr</code> 命令匹配包含 PID 字符串的标题行，并查找指定的端口。例如， <code>PID :3000</code>。</p><p>然后终止此进程（<code>/f</code> 是强制）：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ taskkill /pid 18264 /f</span><br></pre></td></tr></table></figure><blockquote><p>原文地址：<a href="https://114.li/jscdep">How to kill server when seeing “EADDRINUSE: address already in use”</a><br>原文作者：<a href="https://bindichen.medium.com/">BChen</a><br>译文出自：<a href="https://blog.zisheng.pro/categories/%E6%B4%9B%E7%AB%B9%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/">紫升翻译计划</a></p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/4475c84c4024/</id>
    <link href="https://blog.zisheng.pro/posts/4475c84c4024/"/>
    <published>2023-03-09T18:25:43.000Z</published>
    <summary>本文介绍了如何解决 EADDRINUSE: address already in use</summary>
    <title>「已解决」EADDRINUSE: address already in use</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="JavaScript" scheme="https://blog.zisheng.pro/tags/JavaScript/"/>
    <category term="npm" scheme="https://blog.zisheng.pro/tags/npm/"/>
    <category term="lerna" scheme="https://blog.zisheng.pro/tags/lerna/"/>
    <category term="yarn" scheme="https://blog.zisheng.pro/tags/yarn/"/>
    <content>
      <![CDATA[<p>将大型代码仓库分割成多个独立版本化的 软件包（package）对于代码共享来说非常有用。但是，如果某些更改 跨越了多个代码仓库的话将变得很麻烦并且难以跟踪，并且，跨越多个代码仓库的测试将迅速变得非常复杂。</p><p>为了解决这些（以及许多其它）问题，某些项目会将 代码仓库分割成多个软件包（package），并将每个软件包存放到独立的代码仓库中。但是，例如 Babel、 React、Angular、Ember、Meteor、Jest 等项目以及许多其他项目则是在 一个代码仓库中包含了多个软件包（package）并进行开发。</p><p>Lerna 是一种工具，针对 使用 git 和 npm 管理多软件包代码仓库的工作流程进行优化。</p><h2 id="开始"><a href="#开始" class="headerlink" title="开始"></a>开始</h2><h3 id="全局安装-lerna"><a href="#全局安装-lerna" class="headerlink" title="全局安装 lerna"></a>全局安装 lerna</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ npm install lerna -g</span><br></pre></td></tr></table></figure><h3 id="初始化-lerna-项目"><a href="#初始化-lerna-项目" class="headerlink" title="初始化 lerna 项目"></a>初始化 lerna 项目</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ lerna init --independent</span><br></pre></td></tr></table></figure><p>你的代码仓库目前应该是如下结构：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">lerna-repo/</span><br><span class="line">  packages/</span><br><span class="line">  package.json</span><br><span class="line">  lerna.json</span><br></pre></td></tr></table></figure><h2 id="创建-package"><a href="#创建-package" class="headerlink" title="创建 package"></a>创建 package</h2><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ lerna create module-1</span><br><span class="line">$ lerna create module-2</span><br></pre></td></tr></table></figure><h2 id="yarn-workspaces-Lerna-Hoisting"><a href="#yarn-workspaces-Lerna-Hoisting" class="headerlink" title="yarn workspaces &amp; Lerna Hoisting"></a>yarn workspaces &amp; Lerna Hoisting</h2><p>使用 <a href="https://yarnpkg.com/lang/zh-Hans/docs/workspaces/">yarn workspaces</a> 结合 Lerna <code>useWorkspaces</code> 可以实现 <a href="https://github.com/lerna/lerna/blob/main/doc/hoist.md">Lerna Hoisting</a>。这并不是多此一举，这可以让你在统一的地方（根目录）管理依赖，这即节省时间又节省空间。</p><p>配置 lerna.json:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  ...</span><br><span class="line">  <span class="attr">&quot;npmClient&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;useWorkspaces&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>顶级 package.json 必须包含一个 workspaces 数组:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;private&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  ...</span><br><span class="line">  <span class="attr">&quot;workspaces&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;packages/*&quot;</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="npm-registry"><a href="#npm-registry" class="headerlink" title="npm registry"></a>npm registry</h2><h3 id="搭建-verdaccio"><a href="#搭建-verdaccio" class="headerlink" title="搭建 verdaccio"></a>搭建 verdaccio</h3><blockquote><p>verdaccio 是一个开源轻量的 npm 私服</p></blockquote><p>全局安装：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ npm install verdaccio -g</span><br></pre></td></tr></table></figure><p>配置 <code>~/.config/verdaccio/config.yaml</code> uplinks:</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="comment"># a list of other known repositories we can talk to</span></span><br><span class="line"><span class="attr">uplinks:</span></span><br><span class="line">  <span class="attr">npmjs:</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">https://registry.npmjs.org/</span></span><br><span class="line">  <span class="attr">taobao:</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">https://registry.npm.taobao.org/</span></span><br></pre></td></tr></table></figure><h3 id="设置开机自启动"><a href="#设置开机自启动" class="headerlink" title="设置开机自启动"></a>设置开机自启动</h3><p>0、run <code>sudo npm i -g pm2</code></p><p>1、run <code>pm2 start verdaccio</code> &amp; <code>pm2 startup</code></p><p>outputs:</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[PM2] Init System found: launchd</span><br><span class="line">[PM2] To setup the Startup Script, copy/paste the following <span class="built_in">command</span>:</span><br><span class="line">sudo <span class="built_in">env</span> PATH=<span class="variable">$PATH</span>:/usr/local/bin /usr/local/lib/node_modules/pm2/bin/pm2 startup launchd -u luozhu --hp /Users/luozhu</span><br></pre></td></tr></table></figure><p>2、run <code>sudo env PATH=$PATH:/usr/local/bin /usr/local/lib/node_modules/pm2/bin/pm2 startup launchd -u luozhu --hp /Users/luozhu</code></p><p>outputs:</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">[PM2] Freeze a process list on reboot via:</span><br><span class="line">$ pm2 save</span><br><span class="line"></span><br><span class="line">[PM2] Remove init script via:</span><br><span class="line">$ pm2 unstartup launchd</span><br></pre></td></tr></table></figure><h3 id="修改-lerna-publishConfig"><a href="#修改-lerna-publishConfig" class="headerlink" title="修改 lerna publishConfig"></a>修改 lerna publishConfig</h3><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// lerna.json</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;ignoreChanges&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;ignored-file&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;**/__tests__/**&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;**/*.md&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;command&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;publish&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;registry&quot;</span><span class="punctuation">:</span> <span class="string">&quot;http://localhost:4873&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;conventionalCommits&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;chore(release): publish&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><blockquote><p>注意：如果子包是带 <code>scope</code> 的，需要为子包设置 <code>publishConfig.access</code></p></blockquote><h2 id="commitlint-commitizen"><a href="#commitlint-commitizen" class="headerlink" title="commitlint &amp; commitizen"></a>commitlint &amp; commitizen</h2><blockquote><p>请参考我之前的文章 <a href="https://juejin.im/post/6877462747631026190">一文搞定规范化 Git Commit</a></p></blockquote><h2 id="开发流程"><a href="#开发流程" class="headerlink" title="开发流程"></a>开发流程</h2><h3 id="install"><a href="#install" class="headerlink" title="install"></a>install</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn install</span><br></pre></td></tr></table></figure><h3 id="package-依赖"><a href="#package-依赖" class="headerlink" title="package 依赖"></a>package 依赖</h3><p>给指定 package 安装依赖：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 或者（推荐）</span></span><br><span class="line">$ lerna add lodash packages/module-1</span><br><span class="line"><span class="comment"># 或者</span></span><br><span class="line">$ lerna add lodash --scope=module-1</span><br><span class="line"><span class="comment"># 或者</span></span><br><span class="line">$ lerna add lodash **/module-1</span><br><span class="line"><span class="comment"># 或者</span></span><br><span class="line">$ yarn workspace module-1 add lodash</span><br></pre></td></tr></table></figure><p>给所有 package 安装依赖：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ lerna add lodash</span><br></pre></td></tr></table></figure><h3 id="workspace-之间的依赖"><a href="#workspace-之间的依赖" class="headerlink" title="workspace 之间的依赖"></a>workspace 之间的依赖</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ lerna add module-2 --scope module-1</span><br><span class="line"><span class="comment"># 或者</span></span><br><span class="line">$ lerna add module-2 packages/module-1</span><br></pre></td></tr></table></figure><h3 id="共用的工具依赖"><a href="#共用的工具依赖" class="headerlink" title="共用的工具依赖"></a>共用的工具依赖</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add -WD typescript</span><br></pre></td></tr></table></figure><h2 id="lerna-json"><a href="#lerna-json" class="headerlink" title="lerna.json"></a>lerna.json</h2><ul><li>version: 当前仓库的版本，Independent mode 请设置为 <code>independent</code></li><li>npmClient: 指定运行命令的客户端程序（默认是 npm）</li><li>ignoreChanges: 一个不包含在 <code>lerna changed/publish</code> 的 glob 数组。使用这个去阻止发布不必要的更新，比如修复 <code>README.md</code></li><li>command<ul><li>publish<ul><li>registry：设置自定义的 npm 代理（比如使用 verdaccio 搭建的私服）</li><li>conventionalCommits：<code>lerna version</code> 会自动决定 version bump 和生成 CHANGELOG 文件</li><li>message：一个 publish 时的自定义 commit 信息。详情请查看<a href="https://github.com/lerna/lerna/blob/main/commands/version#--message-msg">@lerna&#x2F;version</a></li></ul></li></ul></li></ul><h2 id="npm-scripts"><a href="#npm-scripts" class="headerlink" title="npm scripts"></a>npm scripts</h2><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;release:beta&quot;</span><span class="punctuation">:</span> <span class="string">&quot;lerna publish --canary --pre-dist-tag=beta --preid=beta --yes&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;release:rc&quot;</span><span class="punctuation">:</span> <span class="string">&quot;lerna publish prerelease --pre-dist-tag=rc --preid=rc&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;release:next&quot;</span><span class="punctuation">:</span> <span class="string">&quot;lerna publish prerelease --pre-dist-tag=next --preid=next&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;release:preminor&quot;</span><span class="punctuation">:</span> <span class="string">&quot;lerna publish preminor --pre-dist-tag=next --preid=next&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;release:premajor&quot;</span><span class="punctuation">:</span> <span class="string">&quot;lerna publish premajor --pre-dist-tag=next --preid=next&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;release&quot;</span><span class="punctuation">:</span> <span class="string">&quot;lerna publish&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;release:minor&quot;</span><span class="punctuation">:</span> <span class="string">&quot;lerna publish minor&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;release:major&quot;</span><span class="punctuation">:</span> <span class="string">&quot;lerna publish major&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;commit&quot;</span><span class="punctuation">:</span> <span class="string">&quot;git cz&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><blockquote><p>本文首发于「<a href="https://blog.zisheng.pro/">紫升的博客</a>」，同步于公众号「<a href="https://cdn.jsdelivr.net/gh/youngjuning/images/20210418112129.jpeg">紫升早茶馆</a>」和「<a href="https://juejin.cn/user/325111174662855">掘金专栏</a>」。</p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/4e0dc52b8e3c/</id>
    <link href="https://blog.zisheng.pro/posts/4e0dc52b8e3c/"/>
    <published>2023-03-08T10:26:27.000Z</published>
    <summary>将大型代码仓库分割成多个独立版本化的 软件包（package）对于代码共享来说非常有用。但是，如果某些更改 跨越了多个代码仓库的话将变得很麻烦并且难以跟踪，并且，跨越多个代码仓库的测试将迅速变得非常复杂。</summary>
    <title>基于 lerna 的多包 JavaScript 项目搭建维护</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="运维" scheme="https://blog.zisheng.pro/categories/%E8%BF%90%E7%BB%B4/"/>
    <category term="NPM" scheme="https://blog.zisheng.pro/tags/NPM/"/>
    <category term="GitHub" scheme="https://blog.zisheng.pro/tags/GitHub/"/>
    <category term="Probot" scheme="https://blog.zisheng.pro/tags/Probot/"/>
    <category term="机器人" scheme="https://blog.zisheng.pro/tags/%E6%9C%BA%E5%99%A8%E4%BA%BA/"/>
    <content>
      <![CDATA[<h2 id="关于-Github-Apps"><a href="#关于-Github-Apps" class="headerlink" title="关于 Github Apps"></a>关于 Github Apps</h2><p>GitHub Apps 是 GitHub 中优秀的产品。一个 GitHub App 通过 API 直接使用自己的身份进行操作，这意味着你不需要作为一个单独的用户维护一个机器人或服务账户。</p><p>GitHub Apps 可以被直接安装到组织或者用户账号上，并且可以赋予它们访问指定仓库的权限。它们带有内置的 webhook 和狭窄的特定权限。设置 GitHub 应用程序时，可以选择希望它访问的仓库。比如你可以设置一个叫 <code>MyGitHub</code> 的应用程序，该应用程序有且仅有 <code>octocat</code> 仓库的写入 <code>issues</code> 的权限。安装 GitHub App 需要你是组织的所有者或对仓库拥有 admin 权限。</p><h2 id="关于-Probot"><a href="#关于-Probot" class="headerlink" title="关于 Probot"></a>关于 Probot</h2><p>Probot 是一个基于 Node.js 构建 GitHub Apps 的框架。它旨在消除所有繁琐的事情，如接收和验证 webhooks 以及做认证动作，这样你就可以专注于你想要实现的功能。Probot 应用程序非常容易编写、部署和分享。大多数流行的 Probot 应用被托管，所以你不需要做任何部署和管理工作。这里有几个基于 Probot 构建的应用：</p><ul><li><a href="https://probot.github.io/apps/eslint-disable/">ESLint Disable Watcher</a> - 当 pr 中尝试禁用 eslint 规则时进行评论。</li><li><a href="https://probot.github.io/apps/dco/">Developer Certificate of Origin</a> - 强制 Pull Requests 符合 DCO 规范</li><li><a href="https://probot.github.io/apps/issue-check/">Issue Check</a> - 根据关键字和任务列表检查 issues 以确保包含必要的信息</li><li><a href="https://probot.github.io/apps/pull/">Pull</a> - Keep your forks up-to-date.</li></ul><p>你可以在<a href="https://probot.github.io/apps/">特色应用</a>或<a href="https://github.com/search?q=topic:probot-app&type=Repositories">GitHub probot-app 话题</a>浏览更多应用。</p><h2 id="Hello-Probot"><a href="#Hello-Probot" class="headerlink" title="Hello Probot"></a>Hello Probot</h2><p>一个 Probot 应用本质上是导出一个函数的 <a href="https://nodejs.org/api/modules.html">Node.js 模块</a>：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = <span class="function"><span class="params">app</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// your code here</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><code>app</code> 参数是 <a href="https://probot.github.io/api/latest/classes/probot.html">Probot</a> 类的实例，该实例可以让你访问所有的 GitHub 精华。</p><p><code>aap.on</code> 负责监听所有 <a href="https://probot.github.io/docs/webhooks/">GitHub 触发的 webhook 事件</a>，当 GitHub 上发生任何有趣的事情，你的应用程序想知道的时候，它会通知你。</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = <span class="function"><span class="params">app</span> =&gt;</span> &#123;</span><br><span class="line">  app.<span class="title function_">on</span>(<span class="string">&#x27;issues.opened&#x27;</span>, <span class="keyword">async</span> context =&gt; &#123;</span><br><span class="line">    <span class="comment">// 一个新的 issue 被打开，我们应该做些什么呢？</span></span><br><span class="line">    context.<span class="property">log</span>.<span class="title function_">info</span>(context.<span class="property">payload</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>传递给事件处理程序的上下文包括关于被触发的事件的一切，以及一些有用的属性，以便对事件做出有用的回应。<code>context.octokit</code> 是一个经过认证的 GitHub 客户端，可以用来<a href="https://probot.github.io/docs/github-api/">进行 REST API 和 GraphQL 调用</a>，并允许你以编程方式做几乎任何你可以在 GitHub 上通过浏览器做的事情。</p><p>下面是一个当新打开 issues 时自动评论的 App。</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = <span class="function"><span class="params">app</span> =&gt;</span> &#123;</span><br><span class="line">  app.<span class="title function_">on</span>(<span class="string">&#x27;issues.opened&#x27;</span>, <span class="keyword">async</span> context =&gt; &#123;</span><br><span class="line">    <span class="comment">// context` 从事件中提取信息，可以传递给 GitHub API 调用。这将返回：</span></span><br><span class="line">    <span class="comment">// &#123; owner: &#x27;yourname&#x27;, repo: &#x27;yourrepo&#x27;, number: 123, body: &#x27;Hello World !&#125;</span></span><br><span class="line">    <span class="keyword">const</span> params = context.<span class="title function_">issue</span>(&#123; <span class="attr">body</span>: <span class="string">&#x27;Hello World!&#x27;</span> &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 在 issue 上发一条评论</span></span><br><span class="line">    <span class="keyword">return</span> context.<span class="property">octokit</span>.<span class="property">issues</span>.<span class="title function_">createComment</span>(params);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="开发一个-Probot-app"><a href="#开发一个-Probot-app" class="headerlink" title="开发一个 Probot app"></a>开发一个 Probot app</h2><p>为了开发一个 Probot app，你首先需要安装 Node.js 10.0.0 或更新的版本。</p><h3 id="生成一个新的-app"><a href="#生成一个新的-app" class="headerlink" title="生成一个新的 app"></a>生成一个新的 app</h3><p><a href="https://github.com/probot/create-probot-app">create-probot-app</a> 是开始构建一个新的 app 的最佳方式。它将生成一个新的应用程序，其中包含你所需要的一切，以开始并在生产中运行你的应用程序。</p><p>运行下面的命令生成一个项目：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ npx create-probot-app my-first-app</span><br></pre></td></tr></table></figure><p>该命令会问一系列关于你的 app 的问题，看起来就像：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">Let<span class="string">&#x27;s create a Probot app!</span></span><br><span class="line"><span class="string">? App name: my-first-app</span></span><br><span class="line"><span class="string">? Description of app: A &#x27;</span>Hello World<span class="string">&#x27; GitHub App built with Probot.</span></span><br><span class="line"><span class="string">? Author&#x27;</span>s full name: Katie Horne</span><br><span class="line">? Author<span class="string">&#x27;s email address: katie@auth0.com</span></span><br><span class="line"><span class="string">? GitHub user or org name: khorne3</span></span><br><span class="line"><span class="string">? Repository name: my-first-app</span></span><br><span class="line"><span class="string">? Which template would you like to use? (Use arrow keys)</span></span><br><span class="line"><span class="string">❯ basic-js</span></span><br><span class="line"><span class="string">  basic-ts (use this one for TypeScript support)</span></span><br><span class="line"><span class="string">  checks-js</span></span><br><span class="line"><span class="string">  git-data-js</span></span><br><span class="line"><span class="string">  deploy-js</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">Finished scaffolding files!</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">Installing dependencies. This may take a few minutes...</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">Successfully created my-first-app.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">Begin using your app with:</span></span><br><span class="line"><span class="string">  cd my-first-app</span></span><br><span class="line"><span class="string">  npm start</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">View your app&#x27;</span>s README <span class="keyword">for</span> more usage instructions.</span><br><span class="line"></span><br><span class="line">Visit the Probot docs:</span><br><span class="line">  https://probot.github.io/docs/</span><br><span class="line"></span><br><span class="line">Get <span class="built_in">help</span> from the community:</span><br><span class="line">  https://probot.github.io/community/</span><br><span class="line"></span><br><span class="line">Enjoy building your Probot app!</span><br></pre></td></tr></table></figure><p>创建的最重要的文件是 <code>index.js</code>（你的 app 代码所在的位置）和 <code>package.json</code>（使你的 app 成为标准 <a href="https://docs.npmjs.com/files/package.json">npm module</a>）。</p><h3 id="本地运行-app"><a href="#本地运行-app" class="headerlink" title="本地运行 app"></a>本地运行 app</h3><p>现在你已经准备好在本地运行 app 了。运行 <code>npm start</code> 来开启一个 server 吧：</p><blockquote><p>注意：如果你选择了 TypeScript 模板，请确保执行了 <code>npm run build</code>！</p></blockquote><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">$ yarn start</span><br><span class="line">yarn run v1.22.10</span><br><span class="line">$ probot run ./lib/index.js</span><br><span class="line">INFO (server): Running Probot v11.3.0 (Node.js: v14.15.5)</span><br><span class="line">INFO (server): Forwarding https://smee.io/dz7D1zur24cGNj7 to http://localhost:3000/</span><br><span class="line">INFO (server): Listening on http://localhost:3000</span><br><span class="line">INFO (server): Connected</span><br></pre></td></tr></table></figure><h3 id="配置-GitHub-App"><a href="#配置-GitHub-App" class="headerlink" title="配置 GitHub App"></a>配置 GitHub App</h3><p>下列是自动配置 GitHub App 的步骤：</p><ol><li>在本地命令行中执行 <code>npm start</code>。</li><li>访问 <a href="http://localhost:3000/">http://localhost:3000</a> 查看下一步。</li><li>你会看到类似下面的页面。</li></ol><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0e1601ae833f47a1815d79cb1dea2e6f~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><ol start="4"><li>点击 <strong>Register a GitHub App</strong> 按钮继续。</li><li>接着，你需要给你的 App 取一个没有被占用的名字，注意：如果你看到类似 <code>Name is reserved for the account @tuya</code> 的提示，这意味着你不能使用已存在的 GitHub organization 的名字作为 app 的名字（除非你是该组织的 owner）</li></ol><h2 id="GitHub-Release-时-npm-publish"><a href="#GitHub-Release-时-npm-publish" class="headerlink" title="GitHub Release 时 npm publish"></a>GitHub Release 时 <code>npm publish</code></h2><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a5e7c791feb140cbb537a79c89450842~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>实现 GitHub CI 自动发布 NPM 包，主要是为了合理管理对外 npm 发布权限。而比较通用的发布时机是在 GitHub release 时。基于上面流程图的分析，我们可以看出 released 状态时执行 <code>npm publish</code> 最合适。</p><p>我们实现的具体逻辑是，当 Probot app 监听到 <code>release.released</code> 事件时，处理发布前的操作。重要的是我们需要根据 <code>package.json</code> 中的 <code>version</code> 字段匹配出 tag，比如：</p><ul><li>1.0.0：tag 为 latest 的 1.0.0</li><li>1.0.0-beta.0：tag 为 beta 的 1.0.0-beta.0</li><li>1.0.0-alpha.0：tag 为 alpha 的 1.0.0-alpha</li></ul><h2 id="NPM-自动发布实现原理"><a href="#NPM-自动发布实现原理" class="headerlink" title="NPM 自动发布实现原理"></a>NPM 自动发布实现原理</h2><p>发布之前我们需要拉取仓库代码、取出版本和 tag、设置 NPM publish Token 等工作。先上核心代码，后面我们详细解析。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">app.<span class="title function_">on</span>(<span class="string">&#x27;release.released&#x27;</span>, <span class="keyword">async</span> context =&gt; &#123;</span><br><span class="line">  <span class="keyword">if</span> (!<span class="title function_">isTuya</span>(context)) <span class="keyword">return</span>;</span><br><span class="line">  app.<span class="title function_">log</span>(<span class="string">&#x27;npm publishing&#x27;</span>);</span><br><span class="line">  <span class="keyword">const</span> &#123; <span class="attr">repository</span>: repo &#125; = context.<span class="property">payload</span>;</span><br><span class="line">  <span class="keyword">const</span> downloadDefaultBranch = <span class="string">`<span class="subst">$&#123;repo.full_name&#125;</span>#<span class="subst">$&#123;context.payload.release.tag_name&#125;</span>`</span>;</span><br><span class="line">  <span class="keyword">const</span> downLoadTempDir = <span class="string">`<span class="subst">$&#123;os.tmpdir()&#125;</span>/<span class="subst">$&#123;repo.full_name&#125;</span>`</span>;</span><br><span class="line">  <span class="keyword">await</span> <span class="title function_">download</span>(downloadDefaultBranch, downLoadTempDir);</span><br><span class="line">  <span class="keyword">const</span> &#123; version, scripts &#125; = <span class="built_in">require</span>(<span class="string">`<span class="subst">$&#123;downLoadTempDir&#125;</span>/package.json`</span>);</span><br><span class="line">  <span class="keyword">const</span> tag = <span class="regexp">/^\d\.\d\.\d-(.*)\.\d$/</span>.<span class="title function_">exec</span>(version)</span><br><span class="line">    ? <span class="regexp">/^\d\.\d\.\d-(.*)\.\d$/</span>.<span class="title function_">exec</span>(version)[<span class="number">1</span>]</span><br><span class="line">    : <span class="string">&#x27;latest&#x27;</span>;</span><br><span class="line">  <span class="comment">// 如果有 build 脚本则先执行 build 脚本</span></span><br><span class="line">  <span class="keyword">if</span> (scripts.<span class="property">build</span>) &#123;</span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">execSh</span>(<span class="string">`cd <span class="subst">$&#123;downLoadTempDir&#125;</span> &amp;&amp; npm install &amp;&amp; npm run build`</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> result = <span class="keyword">await</span> <span class="title function_">npmPublish</span>(&#123;</span><br><span class="line">      <span class="attr">package</span>: <span class="string">`<span class="subst">$&#123;downLoadTempDir&#125;</span>/package.json`</span>,</span><br><span class="line">      <span class="attr">token</span>: process.<span class="property">env</span>.<span class="property">NPM_AUTH_TOKEN</span>,</span><br><span class="line">      <span class="attr">registry</span>: <span class="string">&#x27;https://registry.npmjs.org/&#x27;</span>,</span><br><span class="line">      tag,</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">if</span> (result.<span class="property">type</span> === <span class="string">&#x27;none&#x27;</span>) &#123;</span><br><span class="line">      app.<span class="property">log</span>.<span class="title function_">error</span>(</span><br><span class="line">        <span class="string">`You can&#x27;t publish duplicate version <span class="subst">$&#123;result.package&#125;</span>@<span class="subst">$&#123;result.version&#125;</span>`</span>,</span><br><span class="line">      );</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">    app.<span class="property">log</span>.<span class="title function_">error</span>(error);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="NPM-Publish-Token"><a href="#NPM-Publish-Token" class="headerlink" title="NPM Publish Token"></a>NPM Publish Token</h3><h4 id="申请-NPM-Publish-Token"><a href="#申请-NPM-Publish-Token" class="headerlink" title="申请 NPM Publish Token"></a>申请 NPM Publish Token</h4><p><strong>1. 访问 npmjs.com 进入 Access Tokens 页面</strong></p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2e077e9f9d0a4dc8bde0a24e121461c3~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p><strong>2. 点击 Generate New Token 按钮</strong></p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/82e09021f85c413d80ac77ed3df6746e~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p><strong>3. Token 类型选择 Publish</strong></p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/256574d0e063410aa50c85d469346809~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><h4 id="保证-NPM-Publish-Token-安全性"><a href="#保证-NPM-Publish-Token-安全性" class="headerlink" title="保证 NPM Publish Token 安全性"></a>保证 NPM Publish Token 安全性</h4><p>NPM Token 是不能被别人看到的，为了达到这个目的，首先项目需要设置为私有的，然后将 Token 放到 <code>.env</code> 中，通过 <code>process.env.NPM_AUTH_TOKEN</code> 获取。另外谨记不要在日志中打印环境变量。</p><h4 id="保证-GitHub-App-安全性"><a href="#保证-GitHub-App-安全性" class="headerlink" title="保证 GitHub App 安全性"></a>保证 GitHub App 安全性</h4><p>如果把 GitHub App 发布为 public 的，那么任何仓库都可以安装该应用，这不是我们想要的结果。解决办法有两个，一是将应用注册为 private 类型的，二是在监听回调中判断是否是允许的组织或者用户。我选择的是第二种方案，校验函数如下：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">isTuya</span> = context =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; full_name &#125; = context.<span class="property">payload</span>.<span class="property">repository</span>;</span><br><span class="line">  <span class="keyword">return</span> full_name.<span class="title function_">startsWith</span>(<span class="string">&#x27;youngjuning&#x27;</span>) || full_name.<span class="title function_">startsWith</span>(<span class="string">&#x27;tuya&#x27;</span>);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="下载源码"><a href="#下载源码" class="headerlink" title="下载源码"></a>下载源码</h3><p>我们选择了 download-git-repo 下载 git 仓库，但是该仓库不支持 Promise，我们做一下简单的改造：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> download = <span class="built_in">require</span>(<span class="string">&#x27;download-git-repo&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = <span class="function">(<span class="params">repo, tempDir</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">download</span>(repo, tempDir, <span class="function"><span class="params">err</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (err) &#123;</span><br><span class="line">        <span class="title function_">reject</span>(err);</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="title function_">resolve</span>(<span class="literal">null</span>);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="npmPublish"><a href="#npmPublish" class="headerlink" title="npmPublish"></a>npmPublish</h3><p>我们选择了 @jsdevtools&#x2F;npm-publish 执行发布动作，该仓库除了编程调用外，还可以作为 GitHub Action 和命令行工具使用。需要注意的是，我们需要用正则取出我们要发布的 tag：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> tag = <span class="regexp">/^\d\.\d\.\d-(.*)\.\d$/</span>.<span class="title function_">exec</span>(version)</span><br><span class="line">  ? <span class="regexp">/^\d\.\d\.\d-(.*)\.\d$/</span>.<span class="title function_">exec</span>(version)[<span class="number">1</span>]</span><br><span class="line">  : <span class="string">&#x27;latest&#x27;</span>;</span><br></pre></td></tr></table></figure><h3 id="lerna-publish"><a href="#lerna-publish" class="headerlink" title="lerna publish"></a>lerna publish</h3><p>lerna 管理版本由于是一次可能发布多个仓库，所以无法使用上面提到的发布流程。针对 lerna，我设计的发布流程是监听到 push 动作后取最新的一条 commit，匹配是否包含 <code>chore(release): publish</code>。具体原理如下：</p><ol><li>判断 push 分支是否是主分支且提交信息包含 <code>chore(release): publish</code></li><li>因为是 lerna publish，所以需要使用 simple-git 这个库 clone 项目。</li><li>由于 lerna publish <a href="https://github.com/lerna/lerna/issues/2404">不支持 token</a>，我们采用将 <code>//registry.npmjs.org/:_authToken=${process.env.NPM_AUTH_TOKEN}</code> 写入 <code>.npmrc</code> 的方式完成带 token 的发布。</li><li>最后，我们需要使用 <code>from-git</code> 的方式执行 <code>lerna publish</code>，<code>from-git</code> 的场景便是本地执行 <code>lerna version</code>，在 CI 中执行 <code>lerna publish</code>。</li></ol><p>完整代码如下：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">app.<span class="title function_">on</span>(<span class="string">&#x27;push&#x27;</span>, <span class="keyword">async</span> context =&gt; &#123;</span><br><span class="line">  <span class="keyword">if</span> (!<span class="title function_">isTuya</span>(context)) <span class="keyword">return</span>;</span><br><span class="line">  <span class="keyword">if</span> (</span><br><span class="line">    context.<span class="property">payload</span>.<span class="property">ref</span>.<span class="title function_">indexOf</span>(context.<span class="property">payload</span>.<span class="property">repository</span>.<span class="property">default_branch</span>) !==</span><br><span class="line">      -<span class="number">1</span> &amp;&amp;</span><br><span class="line">    context.<span class="property">payload</span>.<span class="property">head_commit</span>.<span class="property">message</span>.<span class="title function_">indexOf</span>(<span class="string">&#x27;chore(release): publish&#x27;</span>) === <span class="number">0</span></span><br><span class="line">  ) &#123;</span><br><span class="line">    app.<span class="title function_">log</span>(<span class="string">&#x27;push event&#x27;</span>);</span><br><span class="line">    <span class="title function_">execSh</span>(<span class="string">`git --version`</span>);</span><br><span class="line">    <span class="keyword">const</span> &#123; <span class="attr">repository</span>: repo &#125; = context.<span class="property">payload</span>;</span><br><span class="line">    <span class="keyword">const</span> cloneTempDir = <span class="string">`<span class="subst">$&#123;os.tmpdir()&#125;</span>/<span class="subst">$&#123;repo.full_name&#125;</span>`</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      <span class="keyword">await</span> git.<span class="title function_">clone</span>(repo.<span class="property">clone_url</span>, cloneTempDir);</span><br><span class="line">      <span class="keyword">const</span> &#123; devDependencies &#125; = <span class="built_in">require</span>(<span class="string">`<span class="subst">$&#123;cloneTempDir&#125;</span>/package.json`</span>);</span><br><span class="line">      <span class="keyword">if</span> (devDependencies[<span class="string">&#x27;lerna&#x27;</span>]) &#123;</span><br><span class="line">        app.<span class="title function_">log</span>(<span class="string">&#x27;lerna publishing&#x27;</span>);</span><br><span class="line">        <span class="keyword">await</span> <span class="title function_">execSh</span>(</span><br><span class="line">          <span class="string">`cd <span class="subst">$&#123;cloneTempDir&#125;</span> &amp;&amp; echo //registry.npmjs.org/:_authToken=<span class="subst">$&#123;process.env.NPM_AUTH_TOKEN&#125;</span> &gt; .npmrc`</span>,</span><br><span class="line">        );</span><br><span class="line">        <span class="keyword">await</span> <span class="title function_">execSh</span>(</span><br><span class="line">          <span class="string">`cd <span class="subst">$&#123;cloneTempDir&#125;</span> &amp;&amp; npm install &amp;&amp; npm run build &amp;&amp; ./node_modules/.bin/lerna publish from-git --yes --no-verify-access`</span>,</span><br><span class="line">        );</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">await</span> <span class="title function_">execSh</span>(<span class="string">`rm -rf <span class="subst">$&#123;cloneTempDir&#125;</span>`</span>);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">      <span class="keyword">await</span> <span class="title function_">execSh</span>(<span class="string">`rm -rf <span class="subst">$&#123;cloneTempDir&#125;</span>`</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="Glitch-部署"><a href="#Glitch-部署" class="headerlink" title="Glitch 部署"></a>Glitch 部署</h2><p>如果你有自己的服务器，可以直接将机器人程序部署到自己的服务器。我这里使用官方推荐的 Glitch 服务部署。Glitch 可以免费托管 node 应用并且直接在浏览器中编辑他们。对于简单的应用完全够了。</p><ol><li>注册并在 <a href="https://glitch.com/">Glitch</a> 新建项目，选择 <strong>Import from GitHub</strong>，弹窗写上应用 github 地址，或者使用 <a href="https://github.com/behaviorbot/new-issue-welcome">https://github.com/behaviorbot/new-issue-welcome</a> 作为模板导入后再将自己的代码复制过来。</li><li>打开 <code>.env</code> 文件使用以下内容替代：</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">APP_ID=&lt;your app id&gt;</span><br><span class="line">WEBHOOK_SECRET=&lt;your app secret&gt;</span><br><span class="line">PRIVATE_KEY_PATH=&lt;your private_key&gt;</span><br><span class="line">NODE_ENV=production</span><br><span class="line">NPM_AUTH_TOKEN=3c2c104e-9f1f-4fc5-903e-726610b75ce1</span><br><span class="line">INPUT_TOKEN=</span><br></pre></td></tr></table></figure><ol start="4"><li>将 glitch 链接设置为 GitHub App 的 webhook 地址即可，之后更新代码，glitch 会自动更新部署。</li></ol><h2 id="权限"><a href="#权限" class="headerlink" title="权限"></a>权限</h2><p>Probot App 的初始权限在 <code>app.yml</code> 文件中，如果 App 已经创建了，又想要更新权限，可以在 <a href="https://github.com/settings/apps">https://github.com/settings/apps</a> 中更新。我所用的权限配置请点击 <a href="https://glitch.com/edit/#!/tuya-robot?path=app.yml%3A119%3A0">app.yml</a> 查看。</p><blockquote><p>本文首发于「<a href="https://blog.zisheng.pro/">紫升的博客</a>」，同步于公众号「<a href="https://cdn.jsdelivr.net/gh/youngjuning/images/20210418112129.jpeg">紫升早茶馆</a>」和「<a href="https://juejin.cn/user/325111174662855">掘金专栏</a>」。</p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/aa64469afff6/</id>
    <link href="https://blog.zisheng.pro/posts/aa64469afff6/"/>
    <published>2023-03-08T09:21:07.000Z</published>
    <summary>GitHub Apps 是 GitHub 中优秀的产品。一个 GitHub App 通过 API 直接使用自己的身份进行操作，这意味着你不需要作为一个单独的用户维护一个机器人或服务账户。</summary>
    <title>基于 Probot 实现 GitHub NPM 发布机器人🤖</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="JavaScript" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/JavaScript/"/>
    <category term="JavaScript" scheme="https://blog.zisheng.pro/tags/JavaScript/"/>
    <category term="Array" scheme="https://blog.zisheng.pro/tags/Array/"/>
    <category term="ECMAScript" scheme="https://blog.zisheng.pro/tags/ECMAScript/"/>
    <content>
      <![CDATA[<p>ECMAScript 数组的每一项可以保存任何类型的数据；而且大小是可以动态调整的，即可以随着数据的添加自动增长以容纳新增数据。</p><h1 id="创建数组"><a href="#创建数组" class="headerlink" title="创建数组"></a>创建数组</h1><h2 id="构造函数"><a href="#构造函数" class="headerlink" title="构造函数"></a>构造函数</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> colors = <span class="keyword">new</span> <span class="title class_">Array</span>() <span class="comment">// []</span></span><br><span class="line">colors = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="number">10</span>) <span class="comment">// [empty × 10]</span></span><br><span class="line">colors = <span class="keyword">new</span> <span class="title class_">Array</span>(<span class="string">&#x27;red&#x27;</span>, <span class="string">&#x27;blue&#x27;</span>, <span class="string">&#x27;green&#x27;</span>) <span class="comment">// [&quot;red&quot;, &quot;blue&quot;, &quot;green&quot;]</span></span><br></pre></td></tr></table></figure><h2 id="数组字面量表示法"><a href="#数组字面量表示法" class="headerlink" title="数组字面量表示法"></a>数组字面量表示法</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> colors = [] <span class="comment">// 创建一个空数组</span></span><br><span class="line"><span class="keyword">var</span> colors = [<span class="string">&#x27;red&#x27;</span>, <span class="string">&#x27;blue&#x27;</span>, <span class="string">&#x27;green&#x27;</span>] <span class="comment">// 创建一个包含 3 个字符串的数组</span></span><br><span class="line"><span class="keyword">var</span> values = [<span class="number">1</span>, <span class="number">2</span>] <span class="comment">// 不要这样！这样会创建一个包含2或3项的数组</span></span><br><span class="line"><span class="keyword">var</span> options = [, , , , ,] <span class="comment">// 不要这样！这样会创建一个包含5或6项的数组</span></span><br></pre></td></tr></table></figure><h1 id="读取和设置数组的值"><a href="#读取和设置数组的值" class="headerlink" title="读取和设置数组的值"></a>读取和设置数组的值</h1><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> colors = [<span class="string">&#x27;red&#x27;</span>, <span class="string">&#x27;blue&#x27;</span>, <span class="string">&#x27;green&#x27;</span>] <span class="comment">// 创建一个包含3个字符串的数组</span></span><br><span class="line"><span class="title function_">alert</span>(colors[<span class="number">0</span>]) <span class="comment">// 显示第1项</span></span><br><span class="line">colors[<span class="number">2</span>] = <span class="string">&#x27;black&#x27;</span> <span class="comment">// 修改第3项</span></span><br><span class="line">colors[<span class="number">3</span>] = <span class="string">&#x27;brown&#x27;</span> <span class="comment">// 新增第4项</span></span><br></pre></td></tr></table></figure><h1 id="length-属性"><a href="#length-属性" class="headerlink" title="length 属性"></a>length 属性</h1><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> colors = [<span class="string">&#x27;red&#x27;</span>, <span class="string">&#x27;blue&#x27;</span>, <span class="string">&#x27;green&#x27;</span>] <span class="comment">// 创建一个包含3个字符串的数组</span></span><br><span class="line"><span class="title function_">alert</span>(colors.<span class="property">length</span>) <span class="comment">// 显示数组长度</span></span><br><span class="line">colors[colors.<span class="property">length</span>] = <span class="string">&#x27;black&#x27;</span> <span class="comment">// 在位置3添加一种颜色</span></span><br><span class="line">colors[colors.<span class="property">length</span>] = <span class="string">&#x27;brown&#x27;</span> <span class="comment">// 在位置4再添加一种颜色</span></span><br></pre></td></tr></table></figure><h1 id="检测数组"><a href="#检测数组" class="headerlink" title="检测数组"></a>检测数组</h1><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> array = []</span><br><span class="line"><span class="keyword">if</span> (<span class="title class_">Array</span>.<span class="title function_">isArray</span>(array)) &#123;</span><br><span class="line">  <span class="comment">// 对数组执行某些操作</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Array-isArray-Polyfill"><a href="#Array-isArray-Polyfill" class="headerlink" title="Array.isArray Polyfill"></a>Array.isArray Polyfill</h2><p>如果是实际项目可以配置 <code>@babel/polyfill</code> 或者 <code>transform-runtime</code>，这不在本文的讨论范围</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (!<span class="title class_">Array</span>.<span class="property">isArray</span>) &#123;</span><br><span class="line">  <span class="title class_">Array</span>.<span class="property">isArray</span> = <span class="keyword">function</span>(<span class="params">arg</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title class_">Object</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span>.<span class="title function_">call</span>(arg) === <span class="string">&#x27;[object Array]&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="ES1-方法"><a href="#ES1-方法" class="headerlink" title="ES1 方法"></a>ES1 方法</h1><ul><li><code>Array.prototype.join</code>: <code>join()</code> 方法将一个数组（或一个类数组对象）的所有元素连接成一个字符串并返回这个字符串。如果数组只有一个项目，那么将返回该项目而不使用分隔符。<strong>原始数组不会被改变</strong>。</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// join</span></span><br><span class="line"><span class="keyword">var</span> colors = [<span class="string">&#x27;red&#x27;</span>, <span class="string">&#x27;blue&#x27;</span>, <span class="string">&#x27;green&#x27;</span>]</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(colors.<span class="title function_">join</span>()) <span class="comment">// &quot;red,blue,green&quot;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(colors.<span class="title function_">join</span>(<span class="string">&#x27;|&#x27;</span>)) <span class="comment">// &quot;red|blue|green&quot;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(colors.<span class="title function_">join</span>(<span class="string">&#x27;|&#x27;</span>)) <span class="comment">// &quot;red|blue|green&quot;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;red|blue|green&#x27;</span>.<span class="title function_">split</span>(<span class="string">&#x27;|&#x27;</span>)) <span class="comment">// [&quot;red&quot;, &quot;blue&quot;, &quot;green&quot;]</span></span><br></pre></td></tr></table></figure><ul><li><code>Array.prototype.reverse()</code>: <code>reverse()</code> 方法将数组中元素的位置颠倒，并返回该数组。<strong>该方法会改变原数组</strong>。</li><li><code>Array.prototype.sort()</code>: <code>sort()</code> 方法用原地算法对数组的元素进行排序，并返回数组。默认排序顺序是在将元素转换为字符串，然后比较它们的 UTF-16 代码单元值序列时构建的; sort 方法接收一个比较函数作为参数，如果第一参数应该位于第二个参数之前则返回负数，如果第一个参数应该位于第二个之后则返回一个正数。<strong>该方法会改变原数组</strong>。</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// reverse</span></span><br><span class="line"><span class="keyword">var</span> colors = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>]</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(colors.<span class="title function_">reverse</span>()) <span class="comment">// [5, 4, 3, 2, 1]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// sort</span></span><br><span class="line"><span class="keyword">var</span> colors = [<span class="number">1</span>, <span class="number">10</span>, <span class="number">3</span>, <span class="number">20</span>, <span class="number">5</span>]</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">sort</span>(<span class="params">arr, asc = <span class="literal">true</span></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> arr.<span class="title function_">sort</span>(<span class="function">(<span class="params">a, b</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (asc) &#123;</span><br><span class="line">      <span class="keyword">return</span> a - b</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> b - a</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">sort</span>(colors)) <span class="comment">// [1, 3, 5, 10, 20]</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">sort</span>(colors, <span class="literal">false</span>)) <span class="comment">// [20, 10, 5, 3, 1]</span></span><br></pre></td></tr></table></figure><h1 id="ES3-方法"><a href="#ES3-方法" class="headerlink" title="ES3 方法"></a>ES3 方法</h1><ul><li><code>Array.prototype.push</code>: <code>push()</code> 方法可以接受任意数量的参数，把它们逐个添加到数组末尾，并返回修改后数组的长度。<strong>该方法会改变原数组</strong>。</li><li><code>Array.prototype.pop</code>: <code>pop()</code> 方法从数组末尾移除最后一项，减少数组的 <code>length</code> 值，并返回该项。<strong>该方法会改变原数组</strong>。</li><li><code>Array.prototype.shift</code>: <code>shift()</code> 方法能够移除数组中的第一项并返回该项, 同时将数组长度减 1。<strong>该方法会改变原数组</strong>。</li><li><code>Array.prototype.unshift</code>: <code>unshift()</code> 方法能够在数组前端添加任意个项，并返回新数组的长度。<strong>该方法会改变原数组</strong>。</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// push、pop</span></span><br><span class="line"><span class="keyword">var</span> colors = [<span class="string">&#x27;red&#x27;</span>, <span class="string">&#x27;blue&#x27;</span>, <span class="string">&#x27;green&#x27;</span>]</span><br><span class="line"><span class="keyword">var</span> count = colors.<span class="title function_">push</span>(<span class="string">&#x27;black&#x27;</span>, <span class="string">&#x27;brown&#x27;</span>) <span class="comment">// 向末尾推入两项</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(count) <span class="comment">// 5</span></span><br><span class="line"><span class="keyword">var</span> item = colors.<span class="title function_">pop</span>() <span class="comment">// 取得最后一项</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(item) <span class="comment">// &quot;brown&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// shift、unshift</span></span><br><span class="line"><span class="keyword">var</span> colors = [<span class="string">&#x27;red&#x27;</span>, <span class="string">&#x27;blue&#x27;</span>, <span class="string">&#x27;green&#x27;</span>]</span><br><span class="line"><span class="keyword">var</span> count = colors.<span class="title function_">unshift</span>(<span class="string">&#x27;black&#x27;</span>, <span class="string">&#x27;brown&#x27;</span>) <span class="comment">// 向开头推入两项</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(count) <span class="comment">// 5</span></span><br><span class="line"><span class="keyword">var</span> item = colors.<span class="title function_">shift</span>() <span class="comment">// 取得第一项</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(item) <span class="comment">// &quot;red&quot;</span></span><br></pre></td></tr></table></figure><ul><li><code>Array.prototype.concat()</code>: <code>concat()</code> 方法用于合并两个或多个数组。<strong>原始数组不会被改变</strong>，而是返回一个新数组。</li><li><code>Array.prototype.slice()</code>: <code>slice()</code> 方法返回一个新的数组对象，这一对象是一个由 <code>begin</code> 和 <code>end</code> 决定的原数组的浅拷贝（包括 <code>begin</code>，不包括 <code>end</code>）。<strong>原始数组不会被改变</strong>。</li><li><code>Array.prototype.splice()</code>: <code>splice()</code> 方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。<strong>此方法会改变原数组</strong>。<ul><li><strong>删除</strong>: 可以删除任意数量的项，只需指定 2 个参数: 要删除的第一项的位置和要删除的项数。例如，<code>splice(0, 2)</code> 会删除数组中的前两项。</li><li><strong>插入</strong>: 可以向指定位置插入任意数量的项，只需要提供 3 个参数：起始位置、0（要删除的项数）和要插入的项。如果要插入多个项，可以再传入任意多个项。例如，<code>splice(2, 0, &quot;red&quot;, &quot;green&quot;)</code> 会从当前数组的位置 2 开始插入字符串 <code>&quot;red&quot;</code> 和 <code>green</code>。</li><li><strong>替换</strong>: 可以向指定位置插入任意数量的项，且同时删除任意数量的项，只需要指定 3 个参数：起始位置、要删除的项数和要插入的任意数量的项。插入的项不必与删除的项数相等。例如，<code>splice(2, 1, &quot;red&quot;, &quot;green&quot;)</code> 会删除当前数组位置 2 的项，然后再从位置 2 开始插入字符串 <code>&quot;red&quot;</code> 和 <code>green</code>。</li></ul></li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// concat</span></span><br><span class="line"><span class="keyword">var</span> colors = [<span class="string">&#x27;red&#x27;</span>, <span class="string">&#x27;blue&#x27;</span>, <span class="string">&#x27;green&#x27;</span>]</span><br><span class="line"><span class="keyword">var</span> colors1 = [<span class="string">&#x27;black&#x27;</span>, <span class="string">&#x27;brown&#x27;</span>]</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(colors.<span class="title function_">concat</span>(colors1)) <span class="comment">// [&quot;red&quot;, &quot;blue&quot;, &quot;green&quot;, &quot;black&quot;, &quot;brown&quot;]</span></span><br><span class="line"><span class="comment">// slice</span></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 如果 `slice()` 方法的参数中有一个负数，则用数组长度加上该数来确定相应地位置。例如，在一个包含5项的数组上调用 `slice(-2, -1)` 与调用 `slice(3, 4)` 得到的结果相同。如果结束位置小于起始位置，则返回空数组。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">var</span> colors = [<span class="string">&#x27;red&#x27;</span>, <span class="string">&#x27;blue&#x27;</span>, <span class="string">&#x27;green&#x27;</span>, <span class="string">&#x27;black&#x27;</span>, <span class="string">&#x27;brown&#x27;</span>]</span><br><span class="line"><span class="keyword">var</span> colors2 = colors.<span class="title function_">slice</span>(<span class="number">1</span>)</span><br><span class="line"><span class="keyword">var</span> colors3 = colors.<span class="title function_">slice</span>(<span class="number">1</span>, <span class="number">4</span>)</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(colors2) <span class="comment">// [&quot;blue&quot;, &quot;green&quot;, &quot;black&quot;, &quot;brown&quot;]</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(colors3) <span class="comment">// [&quot;blue&quot;, &quot;green&quot;, &quot;black&quot;]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * splice() 方法始终都会返回一个数组，该数组中包含从原始数组中删除的项（如果没有删除任何项，则返回一个空数组）</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">var</span> colors = [<span class="string">&#x27;red&#x27;</span>, <span class="string">&#x27;green&#x27;</span>, <span class="string">&#x27;blue&#x27;</span>]</span><br><span class="line"><span class="keyword">var</span> removed = colors.<span class="title function_">splice</span>(<span class="number">0</span>, <span class="number">1</span>) <span class="comment">// 删除第一项</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(colors) <span class="comment">// [&quot;green&quot;, &quot;blue&quot;]</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(removed) <span class="comment">// [&quot;red&quot;] 返回的数组中只包含一项</span></span><br><span class="line"></span><br><span class="line">removed = colors.<span class="title function_">splice</span>(<span class="number">1</span>, <span class="number">0</span>, <span class="string">&#x27;yellow&#x27;</span>, <span class="string">&#x27;orange&#x27;</span>) <span class="comment">// 从位置 1 开始插入两项</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(colors) <span class="comment">// [&quot;green&quot;, &quot;red&quot;, &quot;orange&quot;, &quot;blue&quot;]</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(removed) <span class="comment">// 返回的是一个空数组</span></span><br><span class="line"></span><br><span class="line">removed = colors.<span class="title function_">splice</span>(<span class="number">1</span>, <span class="number">1</span>, <span class="string">&#x27;red&#x27;</span>, <span class="string">&#x27;purple&#x27;</span>) <span class="comment">// 删除一项，增加两项</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(colors) <span class="comment">// [&quot;green&quot;, &quot;red&quot;, &quot;purple&quot;, &quot;orange&quot;, &quot;blue&quot;]</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(removed) <span class="comment">// yellow，返回的数组中只包含一项</span></span><br></pre></td></tr></table></figure><h1 id="ES5-方法"><a href="#ES5-方法" class="headerlink" title="ES5 方法"></a>ES5 方法</h1><ul><li><code>Array.prototype.indexOf()</code>: <code>indexOf()</code> 方法返回在数组中可以找到一个给定元素的第一个索引，如果不存在，则返回-1。从数组的前面向后查找，从 <code>fromIndex</code> 处开始。</li><li><code>Array。prototype.lastIndexOf)()</code>: <code>lastIndexOf()</code> 方法返回指定元素在数组中的最后一个索引，如果不存在则返回-1。从数组的后面向前查找，从 <code>fromIndex</code> 处开始。</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> numbers = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">4</span>, <span class="number">3</span>, <span class="number">2</span>, <span class="number">1</span>]</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(numbers.<span class="title function_">indexOf</span>(<span class="number">4</span>)) <span class="comment">// 3</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(numbers.<span class="title function_">lastIndexOf</span>(<span class="number">4</span>)) <span class="comment">// 5</span></span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(numbers.<span class="title function_">indexOf</span>(<span class="number">4</span>, <span class="number">4</span>)) <span class="comment">// 5</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(numbers.<span class="title function_">lastIndexOf</span>(<span class="number">4</span>, <span class="number">4</span>)) <span class="comment">// 3</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> person = &#123; <span class="attr">name</span>: <span class="string">&#x27;Nicholas&#x27;</span> &#125;</span><br><span class="line"><span class="keyword">var</span> people = [&#123; <span class="attr">name</span>: <span class="string">&#x27;Nicholas&#x27;</span> &#125;]</span><br><span class="line"><span class="keyword">var</span> morePeople</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * indexOf、lastIndexOf 在比较第一个参数与数组中的每一项时，会使用全等操作符</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(people.<span class="title function_">indexOf</span>(person)) <span class="comment">// -1</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(morePeople.<span class="title function_">indexOf</span>(person)) <span class="comment">// 0</span></span><br></pre></td></tr></table></figure><ul><li><code>Array.prototype.every()</code>: 对数组中的每一项运行给定函数，如果该函数对每一项都返回 <code>true</code>，则返回 <code>true</code>。</li><li><code>Array.prototype.some()</code>: 对数组中的每一项运行给定函数，如果该函数对任一项返回 <code>true</code>，则返回 <code>true</code>。</li><li><code>Array.prototype.filter()</code>: 对数组中的每一项运行给定函数，返回该函数会返回 <code>true</code> 的项组成的数组。</li><li><code>Array.prototype.map()</code>: 对数组中的每一项运行给定函数，返回每次函数调用的结果组成的数组。</li><li><code>Array.prototype.forEach()</code>: 对数组中的每一项运行给定函数，这个方法没有返回值。</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// every</span></span><br><span class="line"><span class="keyword">var</span> numbers = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">4</span>, <span class="number">3</span>, <span class="number">2</span>, <span class="number">1</span>]</span><br><span class="line"><span class="keyword">var</span> everyResult = numbers.<span class="title function_">every</span>(<span class="function">(<span class="params">item, index, array</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> item &gt; <span class="number">2</span></span><br><span class="line">&#125;)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(everyResult) <span class="comment">// false</span></span><br><span class="line"><span class="comment">// some</span></span><br><span class="line"><span class="keyword">var</span> someResult = numbers.<span class="title function_">some</span>(<span class="function">(<span class="params">item, index, array</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> item &gt; <span class="number">2</span></span><br><span class="line">&#125;)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(someResult) <span class="comment">// true</span></span><br><span class="line"><span class="comment">// filter</span></span><br><span class="line"><span class="keyword">var</span> numbers = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">4</span>, <span class="number">3</span>, <span class="number">2</span>, <span class="number">1</span>]</span><br><span class="line"><span class="keyword">var</span> filterResult = numbers.<span class="title function_">filter</span>(<span class="function">(<span class="params">item, index, array</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> item &gt; <span class="number">2</span></span><br><span class="line">&#125;)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(filterResult) <span class="comment">// [3, 4, 5, 4, 3]</span></span><br><span class="line"><span class="comment">// map</span></span><br><span class="line"><span class="keyword">var</span> numbers = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">4</span>, <span class="number">3</span>, <span class="number">2</span>, <span class="number">1</span>]</span><br><span class="line"><span class="keyword">var</span> mapResult = numbers.<span class="title function_">map</span>(<span class="function">(<span class="params">item, index, array</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> item * <span class="number">2</span></span><br><span class="line">&#125;)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(mapResult) <span class="comment">// [2, 4, 6, 8, 10, 8, 6, 4, 2]</span></span><br><span class="line"><span class="comment">// forEach</span></span><br><span class="line"><span class="keyword">var</span> numbers = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">4</span>, <span class="number">3</span>, <span class="number">2</span>, <span class="number">1</span>]</span><br><span class="line">numbers.<span class="title function_">forEach</span>(<span class="function">(<span class="params">item, index, array</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// 执行某些操作</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><ul><li><code>Array.prototype.reduce()</code></li><li><code>Array.prototype.reduceRight()</code></li></ul><p><code>reduce()</code> 和 <code>reduceRight()</code> 方法都会迭代数组的所有项，然后构建一个最终返回的值。其中，<code>reduce()</code> 方法从数组的第一项开始，逐个遍历到最后。而 <code>reduceRight()</code> 则从数组的最后一项开始，向前遍历到第一项。</p><p>这两个参数都接受两个参数: 一个在每一项上调用的函数和（可选的）作为归并基础的初始值。传给 <code>reduce</code> 和 <code>reduceRight</code> 的函数接受 4 个参数：前一个值、当前值、项的索引和数组对象。这个函数返回的任何值都会作为第一个参数自动传给下一项。第一次迭代发生在数组的第二项上，因此第一个参数是数组的第一项，第二个参数就是数组的第二项。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 数组求和</span></span><br><span class="line"><span class="keyword">var</span> values = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>]</span><br><span class="line"><span class="keyword">var</span> sum = values.<span class="title function_">reduce</span>(<span class="function">(<span class="params">prev, cur, index, array</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> prev + cur</span><br><span class="line">&#125;)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(sum)</span><br><span class="line"><span class="comment">// 数组求乘积</span></span><br><span class="line"><span class="keyword">var</span> values = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>]</span><br><span class="line"><span class="keyword">var</span> pro = values.<span class="title function_">reduce</span>(<span class="function">(<span class="params">prev, cur, index, array</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> prev * cur</span><br><span class="line">&#125;)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(pro) <span class="comment">// 120</span></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 【累加对象数组里的值】</span></span><br><span class="line"><span class="comment"> * 要累加对象数组中包含的值，必须提供初始值，以便各个item正确通过你的函数。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">var</span> values = [&#123; <span class="attr">x</span>: <span class="number">1</span> &#125;, &#123; <span class="attr">x</span>: <span class="number">2</span> &#125;, &#123; <span class="attr">x</span>: <span class="number">3</span> &#125;]</span><br><span class="line"><span class="keyword">var</span> sum = values.<span class="title function_">reduce</span>(<span class="function">(<span class="params">prev, cur</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> prev + cur.<span class="property">x</span></span><br><span class="line">&#125;, <span class="number">0</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(sum) <span class="comment">// 6</span></span><br><span class="line"><span class="comment">// 求最大值</span></span><br><span class="line"><span class="keyword">var</span> values = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>]</span><br><span class="line"><span class="keyword">var</span> max = values.<span class="title function_">reduce</span>(<span class="function">(<span class="params">prev, cur, index, array</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> prev &gt; cur ? prev : cur</span><br><span class="line">&#125;)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(max) <span class="comment">// 5</span></span><br><span class="line"><span class="comment">// 计算数组中每个元素出现的次数</span></span><br><span class="line"><span class="keyword">var</span> names = [<span class="string">&#x27;Alice&#x27;</span>, <span class="string">&#x27;Bob&#x27;</span>, <span class="string">&#x27;Tiff&#x27;</span>, <span class="string">&#x27;Bruce&#x27;</span>, <span class="string">&#x27;Alice&#x27;</span>]</span><br><span class="line"><span class="keyword">var</span> countedNames = names.<span class="title function_">reduce</span>(<span class="function">(<span class="params">allNames, name</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (allNames.<span class="title function_">indexOf</span>(name) &gt; -<span class="number">1</span>) &#123;</span><br><span class="line">    allNames[name]++</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    allNames[name] = <span class="number">1</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> allNames</span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">// 按属性对object array分类</span></span><br><span class="line"><span class="keyword">var</span> people = [</span><br><span class="line">  &#123; <span class="attr">name</span>: <span class="string">&#x27;张三&#x27;</span>, <span class="attr">age</span>: <span class="number">26</span> &#125;,</span><br><span class="line">  &#123; <span class="attr">name</span>: <span class="string">&#x27;李扬&#x27;</span>, <span class="attr">age</span>: <span class="number">25</span> &#125;,</span><br><span class="line">  &#123; <span class="attr">name</span>: <span class="string">&#x27;杨六&#x27;</span>, <span class="attr">age</span>: <span class="number">25</span> &#125;,</span><br><span class="line">  &#123; <span class="attr">name</span>: <span class="string">&#x27;王五&#x27;</span>, <span class="attr">age</span>: <span class="number">26</span> &#125;,</span><br><span class="line">]</span><br><span class="line"><span class="keyword">var</span> <span class="title function_">groupBy</span> = (<span class="params">objectArray, property</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> objectArray.<span class="title function_">reduce</span>(<span class="function">(<span class="params">acc, cur</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> key = cur[property]</span><br><span class="line">    <span class="keyword">if</span> (!acc[key]) &#123;</span><br><span class="line">      acc[key] = [cur]</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      acc[key].<span class="title function_">push</span>(cur)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> acc</span><br><span class="line">  &#125;, &#123;&#125;)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> groupedPeople = <span class="title function_">groupBy</span>(people, <span class="string">&#x27;age&#x27;</span>)</span><br></pre></td></tr></table></figure><p><strong>reduce 高级用法:</strong></p><blockquote><p>搬砖工小王拿到了这样的格式：<code>var arr = [ {n ame: &#39;brick1&#39;}, {name: &#39;brick2&#39;}, {name: &#39;brick3&#39;} ]</code><br>希望得到这样的数据格式：<code>brick1, brick2 &amp; brick3</code><br>当然数组异常流: <code>[{name:&#39;brick1&#39;}]</code> 和空数组传入得到 <code>&quot;brick1&quot;</code> 和空</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> array = [&#123; <span class="attr">name</span>: <span class="string">&#x27;brick11&#x27;</span> &#125;, &#123; <span class="attr">name</span>: <span class="string">&#x27;brick12&#x27;</span> &#125;, &#123; <span class="attr">name</span>: <span class="string">&#x27;brick13&#x27;</span> &#125;]</span><br><span class="line"><span class="keyword">var</span> <span class="title function_">carryBricks</span> = arr =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> arr.<span class="title function_">reduce</span>(<span class="function">(<span class="params">prev, cur, index, array</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (index === <span class="number">0</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> cur.<span class="property">name</span></span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (index === array.<span class="property">length</span> - <span class="number">1</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> prev + <span class="string">&#x27; &amp; &#x27;</span> + cur.<span class="property">name</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> prev + <span class="string">&#x27;,&#x27;</span> + cur.<span class="property">name</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">carryBricks</span>(array)) <span class="comment">// &quot;brick11,brick12 &amp; brick13&quot;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">carryBricks</span>([&#123; <span class="attr">name</span>: <span class="string">&#x27;brick1&#x27;</span> &#125;])) <span class="comment">// &quot;brick1&quot;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">carryBricks</span>([])) <span class="comment">// &quot;&quot;</span></span><br></pre></td></tr></table></figure><h1 id="ES2015-ES6"><a href="#ES2015-ES6" class="headerlink" title="ES2015&#x2F;ES6"></a>ES2015&#x2F;ES6</h1><ul><li><p><code>Array.from</code>：用于将两类对象转为真正的数组：类似数组的对象（array-like object）和可遍历（iterable）的对象（包括 ES6 新增的数据结构 Set 和 Map）</p></li><li><p><code>Array.of</code>：用于将一组值，转换为数组</p></li><li><p><code>arr.fill(value[, start[, end]])</code>：用一个固定值填充一个数组中从起始索引到终止索引内的全部元素。不包括终止索引</p></li><li><p><code>find()</code>：用于找出第一个符合条件的数组成员</p></li><li><p><code>findIndex()</code>：返回第一个符合条件的数组成员的位置，如果所有成员都不符合条件，则返回<code>-1</code></p></li><li><p><code>entries()</code>，<code>keys()</code> 和 values()：<code>keys()</code>是对键名的遍历、<code>values()</code>是对键值的遍历，<code>entries()</code>是对键值对的遍历</p></li></ul><h1 id="ES2016-ES7"><a href="#ES2016-ES7" class="headerlink" title="ES2016&#x2F;ES7"></a>ES2016&#x2F;ES7</h1><ul><li><code>copyWithin()</code>： 方法浅复制数组的一部分到同一数组中的另一个位置，并返回它，不会改变原数组的长度。</li><li><code>includes()</code>：返回一个布尔值，表示某个数组是否包含给定的值</li></ul><h1 id="ES2019-ES10"><a href="#ES2019-ES10" class="headerlink" title="ES2019&#x2F;ES10"></a>ES2019&#x2F;ES10</h1><ul><li><code>flat()</code>：会按照一个可指定的深度递归遍历数组，并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。</li><li><code>flatMap()</code>： 方法首先使用映射函数映射每个元素，然后将结果压缩成一个新数组。它与 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map">map</a> 连着深度值为1的 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat">flat</a> 几乎相同，但 <code>flatMap</code> 通常在合并成一种方法的效率稍微高一些</li></ul><h1 id="lodash"><a href="#lodash" class="headerlink" title="lodash"></a>lodash</h1><h2 id="数组并集"><a href="#数组并集" class="headerlink" title="数组并集"></a>数组并集</h2><ul><li><code>_.union([arrays])</code>: 创建一个按顺序排列的唯一值的数组。所有给定数组的元素值使用 SameValueZero 做等值比较。（ <code>arrays</code>（数组）的并集，按顺序返回，返回数组的元素是唯一的）</li><li><code>_.unionBy([arrays], [iteratee=_.identity])</code>: 这个方法类似 <code>_.union</code> ，除了它接受一个 <code>iteratee</code> （迭代函数），调用每一个数组（<code>array</code>）的每个元素以产生唯一性计算的标准。<code>iteratee</code> 会传入一个参数：(<code>value</code>)。</li><li><code>_.unionWith([arrays], [comparator])</code>: 这个方法类似 <code>_.union</code>， 除了它接受一个 <code>comparator</code> 调用比较 <code>arrays</code> 数组的每一个元素。 <code>comparator</code> 调用时会传入 2 个参数： (<code>arrVal</code>, <code>othVal</code>)。</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> _ <span class="keyword">from</span> <span class="string">&#x27;lodash&#x27;</span></span><br><span class="line"><span class="comment">// union</span></span><br><span class="line">_.<span class="title function_">union</span>([<span class="number">2</span>], [<span class="number">1</span>, <span class="number">2</span>]) <span class="comment">// [2, 1]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// unionBy</span></span><br><span class="line">_.<span class="title function_">unionBy</span>([<span class="number">2.1</span>], [<span class="number">1.2</span>, <span class="number">2.3</span>], <span class="title class_">Math</span>.<span class="property">floor</span>) <span class="comment">// [2.1, 1.2]</span></span><br><span class="line"><span class="comment">// The `_.property` iteratee shorthand.</span></span><br><span class="line">_.<span class="title function_">unionBy</span>([&#123; <span class="attr">x</span>: <span class="number">1</span> &#125;], [&#123; <span class="attr">x</span>: <span class="number">2</span> &#125;, &#123; <span class="attr">x</span>: <span class="number">1</span> &#125;], <span class="string">&#x27;x&#x27;</span>) <span class="comment">// [&#123; &#x27;x&#x27;: 1 &#125;, &#123; &#x27;x&#x27;: 2 &#125;]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// unionWith</span></span><br><span class="line"><span class="keyword">var</span> objects = [</span><br><span class="line">  &#123; <span class="attr">x</span>: <span class="number">1</span>, <span class="attr">y</span>: <span class="number">2</span> &#125;,</span><br><span class="line">  &#123; <span class="attr">x</span>: <span class="number">2</span>, <span class="attr">y</span>: <span class="number">1</span> &#125;,</span><br><span class="line">]</span><br><span class="line"><span class="keyword">var</span> others = [</span><br><span class="line">  &#123; <span class="attr">x</span>: <span class="number">1</span>, <span class="attr">y</span>: <span class="number">1</span> &#125;,</span><br><span class="line">  &#123; <span class="attr">x</span>: <span class="number">1</span>, <span class="attr">y</span>: <span class="number">2</span> &#125;,</span><br><span class="line">]</span><br><span class="line">_.<span class="title function_">unionWith</span>(objects, others, _.<span class="property">isEqual</span>) <span class="comment">// [&#123; &#x27;x&#x27;: 1, &#x27;y&#x27;: 2 &#125;, &#123; &#x27;x&#x27;: 2, &#x27;y&#x27;: 1 &#125;, &#123; &#x27;x&#x27;: 1, &#x27;y&#x27;: 1 &#125;]</span></span><br></pre></td></tr></table></figure><h2 id="数组交集"><a href="#数组交集" class="headerlink" title="数组交集"></a>数组交集</h2><ul><li><code>_.intersection([arrays])</code>: 创建唯一值的数组，这个数组包含所有给定数组都包含的元素，使用 SameValueZero 进行相等性比较。（可以理解为给定数组的交集）</li><li><code>_.intersectionBy([arrays], [iteratee=_.identity])</code>: 这个方法类似 <code>_.intersection</code>，区别是它接受一个 <code>iteratee</code> 调用每一个 arrays 的每个值以产生一个值，通过产生的值进行了比较。结果值是从第一数组中选择。iteratee 会传入一个参数：<code>(value)</code>。</li><li><code>_.intersectionWith([arrays], [comparator])</code>: 这个方法类似 <code>_.intersection</code>，区别是它接受一个 <code>comparator</code> 调用比较 arrays 中的元素。结果值是从第一数组中选择。<code>comparator</code> 会传入两个参数：<code>(arrVal, othVal)</code>。</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> _ <span class="keyword">from</span> <span class="string">&#x27;lodash&#x27;</span></span><br><span class="line"><span class="comment">// intersection</span></span><br><span class="line">_.<span class="title function_">intersection</span>([<span class="number">2</span>, <span class="number">1</span>], [<span class="number">4</span>, <span class="number">2</span>], [<span class="number">1</span>, <span class="number">2</span>])) <span class="comment">// [2]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// intersectionBy</span></span><br><span class="line">_.<span class="title function_">intersectionBy</span>([<span class="number">2.1</span>, <span class="number">1.2</span>], [<span class="number">4.3</span>, <span class="number">2.4</span>], <span class="title class_">Math</span>.<span class="property">floor</span>) <span class="comment">// [2.1]</span></span><br><span class="line"><span class="comment">// The `_.property` iteratee shorthand.</span></span><br><span class="line">_.<span class="title function_">intersectionBy</span>([&#123; <span class="string">&#x27;x&#x27;</span>: <span class="number">1</span> &#125;], [&#123; <span class="string">&#x27;x&#x27;</span>: <span class="number">2</span> &#125;, &#123; <span class="string">&#x27;x&#x27;</span>: <span class="number">1</span> &#125;], <span class="string">&#x27;x&#x27;</span>) <span class="comment">// =&gt; [&#123; &#x27;x&#x27;: 1 &#125;]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// intersectionWith</span></span><br><span class="line"><span class="keyword">var</span> objects = [&#123; <span class="string">&#x27;x&#x27;</span>: <span class="number">1</span>, <span class="string">&#x27;y&#x27;</span>: <span class="number">2</span> &#125;, &#123; <span class="string">&#x27;x&#x27;</span>: <span class="number">2</span>, <span class="string">&#x27;y&#x27;</span>: <span class="number">1</span> &#125;];</span><br><span class="line"><span class="keyword">var</span> others = [&#123; <span class="string">&#x27;x&#x27;</span>: <span class="number">1</span>, <span class="string">&#x27;y&#x27;</span>: <span class="number">1</span> &#125;, &#123; <span class="string">&#x27;x&#x27;</span>: <span class="number">1</span>, <span class="string">&#x27;y&#x27;</span>: <span class="number">2</span> &#125;];</span><br><span class="line">_.<span class="title function_">intersectionWith</span>(objects, others, _.<span class="property">isEqual</span>) <span class="comment">// [&#123; &#x27;x&#x27;: 1, &#x27;y&#x27;: 2 &#125;]</span></span><br></pre></td></tr></table></figure><h2 id="数组差集"><a href="#数组差集" class="headerlink" title="数组差集"></a>数组差集</h2><ul><li><code>_.difference(array, [values])</code>: 创建一个具有唯一 array 值的数组，每个值不包含在其他给定的数组中。（即创建一个新数组，这个数组中的值，为第一个数字（array 参数）排除了给定数组中的值。）该方法使用 SameValueZero 做相等比较。结果值的顺序是由第一个数组中的顺序确定。</li><li><code>_.differenceBy(array, [values], [iteratee=_.identity])</code>: 这个方法类似 _.difference ，除了它接受一个 iteratee （迭代器）， 调用 array 和 values 中的每个元素以产生比较的标准。 结果值是从第一数组中选择。iteratee 会调用一个参数：(value)。（首先使用迭代器分别迭代 array 和 values 中的每个元素，返回的值作为比较值）。</li><li><code>_.differenceWith(array, [values], [comparator])</code>: 这个方法类似 <code>_.difference</code> ，除了它接受一个 <code>comparator</code> （比较器），它调用比较 <code>array</code>，<code>values</code> 中的元素。 结果值是从第一数组中选择。<code>comparator</code> 调用参数有两个：<code>(arrVal, othVal)</code>。</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> _ <span class="keyword">from</span> <span class="string">&#x27;lodash&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// difference</span></span><br><span class="line">_.<span class="title function_">difference</span>([<span class="number">3</span>, <span class="number">2</span>, <span class="number">1</span>], [<span class="number">4</span>, <span class="number">2</span>]) <span class="comment">// [3, 1]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// differenceBy</span></span><br><span class="line">_.<span class="title function_">differenceBy</span>([<span class="number">3.1</span>, <span class="number">2.2</span>, <span class="number">1.3</span>], [<span class="number">4.4</span>, <span class="number">2.5</span>], <span class="title class_">Math</span>.<span class="property">floor</span>) <span class="comment">// [3.1, 1.3]</span></span><br><span class="line"><span class="comment">// The `_.property` iteratee shorthand.</span></span><br><span class="line">_.<span class="title function_">differenceBy</span>([&#123; <span class="attr">x</span>: <span class="number">2</span> &#125;, &#123; <span class="attr">x</span>: <span class="number">1</span> &#125;], [&#123; <span class="attr">x</span>: <span class="number">1</span> &#125;], <span class="string">&#x27;x&#x27;</span>) <span class="comment">// [&#123; &#x27;x&#x27;: 2 &#125;]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// differenceWith</span></span><br><span class="line"><span class="keyword">var</span> objects = [</span><br><span class="line">  &#123; <span class="attr">x</span>: <span class="number">1</span>, <span class="attr">y</span>: <span class="number">2</span> &#125;,</span><br><span class="line">  &#123; <span class="attr">x</span>: <span class="number">2</span>, <span class="attr">y</span>: <span class="number">1</span> &#125;,</span><br><span class="line">]</span><br><span class="line">_.<span class="title function_">differenceWith</span>(objects, [&#123; <span class="attr">x</span>: <span class="number">1</span>, <span class="attr">y</span>: <span class="number">2</span> &#125;], _.<span class="property">isEqual</span>) <span class="comment">// [&#123; &#x27;x&#x27;: 2, &#x27;y&#x27;: 1 &#125;]</span></span><br></pre></td></tr></table></figure><h2 id="数组分组"><a href="#数组分组" class="headerlink" title="数组分组"></a>数组分组</h2><ul><li><code>_.groupBy(collection, [iteratee=_.identity])</code>: 创建一个对象，<code>key</code> 是 <code>iteratee</code> 遍历 <code>collection</code>(集合) 中的每个元素返回的结果。 分组值的顺序是由他们出现在 <code>collection</code>(集合) 中的顺序确定的。每个键对应的值负责生成 <code>key</code> 的元素组成的数组。<code>iteratee</code> 调用 1 个参数： (<code>value</code>)。</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> _ <span class="keyword">from</span> <span class="string">&#x27;lodash&#x27;</span></span><br><span class="line"></span><br><span class="line">_.<span class="title function_">groupBy</span>([<span class="number">6.1</span>, <span class="number">4.2</span>, <span class="number">6.3</span>], <span class="title class_">Math</span>.<span class="property">floor</span>) <span class="comment">// &#123; &#x27;4&#x27;: [4.2], &#x27;6&#x27;: [6.1, 6.3] &#125;</span></span><br><span class="line"><span class="comment">// The `_.property` iteratee shorthand.</span></span><br><span class="line">_.<span class="title function_">groupBy</span>([<span class="string">&#x27;one&#x27;</span>, <span class="string">&#x27;two&#x27;</span>, <span class="string">&#x27;three&#x27;</span>], <span class="string">&#x27;length&#x27;</span>) <span class="comment">// &#123; &#x27;3&#x27;: [&#x27;one&#x27;, &#x27;two&#x27;], &#x27;5&#x27;: [&#x27;three&#x27;] &#125;</span></span><br><span class="line"><span class="keyword">const</span> students = [</span><br><span class="line">  &#123; <span class="attr">name</span>: <span class="string">&#x27;张三&#x27;</span>, <span class="attr">province</span>: <span class="string">&#x27;河南&#x27;</span> &#125;,</span><br><span class="line">  &#123; <span class="attr">name</span>: <span class="string">&#x27;李四&#x27;</span>, <span class="attr">province</span>: <span class="string">&#x27;河南&#x27;</span> &#125;,</span><br><span class="line">  &#123; <span class="attr">name</span>: <span class="string">&#x27;王五&#x27;</span>, <span class="attr">province</span>: <span class="string">&#x27;江苏&#x27;</span> &#125;,</span><br><span class="line">  &#123; <span class="attr">name</span>: <span class="string">&#x27;黄六&#x27;</span>, <span class="attr">province</span>: <span class="string">&#x27;河南&#x27;</span> &#125;,</span><br><span class="line">]</span><br><span class="line">_.<span class="title function_">groupBy</span>(students, <span class="string">&#x27;province&#x27;</span>)</span><br><span class="line"><span class="comment">// &#123; &#x27;江苏&#x27;: [&#123; name: &#x27;王五&#x27;, province: &#x27;江苏&#x27; &#125;], &#x27;河南&#x27;: [&#123; name: &#x27;张三&#x27;, province: &#x27;河南&#x27; &#125;, &#123; name: &#x27;李四&#x27;, province: &#x27;河南&#x27; &#125;,&#123; name: &#x27;黄六&#x27;, province: &#x27;河南&#x27; &#125;] &#125;</span></span><br></pre></td></tr></table></figure><h2 id="删除数组项"><a href="#删除数组项" class="headerlink" title="删除数组项"></a>删除数组项</h2><h3 id="根据断言删除数组项"><a href="#根据断言删除数组项" class="headerlink" title="根据断言删除数组项"></a>根据断言删除数组项</h3><ul><li><code>remove</code>: 移除数组中 predicate（断言）返回为真值的所有元素，并返回移除元素组成的数组。predicate（断言） 会传入 3 个参数： (<code>value</code>, <code>index</code>, <code>array</code>)。</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> _ <span class="keyword">from</span> <span class="string">&#x27;lodash&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> array = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>]</span><br><span class="line"><span class="keyword">const</span> evens = _.<span class="title function_">remove</span>(array, <span class="function"><span class="params">value</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> value % <span class="number">2</span> == <span class="number">0</span></span><br><span class="line">&#125;)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(array) <span class="comment">// =&gt; [1, 3]</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(evens) <span class="comment">// =&gt; [2, 4]</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> students = [</span><br><span class="line">  &#123; <span class="attr">name</span>: <span class="string">&#x27;张三&#x27;</span>, <span class="attr">province</span>: <span class="string">&#x27;河南&#x27;</span> &#125;,</span><br><span class="line">  &#123; <span class="attr">name</span>: <span class="string">&#x27;李四&#x27;</span>, <span class="attr">province</span>: <span class="string">&#x27;河南&#x27;</span> &#125;,</span><br><span class="line">  &#123; <span class="attr">name</span>: <span class="string">&#x27;王五&#x27;</span>, <span class="attr">province</span>: <span class="string">&#x27;江苏&#x27;</span> &#125;,</span><br><span class="line">  &#123; <span class="attr">name</span>: <span class="string">&#x27;黄六&#x27;</span>, <span class="attr">province</span>: <span class="string">&#x27;河南&#x27;</span> &#125;,</span><br><span class="line">]</span><br><span class="line"><span class="keyword">const</span> removedStudent = _.<span class="title function_">remove</span>(students, <span class="function"><span class="params">student</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> student.<span class="property">province</span> === <span class="string">&#x27;江苏&#x27;</span></span><br><span class="line">&#125;)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(students)</span><br><span class="line"><span class="comment">// [&#123;name: &quot;张三&quot;, province: &quot;河南&quot;&#125;, &#123;name: &quot;李四&quot;, province: &quot;河南&quot;&#125;, &#123;name: &quot;黄六&quot;, province: &quot;河南&quot;&#125;]</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(removedStudent)</span><br><span class="line"><span class="comment">// [&#123;name: &quot;王五&quot;, province: &quot;江苏&quot;&#125;]</span></span><br></pre></td></tr></table></figure><h3 id="根据给定值删除数组项"><a href="#根据给定值删除数组项" class="headerlink" title="根据给定值删除数组项"></a>根据给定值删除数组项</h3><ul><li><code>_.pull(array, [values])</code>: 移除数组 array 中所有和给定值相等的元素，使用 SameValueZero 进行全等比较。</li><li><code>_.pullAll(array, values)</code>: 这个方法类似 _.pull，区别是这个方法接收一个要移除值的数组。</li><li><code>_.pullAllBy(array, values, [iteratee=_.identity])</code>: 这个方法类似于 <code>_.pullAll</code> ，区别是这个方法接受一个 <code>iteratee</code>（迭代函数） 调用 <code>array</code> 和 <code>values</code>的每个值以产生一个值，通过产生的值进行了比较。<code>iteratee</code> 会传入一个参数： (<code>value</code>)。</li><li><code>_.pullAllWith(array, values, [comparator])</code>: 这个方法类似于 <code>_.pullAll</code>，区别是这个方法接受 <code>comparator</code> 调用 <code>array</code> 中的元素和 <code>values</code> 比较。<code>comparator</code> 会传入两个参数：(<code>arrVal</code>, <code>othVal</code>)。</li></ul><blockquote><p>pull 系列和 difference 系列不同之处在于 pull 系列方法会改变数组 array</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> _ <span class="keyword">from</span> <span class="string">&#x27;lodash&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// pull</span></span><br><span class="line"><span class="keyword">var</span> array = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br><span class="line">_.<span class="title function_">pull</span>(array, <span class="number">2</span>, <span class="number">3</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(array) <span class="comment">// [1, 1]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// pullAll</span></span><br><span class="line"><span class="keyword">var</span> array = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br><span class="line">_.<span class="title function_">pullAll</span>(array, [<span class="number">2</span>, <span class="number">3</span>])</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(array) <span class="comment">// [1, 1]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// pullAllBy</span></span><br><span class="line"><span class="keyword">var</span> array = [&#123; <span class="attr">x</span>: <span class="number">1</span> &#125;, &#123; <span class="attr">x</span>: <span class="number">2</span> &#125;, &#123; <span class="attr">x</span>: <span class="number">3</span> &#125;, &#123; <span class="attr">x</span>: <span class="number">1</span> &#125;]</span><br><span class="line">_.<span class="title function_">pullAllBy</span>(array, [&#123; <span class="attr">x</span>: <span class="number">2</span> &#125;, &#123; <span class="attr">x</span>: <span class="number">3</span> &#125;], <span class="string">&#x27;x&#x27;</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(array) <span class="comment">// [&#123; &#x27;x&#x27;: 1 &#125;, &#123; &#x27;x&#x27;: 1 &#125;]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// pullAllWith</span></span><br><span class="line"><span class="keyword">var</span> array = [</span><br><span class="line">  &#123; <span class="attr">x</span>: <span class="number">1</span>, <span class="attr">y</span>: <span class="number">2</span> &#125;,</span><br><span class="line">  &#123; <span class="attr">x</span>: <span class="number">3</span>, <span class="attr">y</span>: <span class="number">4</span> &#125;,</span><br><span class="line">  &#123; <span class="attr">x</span>: <span class="number">5</span>, <span class="attr">y</span>: <span class="number">6</span> &#125;,</span><br><span class="line">]</span><br><span class="line">_.<span class="title function_">pullAllWith</span>(array, [&#123; <span class="attr">x</span>: <span class="number">3</span>, <span class="attr">y</span>: <span class="number">4</span> &#125;], _.<span class="property">isEqual</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(array) <span class="comment">// [&#123; &#x27;x&#x27;: 1, &#x27;y&#x27;: 2 &#125;, &#123; &#x27;x&#x27;: 5, &#x27;y&#x27;: 6 &#125;]</span></span><br></pre></td></tr></table></figure><ul><li><code>_.pullAt(array, [indexes])</code>: 根据索引 <code>indexes</code>，移除 <code>array</code> 中对应的元素，并返回被移除元素的数组。</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> _ <span class="keyword">from</span> <span class="string">&#x27;lodash&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> array = [<span class="number">5</span>, <span class="number">10</span>, <span class="number">15</span>, <span class="number">20</span>]</span><br><span class="line"><span class="keyword">var</span> evens = _.<span class="title function_">pullAt</span>(array, <span class="number">1</span>, <span class="number">3</span>)</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(array) <span class="comment">// [5, 15]</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(evens) <span class="comment">// [10, 20]</span></span><br></pre></td></tr></table></figure><ul><li><code>_.without(array, [values])</code>: 创建一个剔除所有给定值的新数组，剔除值的时候，使用 SameValueZero 做相等比较。不像 <code>_.pull</code>, 这个方法会返回一个新数组。</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> _ <span class="keyword">from</span> <span class="string">&#x27;lodash&#x27;</span></span><br><span class="line"></span><br><span class="line">_.<span class="title function_">without</span>([<span class="number">2</span>, <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>], <span class="number">1</span>, <span class="number">2</span>) <span class="comment">// [3]</span></span><br></pre></td></tr></table></figure><h2 id="数组去重"><a href="#数组去重" class="headerlink" title="数组去重"></a>数组去重</h2><ul><li><code>_.uniq(array)</code>: 创建一个去重后的 array 数组副本。使用了 SameValueZero 做等值比较。只有第一次出现的元素才会被保留。</li><li><code>_.uniqBy(array, [iteratee=_.identity])</code>: 这个方法类似 <code>_.uniq</code> ，除了它接受一个 <code>iteratee</code>（迭代函数），调用每一个数组（<code>array</code>）的每个元素以产生唯一性计算的标准。<code>iteratee</code> 调用时会传入一个参数：(<code>value</code>)。</li><li><code>_.uniqWith(array, [comparator])</code>: 这个方法类似 <code>_.uniq</code>， 除了它接受一个 <code>comparator</code> 调用比较 <code>array</code> 数组的每一个元素。 <code>comparator</code> 调用时会传入 2 个参数：(<code>arrVal</code>, <code>othVal</code>)。</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> _ <span class="keyword">from</span> <span class="string">&#x27;lodash&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// uniq</span></span><br><span class="line">_.<span class="title function_">uniq</span>([<span class="number">2</span>, <span class="number">1</span>, <span class="number">2</span>]) <span class="comment">// [2, 1]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// uniqBy</span></span><br><span class="line">_.<span class="title function_">uniqBy</span>([<span class="number">2.1</span>, <span class="number">1.2</span>, <span class="number">2.3</span>], <span class="title class_">Math</span>.<span class="property">floor</span>) <span class="comment">// [2.1, 1.2]</span></span><br><span class="line"><span class="comment">// The `_.property` iteratee shorthand.</span></span><br><span class="line">_.<span class="title function_">uniqBy</span>([&#123; <span class="attr">x</span>: <span class="number">1</span> &#125;, &#123; <span class="attr">x</span>: <span class="number">2</span> &#125;, &#123; <span class="attr">x</span>: <span class="number">1</span> &#125;], <span class="string">&#x27;x&#x27;</span>) <span class="comment">// [&#123; &#x27;x&#x27;: 1 &#125;, &#123; &#x27;x&#x27;: 2 &#125;]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// uniqWith</span></span><br><span class="line"><span class="keyword">var</span> objects = [</span><br><span class="line">  &#123; <span class="attr">x</span>: <span class="number">1</span>, <span class="attr">y</span>: <span class="number">2</span> &#125;,</span><br><span class="line">  &#123; <span class="attr">x</span>: <span class="number">2</span>, <span class="attr">y</span>: <span class="number">1</span> &#125;,</span><br><span class="line">  &#123; <span class="attr">x</span>: <span class="number">1</span>, <span class="attr">y</span>: <span class="number">2</span> &#125;,</span><br><span class="line">]</span><br><span class="line">_.<span class="title function_">uniqWith</span>(objects, _.<span class="property">isEqual</span>) <span class="comment">// [&#123; &#x27;x&#x27;: 1, &#x27;y&#x27;: 2 &#125;, &#123; &#x27;x&#x27;: 2, &#x27;y&#x27;: 1 &#125;]</span></span><br></pre></td></tr></table></figure><h2 id="数组排序"><a href="#数组排序" class="headerlink" title="数组排序"></a>数组排序</h2><ul><li><code>_.sortBy(collection, [iteratees=[_.identity]])</code>: 创建一个元素数组。 以 <code>iteratee</code> 处理的结果升序排序。 这个方法执行稳定排序，也就是说相同元素会保持原始排序。 <code>iteratees</code> 调用1个参数：(<code>value</code>)。可以使用 <code>Array.prototype.reverse</code> 方法倒序</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> _ <span class="keyword">from</span> <span class="string">&#x27;lodash&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> users = [</span><br><span class="line">  &#123; <span class="attr">user</span>: &#123; <span class="attr">name</span>: <span class="string">&#x27;fred&#x27;</span> &#125;, <span class="attr">age</span>: <span class="number">48</span> &#125;,</span><br><span class="line">  &#123; <span class="attr">user</span>: &#123; <span class="attr">name</span>: <span class="string">&#x27;barney&#x27;</span> &#125;, <span class="attr">age</span>: <span class="number">36</span> &#125;,</span><br><span class="line">  &#123; <span class="attr">user</span>: &#123; <span class="attr">name</span>: <span class="string">&#x27;fred&#x27;</span> &#125;, <span class="attr">age</span>: <span class="number">40</span> &#125;,</span><br><span class="line">  &#123; <span class="attr">user</span>: &#123; <span class="attr">name</span>: <span class="string">&#x27;barney&#x27;</span> &#125;, <span class="attr">age</span>: <span class="number">34</span> &#125;,</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="comment">// The `_.property` iteratee shorthand.</span></span><br><span class="line">_.<span class="title function_">sortBy</span>(users, <span class="string">&#x27;user.name&#x27;</span>)</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * [</span></span><br><span class="line"><span class="comment"> *  &#123;age: 34, user: &quot;barney&quot;&#125;,</span></span><br><span class="line"><span class="comment"> *  &#123;age: 36, user: &quot;barney&quot;&#125;,</span></span><br><span class="line"><span class="comment"> *  &#123;age: 40, user: &quot;fred&quot;&#125;,</span></span><br><span class="line"><span class="comment"> *  &#123;age: 48, user: &quot;fred&quot;&#125;</span></span><br><span class="line"><span class="comment"> * ]</span></span><br><span class="line"><span class="comment"> */</span></span><br></pre></td></tr></table></figure><h2 id="数组降维"><a href="#数组降维" class="headerlink" title="数组降维"></a>数组降维</h2><ul><li><code>_.flatten(array)</code>: 减少一级 <code>array</code> 嵌套深度。</li><li><code>_.flattenDeep(array)</code>: 将 <code>array</code> 递归为一维数组。</li><li><code>_.flattenDepth(array, [depth=1])</code>: 根据 <code>depth</code> 递归减少 <code>array</code> 的嵌套层级</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> _ <span class="keyword">from</span> <span class="string">&#x27;lodash&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// flatten</span></span><br><span class="line">_.<span class="title function_">flatten</span>([<span class="number">1</span>, [<span class="number">2</span>, [<span class="number">3</span>, [<span class="number">4</span>]], <span class="number">5</span>]]) <span class="comment">// [1, 2, [3, [4]], 5]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// flattenDeep</span></span><br><span class="line">_.<span class="title function_">flattenDeep</span>([<span class="number">1</span>, [<span class="number">2</span>, [<span class="number">3</span>, [<span class="number">4</span>]], <span class="number">5</span>]]) <span class="comment">// [1, 2, 3, 4, 5]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// flattenDepth</span></span><br><span class="line"><span class="keyword">var</span> array = [<span class="number">1</span>, [<span class="number">2</span>, [<span class="number">3</span>, [<span class="number">4</span>]], <span class="number">5</span>]]</span><br><span class="line">_.<span class="title function_">flattenDepth</span>(array, <span class="number">1</span>) <span class="comment">// [1, 2, [3, [4]], 5]</span></span><br><span class="line">_.<span class="title function_">flattenDepth</span>(array, <span class="number">2</span>) <span class="comment">// [1, 2, 3, [4], 5]</span></span><br><span class="line">_.<span class="title function_">flattenDepth</span>(array, <span class="number">3</span>) <span class="comment">// [1, 2, 3, 4, 5]</span></span><br></pre></td></tr></table></figure><h2 id="获取部分元素"><a href="#获取部分元素" class="headerlink" title="获取部分元素"></a>获取部分元素</h2><ul><li><code>_.initial(array)</code>: 获取数组 <code>array</code> 中除了最后一个元素之外的所有元素（去除数组array中的最后一个元素）。</li><li><code>_.tail(array)</code>: 获取除了 <code>array</code> 数组第一个元素以外的全部元素。</li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/bb5ef5a07d1c/</id>
    <link href="https://blog.zisheng.pro/posts/bb5ef5a07d1c/"/>
    <published>2023-03-08T09:17:16.000Z</published>
    <summary>ECMAScript 数组的每一项可以保存任何类型的数据；而且大小是可以动态调整的，即可以随着数据的添加自动增长以容纳新增数据。</summary>
    <title>JavaScript Array 完全指南</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React-Native/"/>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/categories/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/tags/React-Native/"/>
    <category term="React Native 原生库" scheme="https://blog.zisheng.pro/tags/React-Native-%E5%8E%9F%E7%94%9F%E5%BA%93/"/>
    <content>
      <![CDATA[<p>react-native-create-library 使你可以使用一个命令创建 React Native 原生库的工具</p><h3 id="一、你为什么需要这个？"><a href="#一、你为什么需要这个？" class="headerlink" title="一、你为什么需要这个？"></a>一、你为什么需要这个？</h3><p>如果您要为 React Native 创建原生模块，则需要为要支持的每个平台提供一些原生代码，然后和一些 JavaScript 代码绑定在一起。自己设置可能非常耗时。</p><p>这就是这个工具的用武之地。它创建了一个包含所有当前最佳实践的样板。为什么不用 <code>react-native new-library</code>？不幸的是，该命令不会创建一个最新的库，需要一个已经初始化的 React Native 项目，并且只设置 iOS 方面的东西。</p><blockquote><p>警告：这仅创建没有视图组件的原生模块。</p></blockquote><h3 id="二、安装"><a href="#二、安装" class="headerlink" title="二、安装"></a>二、安装</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ npm install -g react-native-create-library</span><br></pre></td></tr></table></figure><p>这个命令将会创建一个 <code>MyFancyLibrary</code> 文件夹，现在可以执行 <code>yarn install</code> 来为你新创建的 <code>Library</code> 安装依赖。</p><h3 id="三、命令"><a href="#三、命令" class="headerlink" title="三、命令"></a>三、命令</h3><blockquote><p><code>react-native-create-library [options] &lt;name&gt;</code></p></blockquote><p><strong>选项：</strong></p><ul><li><code>-h</code>、<code>--help</code>: 输入有用的信息</li><li><code>-V</code>、<code>--version</code>: 输出版本号</li><li><code>-p</code>、<code>--prefix &lt;prefix&gt;</code>: 库的前缀（默认：<code>RN</code>）</li><li><code>--module-prefix &lt;modulePrefix&gt;</code>: 库的模块前缀（npm）（默认<code>react-native</code>）</li><li><code>--package-identifier &lt;packageIdentifier&gt;</code>: (Android only!) The package name for the Android module (Default: <code>com.reactlibrary</code>)</li><li><code>--namespace &lt;namespace&gt;</code>: (Windows only!) The namespace for the Windows module(Default: The name as PascalCase)</li><li><code>--platforms &lt;platforms&gt;</code>: 支持的平台（用逗号隔开，默认：<code>ios,android,windows</code>）</li><li><code>--github-account &lt;github_account&gt;</code>: 托管库的 github 账号（默认：<code>github_account</code>）</li><li><code>--author-name &lt;name&gt;</code>: 作者的名字（默认：<code>Your Name</code>）</li><li><code>--author-name &lt;email&gt;</code>: 作者的邮箱（默认：<code>yourname@email.com</code>）</li><li><code>--license &lt;license&gt;</code>: The license type of this library (Default: <code>Apache-2.0</code>)</li><li><code>--generate-example &lt;shouldGenerate&gt;</code>: 会生成一个 RN 例子并且 <code>link</code> 刚生成的库（默认：<code>false</code>）</li></ul><h3 id="四、程序化使用"><a href="#四、程序化使用" class="headerlink" title="四、程序化使用"></a>四、程序化使用</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> createLibrary = <span class="built_in">require</span>(<span class="string">&#x27;react-native-create-library&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="title function_">createLibrary</span>(&#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;MyFancyLibrary&#x27;</span>,</span><br><span class="line">&#125;).<span class="title function_">then</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Oh yay! My library has been created!&#x27;</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h4 id="选项"><a href="#选项" class="headerlink" title="选项"></a>选项</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="title class_">String</span>, <span class="comment">/* The name of the library (Default: Library) */</span></span><br><span class="line">  <span class="attr">prefix</span>: <span class="title class_">String</span>, <span class="comment">/* The prefix for the library (Default: RN) */</span></span><br><span class="line">  <span class="attr">modulePrefix</span>: <span class="title class_">String</span>, <span class="comment">/* The module prefix for the library (Default: react-native) */</span></span><br><span class="line">  <span class="attr">platforms</span>: <span class="title class_">Array</span>, <span class="comment">/* Platforms the library will be created for. (Default: [&#x27;ios&#x27;, &#x27;android&#x27;, &#x27;windows&#x27;]) */</span></span><br><span class="line">  <span class="attr">packageIdentifier</span>: <span class="title class_">String</span>, <span class="comment">/* (Android only!) The package name for the Android module (Default: com.reactlibrary) */</span></span><br><span class="line">  <span class="attr">namespace</span>: <span class="title class_">String</span>, <span class="comment">/* (Windows only!) The namespace for the Windows module (Default: The package identifier as PascalCase, which is `Com.Reactlibrary`) */</span></span><br><span class="line">  <span class="attr">githubAccount</span>: <span class="title class_">String</span>, <span class="comment">/* The github account where the library is hosted (Default: `github_account`) */</span></span><br><span class="line">  <span class="attr">authorName</span>: <span class="title class_">String</span>, <span class="comment">/* The author&#x27;s name (Default: `Your Name`) */</span></span><br><span class="line">  <span class="attr">authorEmail</span>: <span class="title class_">String</span>, <span class="comment">/* The author&#x27;s email (Default: `yourname@email.com`) */</span></span><br><span class="line">  <span class="attr">license</span>: <span class="title class_">String</span>, <span class="comment">/* The license type of this library (Default: `Apache-2.0`) */</span></span><br><span class="line">  <span class="attr">generateExample</span>: <span class="title class_">Boolean</span>, <span class="comment">/* Will generate a RN example project and link the new library to it (Default: `false`) */</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/201099a9ba42/</id>
    <link href="https://blog.zisheng.pro/posts/201099a9ba42/"/>
    <published>2023-03-08T08:53:10.000Z</published>
    <summary>react-native-create-library 使你可以使用一个命令创建 React Native 原生库的工具</summary>
    <title>react-native-create-library 中文文档</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/categories/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="React 面试题" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React-%E9%9D%A2%E8%AF%95%E9%A2%98/"/>
    <category term="React Router" scheme="https://blog.zisheng.pro/tags/React-Router/"/>
    <category term="React 面试题" scheme="https://blog.zisheng.pro/tags/React-%E9%9D%A2%E8%AF%95%E9%A2%98/"/>
    <category term="React 面试" scheme="https://blog.zisheng.pro/tags/React-%E9%9D%A2%E8%AF%95/"/>
    <category term="React 面试题与答案" scheme="https://blog.zisheng.pro/tags/React-%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%8E%E7%AD%94%E6%A1%88/"/>
    <category term="前端面试题" scheme="https://blog.zisheng.pro/tags/%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95%E9%A2%98/"/>
    <category term="React Router 面试题" scheme="https://blog.zisheng.pro/tags/React-Router-%E9%9D%A2%E8%AF%95%E9%A2%98/"/>
    <content>
      <![CDATA[<h1 id="1-React-Router-是什么？"><a href="#1-React-Router-是什么？" class="headerlink" title="1. React Router 是什么？"></a>1. React Router 是什么？</h1><p>React Router 是一个建立在 React 之上的强大的路由库，它可以帮助你快速添加新的屏幕和流程到你的应用程序，同时保持 URL 与页面上显示的内容同步。</p><h1 id="2-React-Router-与-history-库有什么不同？"><a href="#2-React-Router-与-history-库有什么不同？" class="headerlink" title="2. React Router 与 history 库有什么不同？"></a>2. React Router 与 history 库有什么不同？</h1><p>React Router 是一个围绕 <code>history</code> 库的包装器，它处理与浏览器的 <code>window.history</code> 和哈希历史记录的交互。 它还提供了内存历史，这对没有全局历史的环境很有用，如移动应用开发（React Native）和 Node 的单元测试。</p><h1 id="3-React-Router-v4-的-组件是什么？"><a href="#3-React-Router-v4-的-组件是什么？" class="headerlink" title="3. React Router v4 的 &lt;Router&gt; 组件是什么？"></a>3. React Router v4 的 <code>&lt;Router&gt;</code> 组件是什么？</h1><p>React Router v4 提供以下 3 个 <code>&lt;Router&gt;</code> 组件。</p><ol><li><code>&lt;BrowserRouter&gt;</code></li><li><code>&lt;HashRouter&gt;</code></li><li><code>&lt;MemoryRouter&gt;</code></li></ol><p>上述组件将创建<em>browser</em>、<em>hash</em>和<em>memory</em>历史实例。React Router v4 使与你的路由相关的 <code>history</code> 实例的属性和方法通过 <code>router</code> 对象中的上下文可用。</p><h1 id="4-history-的-push-和-replace-方法的用处是什么？"><a href="#4-history-的-push-和-replace-方法的用处是什么？" class="headerlink" title="4. history 的 push() 和 replace() 方法的用处是什么？"></a>4. <code>history</code> 的 <code>push()</code> 和 <code>replace()</code> 方法的用处是什么？</h1><p>一个 history 实例有两个方法用于导航。</p><ol><li><code>push()</code></li><li><code>replace()</code></li></ol><p>如果你把 history 看作是一个访问过的位置的数组， <code>push()</code> 将在数组中添加一个新的位置，<code>replace()</code> 将用新的位置替换数组中的当前位置。</p><h1 id="5-如何用-React-Router-v4-进行程序化导航？"><a href="#5-如何用-React-Router-v4-进行程序化导航？" class="headerlink" title="5. 如何用 React Router v4 进行程序化导航？"></a>5. 如何用 React Router v4 进行程序化导航？</h1><p>There are three different ways to achieve programmatic routing&#x2F;navigation within components.</p><ol><li><strong>Using the <code>withRouter()</code> higher-order function:</strong></li></ol><p>The <code>withRouter()</code> higher-order function will inject the history object as a prop of the component. This object provides <code>push()</code> and <code>replace()</code> methods to avoid the usage of context.</p><figure class="highlight jsx"><figcaption><span>| pure</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; withRouter &#125; <span class="keyword">from</span> <span class="string">&#x27;react-router-dom&#x27;</span>; <span class="comment">// this also works with &#x27;react-router-native&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Button</span> = <span class="title function_">withRouter</span>(<span class="function">(<span class="params">&#123; history &#125;</span>) =&gt;</span> (</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">button</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">type</span>=<span class="string">&quot;button&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-xml">      history.push(&#x27;/new-location&#x27;);</span></span><br><span class="line"><span class="language-xml">    &#125;&#125;</span></span><br><span class="line"><span class="language-xml">  &gt;</span></span><br><span class="line"><span class="language-xml">    &#123;&#x27;Click Me!&#x27;&#125;</span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line">));</span><br></pre></td></tr></table></figure><ol start="2"><li><strong>Using <code>&lt;Route&gt;</code> component and render props pattern:</strong></li></ol><p>The <code>&lt;Route&gt;</code> component passes the same props as <code>withRouter()</code>, so you will be able to access the history methods through the history prop.</p><figure class="highlight jsx"><figcaption><span>| pure</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Route</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;react-router-dom&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">Button</span> = (<span class="params"></span>) =&gt; (</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">Route</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">render</span>=<span class="string">&#123;(&#123;</span> <span class="attr">history</span> &#125;) =&gt;</span> (</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">type</span>=<span class="string">&quot;button&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-xml">          history.push(&#x27;/new-location&#x27;);</span></span><br><span class="line"><span class="language-xml">        &#125;&#125;</span></span><br><span class="line"><span class="language-xml">      &gt;</span></span><br><span class="line"><span class="language-xml">        &#123;&#x27;Click Me!&#x27;&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    )&#125;</span></span><br><span class="line"><span class="language-xml">  /&gt;</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><ol start="3"><li><strong>Using context:</strong></li></ol><p>This option is not recommended and treated as unstable API.</p><figure class="highlight jsx"><figcaption><span>| pure</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">Button</span> = (<span class="params">props, context</span>) =&gt; (</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">button</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">type</span>=<span class="string">&quot;button&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-xml">      context.history.push(&#x27;/new-location&#x27;);</span></span><br><span class="line"><span class="language-xml">    &#125;&#125;</span></span><br><span class="line"><span class="language-xml">  &gt;</span></span><br><span class="line"><span class="language-xml">    &#123;&#x27;Click Me!&#x27;&#125;</span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="title class_">Button</span>.<span class="property">contextTypes</span> = &#123;</span><br><span class="line">  <span class="attr">history</span>: <span class="title class_">React</span>.<span class="property">PropTypes</span>.<span class="title function_">shape</span>(&#123;</span><br><span class="line">    <span class="attr">push</span>: <span class="title class_">React</span>.<span class="property">PropTypes</span>.<span class="property">func</span>.<span class="property">isRequired</span>,</span><br><span class="line">  &#125;),</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h1 id="6-如何在-React-Router-v4-中获取查询参数？"><a href="#6-如何在-React-Router-v4-中获取查询参数？" class="headerlink" title="6. 如何在 React Router v4 中获取查询参数？"></a>6. 如何在 React Router v4 中获取查询参数？</h1><p>The ability to parse query strings was taken out of React Router v4 because there have been user requests over the years to support different implementation. So the decision has been given to users to choose the implementation they like. The recommended approach is to use query strings library.</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> queryString = <span class="built_in">require</span>(<span class="string">&#x27;query-string&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> parsed = queryString.<span class="title function_">parse</span>(props.<span class="property">location</span>.<span class="property">search</span>);</span><br></pre></td></tr></table></figure><p>You can also use <code>URLSearchParams</code> if you want something native:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> params = <span class="keyword">new</span> <span class="title class_">URLSearchParams</span>(props.<span class="property">location</span>.<span class="property">search</span>);</span><br><span class="line"><span class="keyword">const</span> foo = params.<span class="title function_">get</span>(<span class="string">&#x27;name&#x27;</span>);</span><br></pre></td></tr></table></figure><p>You should use a <em>polyfill</em> for IE11.</p><h1 id="7-为什么你得到-Router-may-have-only-one-child-element-的警告？"><a href="#7-为什么你得到-Router-may-have-only-one-child-element-的警告？" class="headerlink" title="7. 为什么你得到 Router may have only one child element 的警告？"></a>7. 为什么你得到 <code>Router may have only one child element</code> 的警告？</h1><p>You have to wrap your Route’s in a <code>&lt;Switch&gt;</code> block because <code>&lt;Switch&gt;</code> is unique in that it renders a route exclusively.</p><p>At first you need to add <code>Switch</code> to your imports:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Switch</span>, <span class="title class_">Router</span>, <span class="title class_">Route</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;react-router&#x27;</span>;</span><br></pre></td></tr></table></figure><p>Then define the routes within <code>&lt;Switch&gt;</code> block:</p><figure class="highlight jsx"><figcaption><span>| pure</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&lt;<span class="title class_">Router</span>&gt;</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">Switch</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">Route</span> &#123;/* <span class="attr">...</span> */&#125; /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">Route</span> &#123;/* <span class="attr">...</span> */&#125; /&gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/<span class="name">Switch</span>&gt;</span></span></span><br><span class="line">&lt;/<span class="title class_">Router</span>&gt;</span><br></pre></td></tr></table></figure><h1 id="8-如何在-React-Router-v4-中向-history-push-方法传递参数？"><a href="#8-如何在-React-Router-v4-中向-history-push-方法传递参数？" class="headerlink" title="8. 如何在 React Router v4 中向 history.push 方法传递参数？"></a>8. 如何在 React Router v4 中向 <code>history.push</code> 方法传递参数？</h1><p>While navigating you can pass props to the <code>history</code> object:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">this</span>.<span class="property">props</span>.<span class="property">history</span>.<span class="title function_">push</span>(&#123;</span><br><span class="line">  <span class="attr">pathname</span>: <span class="string">&#x27;/template&#x27;</span>,</span><br><span class="line">  <span class="attr">search</span>: <span class="string">&#x27;?name=sudheer&#x27;</span>,</span><br><span class="line">  <span class="attr">state</span>: &#123; <span class="attr">detail</span>: response.<span class="property">data</span> &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>The <code>search</code> property is used to pass query params in <code>push()</code> method.</p><h1 id="9-如何实现-default-或-NotFound-页面？"><a href="#9-如何实现-default-或-NotFound-页面？" class="headerlink" title="9. 如何实现 default 或 NotFound 页面？"></a>9. 如何实现 <em>default</em> 或 <em>NotFound</em> 页面？</h1><p>A <code>&lt;Switch&gt;</code> renders the first child <code>&lt;Route&gt;</code> that matches. A <code>&lt;Route&gt;</code> with no path always matches. So you just need to simply drop path attribute as below</p><figure class="highlight jsx"><figcaption><span>| pure</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&lt;<span class="title class_">Switch</span>&gt;</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">Route</span> <span class="attr">exact</span> <span class="attr">path</span>=<span class="string">&quot;/&quot;</span> <span class="attr">component</span>=<span class="string">&#123;Home&#125;</span> /&gt;</span></span></span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">&quot;/user&quot;</span> <span class="attr">component</span>=<span class="string">&#123;User&#125;</span> /&gt;</span></span></span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">Route</span> <span class="attr">component</span>=<span class="string">&#123;NotFound&#125;</span> /&gt;</span></span></span><br><span class="line">&lt;/<span class="title class_">Switch</span>&gt;</span><br></pre></td></tr></table></figure><h1 id="10-如何在-React-Router-v4-上获得历史记录？"><a href="#10-如何在-React-Router-v4-上获得历史记录？" class="headerlink" title="10. 如何在 React Router v4 上获得历史记录？"></a>10. 如何在 React Router v4 上获得历史记录？</h1><p>Below are the list of steps to get history object on React Router v4,</p><ol><li>Create a module that exports a <code>history</code> object and import this module across the project.</li></ol><p>For example, create <code>history.js</code> file:</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; createBrowserHistory &#125; <span class="keyword">from</span> <span class="string">&#x27;history&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title function_">createBrowserHistory</span>(&#123;</span><br><span class="line">  <span class="comment">/* pass a configuration object here if needed */</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><ol start="2"><li>You should use the <code>&lt;Router&gt;</code> component instead of built-in routers. Imported the above <code>history.js</code> inside <code>index.js</code> file:</li></ol><figure class="highlight jsx"><figcaption><span>| pure</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Router</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;react-router-dom&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> history <span class="keyword">from</span> <span class="string">&#x27;./history&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">App</span> <span class="keyword">from</span> <span class="string">&#x27;./App&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title class_">ReactDOM</span>.<span class="title function_">render</span>(</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">Router</span> <span class="attr">history</span>=<span class="string">&#123;history&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">App</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/<span class="name">Router</span>&gt;</span></span>,</span><br><span class="line">  holder,</span><br><span class="line">);</span><br></pre></td></tr></table></figure><ol start="3"><li>You can also use push method of <code>history</code> object similar to built-in history object:</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// some-other-file.js</span></span><br><span class="line"><span class="keyword">import</span> history <span class="keyword">from</span> <span class="string">&#x27;./history&#x27;</span>;</span><br><span class="line"></span><br><span class="line">history.<span class="title function_">push</span>(<span class="string">&#x27;/go-here&#x27;</span>);</span><br></pre></td></tr></table></figure><h1 id="11-如何在登录后执行自动重定向？"><a href="#11-如何在登录后执行自动重定向？" class="headerlink" title="11. 如何在登录后执行自动重定向？"></a>11. 如何在登录后执行自动重定向？</h1><p>The <code>react-router</code> package provides <code>&lt;Redirect&gt;</code> component in React Router. Rendering a <code>&lt;Redirect&gt;</code> will navigate to a new location. Like server-side redirects, the new location will override the current location in the history stack.</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span>, &#123; <span class="title class_">Component</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Redirect</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;react-router&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">class</span> <span class="title class_">LoginComponent</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Component</span> &#123;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">isLoggedIn</span> === <span class="literal">true</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">Redirect</span> <span class="attr">to</span>=<span class="string">&quot;/your/redirect/page&quot;</span> /&gt;</span></span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>&#123;&#x27;Login Please&#x27;&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/3f5f64fb94e2/</id>
    <link href="https://blog.zisheng.pro/posts/3f5f64fb94e2/"/>
    <published>2023-03-06T11:30:00.000Z</published>
    <summary>前 500 个 ReactJS 面试必知必会问题与答案，这是紫升诚意翻译的前端 React 面试必备系列的 React Router 面试篇。</summary>
    <title>2023 React Router 面试题与答案</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="紫升翻译计划" scheme="https://blog.zisheng.pro/categories/%E7%B4%AB%E5%8D%87%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="React 面试题" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React-%E9%9D%A2%E8%AF%95%E9%A2%98/"/>
    <category term="React 面试题" scheme="https://blog.zisheng.pro/tags/React-%E9%9D%A2%E8%AF%95%E9%A2%98/"/>
    <category term="React 面试" scheme="https://blog.zisheng.pro/tags/React-%E9%9D%A2%E8%AF%95/"/>
    <category term="React 面试题与答案" scheme="https://blog.zisheng.pro/tags/React-%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%8E%E7%AD%94%E6%A1%88/"/>
    <category term="前端面试题" scheme="https://blog.zisheng.pro/tags/%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95%E9%A2%98/"/>
    <category term="React.js 面试题" scheme="https://blog.zisheng.pro/tags/React-js-%E9%9D%A2%E8%AF%95%E9%A2%98/"/>
    <category term="前端面试" scheme="https://blog.zisheng.pro/tags/%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95/"/>
    <category term="React 教程" scheme="https://blog.zisheng.pro/tags/React-%E6%95%99%E7%A8%8B/"/>
    <category term="React.js 教程" scheme="https://blog.zisheng.pro/tags/React-js-%E6%95%99%E7%A8%8B/"/>
    <content>
      <![CDATA[<details class="folding-tag" green><summary> 1. 什么是 React？ </summary>              <div class='content'>              <p>React 是一个<strong>开源前端 JavaScript 库</strong>，用于构建用户界面，尤其是单页应用程序。它用于处理 Web 和移动应用程序的视图层。React 是由 Facebook 的软件工程师 <a href="https://github.com/jordwalke">乔丹·沃克</a> 创建的。React 于 2011 年在 Facebook 的 News Feed 上首次发布，2012 年首次在 Instagram 发布。</p>              </div>            </details><details class="folding-tag" green><summary> 2. React 的主要特性是什么？ </summary>              <div class='content'>              <p>React 的主要特性是:</p><ul><li>考虑到 DOM 操作内存开销大，React 使用<strong>虚拟 DOM（VirtualDOM）</strong> 替代了真实 DOM（RealDOM）</li><li>支持<strong>服务端渲染</strong></li><li>遵循<strong>单向</strong>数据流或数据绑定</li><li>使用<strong>可复用&#x2F;可组合</strong>的 UI 组件来进行视图开发</li></ul>              </div>            </details><details class="folding-tag" green><summary> 3. 什么是 JSX？ </summary>              <div class='content'>              <p>JSX 是 ECMAScript 的类似 XML 的语法扩展（缩写是 JavaScript XML）。实际上，它只是为 <code>React.createElement()</code> 函数提供语法糖，为我们提供了在 JavaScript 中使用类 HTML 模板语法的能力。</p><p>下面的示例中，<code>&lt;h1&gt;</code> 标签内的文本会作为 JavaScript 函数（<code>React.createElement()</code>）的返回值返回给 render 函数：</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">App</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;&#x27;Welcome to React world!&#x27;&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>              </div>            </details><details class="folding-tag" green><summary> 4. 元素（Element）和组件（Component）的区别是什么？ </summary>              <div class='content'>              <p>元素是一个普通的对象，它描述了你希望以 DOM 节点或其他组件的形式出现在屏幕上的内容。元素可以在其 props 中包含其他 元素。创建一个 React 元素开销很小。一旦创建了元素，就永远不会对其进行修改。</p><p>React Element 的对象表示如下：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> element = <span class="title class_">React</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;div&#x27;</span>, &#123; <span class="attr">id</span>: <span class="string">&#x27;login-btn&#x27;</span> &#125;, <span class="string">&#x27;Login&#x27;</span>);</span><br></pre></td></tr></table></figure><p>上面的 <code>React.createElement()</code> 函数会返回一个如下的对象：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">type</span>: <span class="string">&#x27;div&#x27;</span>,</span><br><span class="line">  <span class="attr">props</span>: &#123;</span><br><span class="line">    <span class="attr">children</span>: <span class="string">&#x27;Login&#x27;</span>,</span><br><span class="line">    <span class="attr">id</span>: <span class="string">&#x27;login-btn&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>最终，它会使用 <code>ReactDOM.render()</code> 将元素渲染到 DOM：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;login-btn&quot;</span>&gt;</span>Login<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><p>而<strong>组件</strong>可以用几种不同的方式声明。它可以是带有 <code>render()</code> 方法的类。或者，你可以简单地将其定义为一个函数。无论哪种情况，它都将 props 作为输入，并返回 JSX 树作为输出：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">Button</span> = (<span class="params">&#123; onLogin &#125;</span>) =&gt; (</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&#123;</span>&#x27;<span class="attr">login-btn</span>&#x27;&#125; <span class="attr">onClick</span>=<span class="string">&#123;onLogin&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    Login</span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>然后将 JSX 编译成 <code>React.createElement()</code> 函数：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">Button</span> = (<span class="params">&#123; onLogin &#125;</span>) =&gt;</span><br><span class="line">  <span class="title class_">React</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;div&#x27;</span>, &#123; <span class="attr">id</span>: <span class="string">&#x27;login-btn&#x27;</span>, <span class="attr">onClick</span>: onLogin &#125;, <span class="string">&#x27;Login&#x27;</span>);</span><br></pre></td></tr></table></figure>              </div>            </details><details class="folding-tag" green><summary> 5. 如何在 React 中创建组件？ </summary>              <div class='content'>              <p>这里有两种可以用方式来创建一个组件：</p><ol><li><strong>函数组件：</strong> 这是创建组件最简单的方式。它们是纯 JavaScript 函数，接受 props 对象作为第一个参数并返回 React 元素：</li></ol><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Greeting</span>(<span class="params">&#123; message &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;`Hello, $&#123;message&#125;`&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="2"><li><strong>类组件：</strong> 你也可以使用 ES6 的 class 语法来定义一个组件。上面的函数组件可以被改写为：</li></ol><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Greeting</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;`Hello, $&#123;this.props.message&#125;`&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>              </div>            </details><details class="folding-tag" green><summary> 6. 何时选择类组件或函数组件 </summary>              <div class='content'>              <p>如果一个组件需要状态或者生命周期方法，则使用类组件，否则使用函数组件。</p><p>然而，从 React 16.8 引入 Hooks 后，你可以在函数组件中使用之前只在类组件中有的特性，比如状态和生命周期方法。</p>              </div>            </details><details class="folding-tag" green><summary> 7. Pure Components 是什么？ </summary>              <div class='content'>              <p><code>React.PureComponent</code> 与 <code>React.Component</code> 几乎完全相同，不同之处在于它为你处理了 <code>shouldComponentUpdate()</code> 方法。当 props 和 state 改变时，PureComponent 将对 props 和 state 进行浅表比较。另一方面，Component 初始不会将当前 props 和 state 与 nextPorps 和 nextState 进行比较。因此，每当调用 <code>shouldComponentUpdate</code> 时，组件将默认重新渲染。</p>              </div>            </details><details class="folding-tag" green><summary> 8. React 中的 state 是什么？ </summary>              <div class='content'>              <p>组件的状态是一个对象，其中包含一些在组件的生命周期中可能会发生变化的信息。我们应该始终尝试使状态尽可能简单，并最大程度减少有状态组件的数量。</p><p>让我们来创建一个带有 message 状态的 user 组件：</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">User</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">props</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(props);</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">state</span> = &#123;</span><br><span class="line">      <span class="attr">message</span>: <span class="string">&#x27;Welcome to React world&#x27;</span>,</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;this.state.message&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8472c6438dfe48068002c5fdf1fbf098~tplv-k3u1fbpfcp-zoom-1.image" alt="state"></p><p>状态类似于 Props，但它是私有的，并由组件完全控制。也就是说除了拥有和设置它的组件之外，其他任何组件都无法访问它。</p>              </div>            </details><details class="folding-tag" green><summary> 9. React 中的 props 是什么 </summary>              <div class='content'>              <p>Props 是组件的输入。它们是单个值或包含一组值的对象，这些对象在创建时会使用类似于 HTML 标签属性的命名约定传递给组件。它们是从父组件传递到子组件的数据。</p><p>React 中 props 的主要目的是提供以下组件功能：</p><ol><li>将自定义数据传递到你的组件。</li><li>触发状态更改。</li><li>通过组件的 <code>render()</code> 方法中的 <code>this.props.reactProp</code> 使用。</li></ol><p>举个例子，让我们创建一个带有 <code>reactProp</code> 属性的元素：</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;<span class="title class_">Element</span> reactProp=&#123;<span class="string">&#x27;1&#x27;</span>&#125; /&gt;</span><br></pre></td></tr></table></figure><p>然后，这个 <code>reactProp</code>（或你想出的任何名称）成为附加到 React 的本地 props 对象的属性，该对象最初已经存在于使用 React 库创建的所有组件上。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">props.<span class="property">reactProp</span>;</span><br></pre></td></tr></table></figure>              </div>            </details><details class="folding-tag" green><summary> 10. state 和 props 的区别是什么？ </summary>              <div class='content'>              <p>props 和 state 都是普通的 JavaScript 对象。尽管它们两者都拥有影响渲染输出的信息，但它们在组件层面的功能却有所不同。将 props 传递给组件类似于传递参数给函数，而 state 则类似于函数中声明的变量一样在组件内进行管理。</p>              </div>            </details><details class="folding-tag" green><summary> 11. 为什么我们不能直接更新状态？ </summary>              <div class='content'>              <p>如果你尝试直接更新状态，React 组件并不会重新渲染。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 错误❌</span></span><br><span class="line"><span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">message</span> = <span class="string">&#x27;Hello world&#x27;</span>;</span><br></pre></td></tr></table></figure><p>正确的做法是使用 <code>setState()</code> 方法。它会计划一个对组件状态对象的更新。当状态更改时，组件通过重新渲染进行响应。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 正确✅</span></span><br><span class="line"><span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123; <span class="attr">message</span>: <span class="string">&#x27;Hello World&#x27;</span> &#125;);</span><br></pre></td></tr></table></figure><blockquote><p>**注意：**你可以使用构造函数或者最新的 javascript class 字段声明语法直接将其分配给状态对象。</p></blockquote>              </div>            </details><details class="folding-tag" yellow><summary> 12. 回调函数作为 `setState()` 的参数的目的是什么？ </summary>              <div class='content'>              <p>setState 完成并重新渲染组件后，将调用回调函数。由于 setState() 是异步的，因此回调函数可用于任何后续操作。</p><blockquote><p>**注意：**我们建议使用生命周期方法而不是这个回调函数</p></blockquote><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">setState</span>(&#123; <span class="attr">name</span>: <span class="string">&#x27;John&#x27;</span> &#125;, <span class="function">() =&gt;</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;The name has updated and component re-rendered&#x27;</span>),</span><br><span class="line">);</span><br></pre></td></tr></table></figure>              </div>            </details><details class="folding-tag" yellow><summary> 13. HTML 和 React 的事件处理有什么不同？ </summary>              <div class='content'>              <p>下面是一些 HTML 和 React 的事件处理的主要不同：</p><ol><li>在 HTML 中，事件名应该是全小写的：</li></ol><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">button</span> <span class="attr">onclick</span>=<span class="string">&quot;activateLasers()&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br></pre></td></tr></table></figure><p>然而在 React 中事件名遵循小驼峰 格式：</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;button onClick=&#123;activateLasers&#125;&gt;</span><br></pre></td></tr></table></figure><ol start="2"><li>在 HTML 中，你应该返回 <code>false</code> 来阻止默认行为：</li></ol><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;#&quot;</span> <span class="attr">onclick</span>=<span class="string">&#x27;console.log(&quot;The link was clicked.&quot;); return false;&#x27;</span> /&gt;</span></span><br></pre></td></tr></table></figure><p>然后在 React 中你必须明确地调用 <code>preventDefault()</code></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">handleClick</span>(<span class="params">event</span>) &#123;</span><br><span class="line">  event.<span class="title function_">preventDefault</span>();</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;The link was clicked.&#x27;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="3"><li>在 HTML 中，你调用函数时需要加上 <code>()</code>：</li></ol><p>然后在 React 中你不应该在函数名后带上 <code>()</code>。（比如前面示例中的 <code>activateLasers</code> 函数）</p>              </div>            </details><details class="folding-tag" yellow><summary> 14. 如何在 JSX 回调函数中绑定方法或事件处理器 </summary>              <div class='content'>              <p>这里有 3 个方法做到这一点：</p><ol><li><strong>在构造器中绑定：</strong> 在 JavaScript 类中，默认情况下不绑定方法。同样的事情也适用于定义为类方法的 React 事件处理器。通常我们将它们绑定在构造函数中。</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Component</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">props</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(props);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">handleClick</span> = <span class="variable language_">this</span>.<span class="property">handleClick</span>.<span class="title function_">bind</span>(<span class="variable language_">this</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">handleClick</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="2"><li><strong>类的公共字段语法：</strong> 如果你不喜欢使用绑定的方式，也可以使用类的公共字段语法来正确绑定回调：</li></ol><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">handleClick = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;this is:&#x27;</span>, <span class="variable language_">this</span>);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;button onClick=&#123;<span class="variable language_">this</span>.<span class="property">handleClick</span>&#125;&gt;&#123;<span class="string">&#x27;Click me&#x27;</span>&#125;&lt;/button&gt;</span><br></pre></td></tr></table></figure><ol start="3"><li><strong>箭头函数作为回调：</strong> 你可以直接在回调中使用箭头函数</li></ol><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;button onClick=&#123;<span class="function"><span class="params">event</span> =&gt;</span> <span class="variable language_">this</span>.<span class="title function_">handleClick</span>(event)&#125;&gt;&#123;<span class="string">&#x27;Click me&#x27;</span>&#125;&lt;/button&gt;</span><br></pre></td></tr></table></figure><blockquote><p><strong>注意：</strong> 如果回调作为 prop 传递给子组件，这些组件可能会触发额外的重渲染。在这些场景中，考虑到性能因素，最佳的选择是使用 <code>.bind()</code> 或类的公共字段语法。</p></blockquote>              </div>            </details><details class="folding-tag" yellow><summary> 15. 如何传递参数给事件处理器或回调？ </summary>              <div class='content'>              <p>你可以使用一个箭头函数来包裹一个事件处理器并传递参数：</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;button onClick=&#123;<span class="function">() =&gt;</span> <span class="variable language_">this</span>.<span class="title function_">handleClick</span>(id)&#125; /&gt;</span><br></pre></td></tr></table></figure><p>这等价于调用 <code>.bind</code> 函数：</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;button onClick=&#123;<span class="variable language_">this</span>.<span class="property">handleClick</span>.<span class="title function_">bind</span>(<span class="variable language_">this</span>, id)&#125; /&gt;</span><br></pre></td></tr></table></figure><p>除了这两种办法，你也可以传递参数给一个箭头函数：</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;button onClick=&#123;<span class="variable language_">this</span>.<span class="title function_">handleClick</span>(id)&#125; /&gt;;</span><br><span class="line">handleClick = <span class="function"><span class="params">id</span> =&gt;</span> <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Hello, your ticket number is&#x27;</span>, id);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>              </div>            </details><details class="folding-tag" yellow><summary> 16. React 中的合成事件是什么？ </summary>              <div class='content'>              <p><code>SyntheticEvent</code> 是基于浏览器本地事件的跨浏览器包装。它的 API 与浏览器的本地事件相同，包括 <code>stopPropagation()</code> 和 <code>preventDefault()</code>，但事件在所有浏览器中的表现均一致。</p>              </div>            </details><details class="folding-tag" yellow><summary> 17. 什么是内联条件表达式？ </summary>              <div class='content'>              <p>你可以使用 JS 可用的 <code>if</code> 语句或三元表达式来有条件地渲染表达式。 除了这些方法之外，还可以通过将所有表达式括在花括号中然后在其后跟 JS 逻辑运算符 <code>&amp;&amp;</code> 来将任何表达式嵌入 JSX。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&lt;h1&gt;<span class="title class_">Hello</span>!&lt;/h1&gt;;</span><br><span class="line">&#123;</span><br><span class="line">  messages.<span class="property">length</span> &gt; <span class="number">0</span> &amp;&amp; !isLogin ? (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">h2</span>&gt;</span>You have &#123;messages.length&#125; unread messages.<span class="tag">&lt;/<span class="name">h2</span>&gt;</span></span></span><br><span class="line">  ) : (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">h2</span>&gt;</span>You don&#x27;t have unread messages.<span class="tag">&lt;/<span class="name">h2</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>              </div>            </details><details class="folding-tag" yellow><summary> 18. 什么是 `key` prop？在元素数组中使用它的好处是什么？ </summary>              <div class='content'>              <p><code>key</code> 是当你创建一个元素数组时应该包含的一个特殊的字符串属性。<code>key</code> prop 会帮助 React 识别具体哪一项被修改、添加或被移除。</p><p>通常，我们将数据中的 ID 用作 <code>key</code>：</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> todoItems = todos.<span class="title function_">map</span>(<span class="function"><span class="params">todo</span> =&gt;</span> <span class="language-xml"><span class="tag">&lt;<span class="name">li</span> <span class="attr">key</span>=<span class="string">&#123;todo.id&#125;</span>&gt;</span>&#123;todo.text&#125;<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span>);</span><br></pre></td></tr></table></figure><p>如果呈现的项目没有稳定的 ID，退而求其次，我们可以将 <code>index</code> 作为 <code>key</code>：</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> todoItems = todos.<span class="title function_">map</span>(<span class="function">(<span class="params">todo, index</span>) =&gt;</span> (</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">li</span> <span class="attr">key</span>=<span class="string">&#123;index.toString()&#125;</span>&gt;</span>&#123;todo.text&#125;<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line">));</span><br></pre></td></tr></table></figure><p><strong>注意：</strong></p><ol><li>如果列表项可能改变，不建议使用 <code>indexes</code> 作为 <code>keys</code>。这可能会对性能产生负面影响，并可能导致组件状态出现问题。</li><li>如果你将列表项提取为单独的组件，则在列表组件上应用 <code>keys</code> 而不是 <code>li</code> 标签。</li><li>如果列表项中不存在 <code>key</code> prop，则控制台中将出现警告消息。</li></ol>              </div>            </details><details class="folding-tag" yellow><summary> 19. refs 有什么用？ </summary>              <div class='content'>              <p>refs 用于返回对该元素的引用。在大多数情况下，应避免使用它们，但是，当你需要直接访问 DOM 元素或组件的实例时，它们会很有用。</p>              </div>            </details><h2 id="20-如何创建-refs？"><a href="#20-如何创建-refs？" class="headerlink" title="20. 如何创建 refs？"></a>20. 如何创建 refs？</h2><p>这里有两种方式</p><p>1.这是最近添加的方法。使用 <code>React.createRef()</code> 方法创建 refs，并通过 ref 属性附加到 React 元素。为了在整个组件中使用 refs，只需将 ref 分配给构造函数中的 instance 属性。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyComponent</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">props</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(props);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">myRef</span> = <span class="title class_">React</span>.<span class="title function_">createRef</span>();</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">ref</span>=<span class="string">&#123;this.myRef&#125;</span> /&gt;</span></span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="2"><li>无论 React 版本如何，你都可以使用 ref 回调方法。例如，搜索栏组件的输入元素的访问方式如下。</li></ol><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">SearchBar</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Component</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">props</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(props);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">txtSearch</span> = <span class="literal">null</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">state</span> = &#123; <span class="attr">term</span>: <span class="string">&#x27;&#x27;</span> &#125;;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">setInputSearchRef</span> = <span class="function"><span class="params">e</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">txtSearch</span> = e;</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">onInputChange</span>(<span class="params">event</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123; <span class="attr">term</span>: <span class="variable language_">this</span>.<span class="property">txtSearch</span>.<span class="property">value</span> &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">input</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">value</span>=<span class="string">&#123;this.state.term&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">onChange</span>=<span class="string">&#123;this.onInputChange.bind(this)&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">ref</span>=<span class="string">&#123;this.setInputSearchRef&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      /&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>你也可以使用闭包在函数组件中使用 refs。</p><blockquote><p><strong>注意：</strong> 你也可以使用内联 ref 回调，即使这不是推荐的方法</p></blockquote><h2 id="21-refs-转发是什么？"><a href="#21-refs-转发是什么？" class="headerlink" title="21. refs 转发是什么？"></a>21. refs 转发是什么？</h2><p><em>Ref 转发</em> 是让某些组件可以使用它们接收的 <code>ref</code> 的特性，这些组件还可以进一步将其传递给子组件。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">ButtonElement</span> = <span class="title class_">React</span>.<span class="title function_">forwardRef</span>(<span class="function">(<span class="params">props, ref</span>) =&gt;</span> (</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">button</span> <span class="attr">ref</span>=<span class="string">&#123;ref&#125;</span> <span class="attr">className</span>=<span class="string">&quot;CustomButton&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    &#123;props.children&#125;</span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line">));</span><br><span class="line"></span><br><span class="line"><span class="comment">// Create ref to the DOM button:</span></span><br><span class="line"><span class="keyword">const</span> ref = <span class="title class_">React</span>.<span class="title function_">createRef</span>();</span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">ButtonElement</span> <span class="attr">ref</span>=<span class="string">&#123;ref&#125;</span>&gt;</span>&#123;&#x27;Forward Ref&#x27;&#125;<span class="tag">&lt;/<span class="name">ButtonElement</span>&gt;</span></span>;</span><br></pre></td></tr></table></figure><h2 id="22-refs-回调和-findDOMNode-哪个是首选项？"><a href="#22-refs-回调和-findDOMNode-哪个是首选项？" class="headerlink" title="22. refs 回调和 findDOMNode() 哪个是首选项？"></a>22. refs 回调和 <code>findDOMNode()</code> 哪个是首选项？</h2><p>最好使用 refs 回调 而不是 <code>findDOMNode()</code> API。因为 <code>findDOMNode()</code> 将来会阻止对 React 的某些改进。</p><p>使用 <code>findDOMNode</code> 的“传统”方法：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyComponent</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Component</span> &#123;</span><br><span class="line">  <span class="title function_">componentDidMount</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="title function_">findDOMNode</span>(<span class="variable language_">this</span>).<span class="title function_">scrollIntoView</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> /&gt;</span></span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>推荐的方式是：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyComponent</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Component</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">props</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(props);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">node</span> = <span class="title function_">createRef</span>();</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">componentDidMount</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">node</span>.<span class="property">current</span>.<span class="title function_">scrollIntoView</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">ref</span>=<span class="string">&#123;this.node&#125;</span> /&gt;</span></span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="23-为什么-Strings-Refs-被遗弃了？"><a href="#23-为什么-Strings-Refs-被遗弃了？" class="headerlink" title="23. 为什么 Strings Refs 被遗弃了？"></a>23. 为什么 Strings Refs 被遗弃了？</h2><p>如果你以前使用过 React，那么你可能会熟悉一个较旧的 API，其中的<code>ref</code> 属性是一个字符串，例如 <code>ref = &#123;textInput&#39;&#125;</code>，并且 DOM 节点作为<code>this.refs.textInput</code> 访问。我们建议你不要这样做，因为 String 引用有以下问题，并且被认为是旧版的。字符串引用已经在 <strong>React v16 中被删除</strong>。</p><ol><li><p>他们迫使 React 跟踪当前正在执行的组件。这是有问题的，因为它使 React 模块成为有状态的，并因此在打包 React 模块时冲突而引起奇怪的错误。</p></li><li><p>它们是“不可组合的” — 如果库在传递的子项上放置了引用，则用户不能在其上放置其他引用。回调引用完全可以组合。</p></li><li><p>他们不能和静态分析工具配合（比如 Flow）。Flow 无法猜测出框架 <code>this.refs</code> 上出现的字符串引用及其类型（可能不同）。 回调引用对静态分析更友好。</p></li><li><p>它无法像大多数人期望的那样使用“渲染回调”模式（例如）</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyComponent</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Component</span> &#123;</span><br><span class="line">  renderRow = <span class="function"><span class="params">index</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// This won&#x27;t work. Ref will get attached to DataTable rather than MyComponent:</span></span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">input</span> <span class="attr">ref</span>=<span class="string">&#123;</span>&#x27;<span class="attr">input-</span>&#x27; + <span class="attr">index</span>&#125; /&gt;</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// This would work though! Callback refs are awesome.</span></span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">input</span> <span class="attr">ref</span>=<span class="string">&#123;input</span> =&gt;</span> (this[&#x27;input-&#x27; + index] = input)&#125; /&gt;</span>;</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">DataTable</span> <span class="attr">data</span>=<span class="string">&#123;this.props.data&#125;</span> <span class="attr">renderRow</span>=<span class="string">&#123;this.renderRow&#125;</span> /&gt;</span></span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><h2 id="24-虚拟-DOM-是什么？"><a href="#24-虚拟-DOM-是什么？" class="headerlink" title="24. 虚拟 DOM 是什么？"></a>24. 虚拟 DOM 是什么？</h2><p><em>Virtual DOM</em>（VDOM）是<em>Real DOM</em>的内存表示形式。 UI 的表示形式保留在内存中，并与“真实” DOM 同步。 这是在调用渲染函数和在屏幕上显示元素之间发生的一步。 这整个过程称为 <a href="https://zh-hans.reactjs.org/docs/reconciliation.html">协调</a>。</p><h2 id="25-虚拟-DOM-原理"><a href="#25-虚拟-DOM-原理" class="headerlink" title="25. 虚拟 DOM 原理"></a>25. 虚拟 DOM 原理</h2><p>虚拟 DOM 工作原理只有三个简单的步骤。</p><ol><li>无论何时任何基础数据发生更改，整个 UI 都将以虚拟 DOM 表现形式重新呈现。</li></ol><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9f8a99a43f7049feb7806a1bd54e5929~tplv-k3u1fbpfcp-zoom-1.image" alt="vdom"></p><ol start="2"><li>然后，计算先前的 DOM 表现形式与新的 DOM 表现形式之间的差异。</li></ol><p><img src="https://github.com/sudheerj/reactjs-interview-questions/raw/master/images/vdom2.png" alt="vdom2"></p><ol start="3"><li>一旦完成计算，将只会更新内容真正改变的那部分真是 DOM。</li></ol><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/461ba77627524f96b2bf270c1935b969~tplv-k3u1fbpfcp-zoom-1.image" alt="vdom3"></p><h2 id="26-Shadow-DOM-和-Virtual-DOM-有什么区别？"><a href="#26-Shadow-DOM-和-Virtual-DOM-有什么区别？" class="headerlink" title="26. Shadow DOM 和 Virtual DOM 有什么区别？"></a>26. Shadow DOM 和 Virtual DOM 有什么区别？</h2><p>Shadow DOM 是一种浏览器技术，主要用于确定 web components 中的变量和 CSS。Virtual DOM 是由浏览器 API 之上的 JavaScript 库实现的概念。</p><h2 id="27-React-Fiber-是什么"><a href="#27-React-Fiber-是什么" class="headerlink" title="27. React Fiber 是什么?"></a>27. React Fiber 是什么?</h2><p>Fiber 是 React v16 中新的 <a href="https://zh-hans.reactjs.org/docs/reconciliation.html">协调</a> 引擎或核心算法的重新实现。React Fiber 的目标是提高其在动画、布局、手势、暂停、中止或重用工作的能力，以及为不同类型的更新分配优先级等方面的适用性和新的并发原语。</p><h2 id="28-React-Fiber-的主要设计目的是什么？"><a href="#28-React-Fiber-的主要设计目的是什么？" class="headerlink" title="28. React Fiber 的主要设计目的是什么？"></a>28. React Fiber 的主要设计目的是什么？</h2><p>React Fiber 的目标是提高其对动画、布局和手势等领域的适用性。它的 headline 功能是<strong>增量渲染</strong>：能够将渲染工作拆分为多个块并将其分布到多个帧中。</p><h2 id="29-受控组件是什么？"><a href="#29-受控组件是什么？" class="headerlink" title="29. 受控组件是什么？"></a>29. 受控组件是什么？</h2><p>在用户输入后能够控制表单中输入元素的组件被称为“受控组件”，比如每一个状态概念都将有一个相关的处理函数</p><p>例如下面的例子中，为了将名字转换为全大写，我们使用 <code>handleChange</code>：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">handleChange</span>(<span class="params">event</span>) &#123;</span><br><span class="line">   <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;<span class="attr">value</span>: event.<span class="property">target</span>.<span class="property">value</span>.<span class="title function_">toUpperCase</span>()&#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="30-非受控组件是什么？"><a href="#30-非受控组件是什么？" class="headerlink" title="30. 非受控组件是什么？"></a>30. 非受控组件是什么？</h2><p>非受控组件是那些把状态维护在其内部的组件，当你想要获得当前值时需要使用 ref 查询 DOM。这有一点像传统的 HTML。</p><p>在下面的 <code>UserProfile</code> 组件中，<code>name</code> 输入被使用 <code>ref</code> 获取：</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">UserProfile</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">props</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(props);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">handleSubmit</span> = <span class="variable language_">this</span>.<span class="property">handleSubmit</span>.<span class="title function_">bind</span>(<span class="variable language_">this</span>);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">input</span> = <span class="title class_">React</span>.<span class="title function_">createRef</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">handleSubmit</span>(<span class="params">event</span>) &#123;</span><br><span class="line">    <span class="title function_">alert</span>(<span class="string">&#x27;A name was submitted: &#x27;</span> + <span class="variable language_">this</span>.<span class="property">input</span>.<span class="property">current</span>.<span class="property">value</span>);</span><br><span class="line">    event.<span class="title function_">preventDefault</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">form</span> <span class="attr">onSubmit</span>=<span class="string">&#123;this.handleSubmit&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">label</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          &#123;&#x27;Name:&#x27;&#125;</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;text&quot;</span> <span class="attr">ref</span>=<span class="string">&#123;this.input&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">label</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;submit&quot;</span> <span class="attr">value</span>=<span class="string">&quot;Submit&quot;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">form</span>&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="31-createElement-和-cloneElement-的区别是什么？"><a href="#31-createElement-和-cloneElement-的区别是什么？" class="headerlink" title="31. createElement 和 cloneElement 的区别是什么？"></a>31. createElement 和 cloneElement 的区别是什么？</h2><p>JSX 元素将被转换为 <code>React.createElement()</code> 函数以创建 React 元素，这些元素将用于 UI 的对象表示。而 <code>cloneElement</code> 用于克隆元素并将新的 <code>props</code> 传递给它。</p><p><strong>课后扩展：</strong></p><ul><li><a href="https://zh-hans.reactjs.org/docs/react-api.html">React 顶层 API</a></li></ul><h2 id="32-React-中的状态提升是什么？"><a href="#32-React-中的状态提升是什么？" class="headerlink" title="32. React 中的状态提升是什么？"></a>32. React 中的状态提升是什么？</h2><p>当多个组件需要共享相同的变化数据时，建议将共享状态提升到它们最接近的共同祖先。这意味着，如果两个子组件共享来自其父组件的相同数据，则将状态移到父组件，而不是在两个子组件中都保持内部状态。</p><h2 id="33-组件生命周期有哪些不同阶段？"><a href="#33-组件生命周期有哪些不同阶段？" class="headerlink" title="33. 组件生命周期有哪些不同阶段？"></a>33. 组件生命周期有哪些不同阶段？</h2><p>组件生命周期具有三个不同的生命周期阶段。</p><ol><li><strong>Mounting：</strong> 组件已准备好安装在浏览器 DOM 中。这个阶段涵盖了生命周期方法 <code>constructor()</code>、<code>getDerivedStateFromProps()</code>、 <code>render()</code> 和 <code>componentDidMount()</code> 的初始化。</li><li><strong>Updating：</strong> 在此阶段，组件以两种方式进行更新，即发送新 <code>props</code> 和从 <code>setState()</code> 或 <code>forceUpdate()</code> 更新状态。此阶段涵盖了<code>getDerivedStateFromProps()</code>，<code>shouldComponentUpdate()</code>，<code>render()</code> 、<code>getSnapshotBeforeUpdate()</code> 和 <code>componentDidUpdate()</code> 生命周期方法。</li><li><strong>Unmounting：</strong> 在最后一个阶段，不再需要该组件并从浏览器 DOM 上卸载该组件。 这个阶段包括 <code>componentWillUnmount()</code> 生命周期方法。</li></ol><p>值得一提的是，在将更改应用于 DOM 时，React 内部具有阶段性概念。 它们分开如下</p><ol><li><strong>Render：</strong> 该组件将渲染而没有任何副作用。这适用于 Pure 组件，在此阶段，React 可以暂停、中止或重新启动渲染。</li><li><strong>Pre-commit：</strong> 在组件将更改实际应用于 DOM 之前，有一段时间可以让 React 通过 <code>getSnapshotBeforeUpdate()</code> 从 DOM 中读取内容。</li><li><strong>Commit：</strong> React 与 DOM 一起工作并分别执行最终的生命周期：<code>componentDidMount()</code> 用于安装，<code>componentDidUpdate()</code> 用于更新，以及 <code>componentWillUnmount()</code> 用于卸载。</li></ol><p>React 16.3+ (或者 <a href="http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/">在线交互版本</a>)</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fac807b8490c4c11a9630a9f29e467d3~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>React 16.3 之前的版本：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/33a918a89bdd49c08e80732f5f6af367~tplv-k3u1fbpfcp-zoom-1.image" alt="phases 16.2"></p><h2 id="34-React-生命周期有哪些？"><a href="#34-React-生命周期有哪些？" class="headerlink" title="34. React 生命周期有哪些？"></a>34. React 生命周期有哪些？</h2><p>React 16.3 以前的版本：</p><ul><li><strong>componentWillMount：</strong> 在渲染之前执行，用于根组件中的应用程序级别配置。</li><li><strong>componentDidMount：</strong> 在首次渲染之后执行，所有 AJAX 请求，DOM 或状态更新以及设置事件侦听器都应在此执行。</li><li><strong>componentWillReceiveProps：</strong> 在特定属性更新以触发状态转换时执行。</li><li><strong>shouldComponentUpdate：</strong> 确定是否要更新组件。默认情况下，它返回 <code>true</code>。如果你确定在状态或属性更新后不需要渲染组件，则可以返回 <code>false</code> 值。这是提高性能的好地方，因为如果组件收到新的 <code>props</code>，它可以防止重新渲染。</li><li><strong>componentWillUpdate：</strong> 当有属性或状态改变被<code>shouldComponentUpdate()</code> 确认并返回 <code>true</code> 时，在重新渲染组件之前执行。</li><li><strong>componentDidUpdate：</strong> 通常，它用于响应属性或状态更改来更新 DOM。</li><li><strong>componentWillUnmount：</strong> 它将用于取消任何传出的网络请求，或删除与该组件关联的所有事件侦听器。</li></ul><p>React 16.3+ 版本</p><ul><li><strong>getDerivedStateFromProps：</strong> 在调用 <code>render()</code> 之前被调用，并且在每次渲染中都会被调用。对于需要派生状态的罕见用例，这是存在的。<a href="https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html">如果您需要派生状态</a> 值得一读。</li><li><strong>componentDidMount：</strong> 在首次渲染之后执行，并且所有 AJAX 请求、DOM 或状态更新以及设置事件侦听器都应在此发生。</li><li><strong>shouldComponentUpdate：</strong> 确定是否将更新组件。默认情况下，它返回 <code>true</code>。如果你确定在状态或属性更新后不需要渲染组件，则可以返回 <code>false</code>值。这是提高性能的好地方，因为如果组件接收到新的属性，它可以防止重新渲染。</li><li><strong>getSnapshotBeforeUpdate：</strong> 在将呈现的输出提交给 DOM 之前立即执行。此方法返回的任何值都将传递到 <code>componentDidUpdate()</code> 中。 这对于从 DOM（即滚动位置）捕获信息很有用。</li><li><strong>componentDidUpdate：</strong> 通常，它用于响应属性或状态更改来更新 DOM。如果 <code>shouldComponentUpdate()</code> 返回 <code>false</code>，则不会触发。</li><li><strong>componentWillUnmount：</strong> 它将用于取消任何传出的网络请求，或删除与该组件关联的所有事件侦听器。</li></ul><h2 id="35-高阶组件是什么"><a href="#35-高阶组件是什么" class="headerlink" title="35. 高阶组件是什么"></a>35. 高阶组件是什么</h2><p>高阶组件（HOC）是接收组件并返回新组件的函数。基本上，这是从 React 的组成性质衍生出来的一种模式。</p><p>我们称它们为纯组件，因为它们可以接受任何动态提供的子组件，但是它们不会修改或复制其输入组件中的任何行为。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">EnhancedComponent</span> = <span class="title function_">higherOrderComponent</span>(<span class="title class_">WrappedComponent</span>);</span><br></pre></td></tr></table></figure><p>HOC 可以用到很多场景中：</p><ol><li>代码重用，逻辑和引导程序抽象。</li><li>渲染劫持。</li><li>状态抽象和操纵。</li><li>props 操作。</li></ol><h2 id="36-如何为-HOC-组件-创建-props-代理？"><a href="#36-如何为-HOC-组件-创建-props-代理？" class="headerlink" title="36. 如何为 HOC 组件 创建 props 代理？"></a>36. 如何为 HOC 组件 创建 props 代理？</h2><p>您可以使用属性代理模式添加或编辑传递给组件的属性，如下所示：</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">HOC</span>(<span class="params">WrappedComponent</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">class</span> <span class="title class_">Test</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Component</span> &#123;</span><br><span class="line">    <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> newProps = &#123;</span><br><span class="line">        <span class="attr">title</span>: <span class="string">&#x27;New Header&#x27;</span>,</span><br><span class="line">        <span class="attr">footer</span>: <span class="literal">false</span>,</span><br><span class="line">        <span class="attr">showFeatureX</span>: <span class="literal">false</span>,</span><br><span class="line">        <span class="attr">showFeatureY</span>: <span class="literal">true</span>,</span><br><span class="line">      &#125;;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">WrappedComponent</span> &#123;<span class="attr">...this.props</span>&#125; &#123;<span class="attr">...newProps</span>&#125; /&gt;</span></span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>课后扩展：</strong></p><ul><li><a href="https://juejin.cn/post/6844903641074106381">react 高阶组件的代理模式</a></li></ul><h2 id="37-context-是什么？"><a href="#37-context-是什么？" class="headerlink" title="37. context 是什么？"></a>37. context 是什么？</h2><p><code>Context</code> 提供了一种通过组件树传递数据的方法，而不需要一层一层手动传递属性。</p><p>例如，需要由许多组件在应用程序中访问经过身份验证的用户，本地设置首选项，UI 主题等。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; <span class="title class_">Provider</span>, <span class="title class_">Consumer</span> &#125; = <span class="title class_">React</span>.<span class="title function_">createContext</span>(defaultValue);</span><br></pre></td></tr></table></figure><h2 id="38-什么是-children-属性？"><a href="#38-什么是-children-属性？" class="headerlink" title="38. 什么是 children 属性？"></a>38. 什么是 children 属性？</h2><p><code>Children</code> 是一个 prop（<code>this.props.children</code>），允许你将组件作为数据传递给其他组件，就像你使用的任何其他 prop 一样。放置在组件的开始标记和结束标记之间的组件树将作为 <code>children</code> 道具传递给该组件。</p><p>React API 中有许多方法可作为该属性。其中包括 <code>React.Children.map</code>、<code>React.Children.forEach</code>，<code>React.Children.count</code>、<code>React.Children.only</code> 和 <code>React.Children.toArray</code>。</p><p>children 的简单用法如下所示：</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">MyDiv</span> = <span class="title class_">React</span>.<span class="title function_">createClass</span>(&#123;</span><br><span class="line">  <span class="attr">render</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>&#123;this.props.children&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="title class_">ReactDOM</span>.<span class="title function_">render</span>(</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">MyDiv</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">span</span>&gt;</span>&#123;&#x27;Hello&#x27;&#125;<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">span</span>&gt;</span>&#123;&#x27;World&#x27;&#125;<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/<span class="name">MyDiv</span>&gt;</span></span>,</span><br><span class="line">  node,</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h2 id="39-React-中如何写注释？"><a href="#39-React-中如何写注释？" class="headerlink" title="39. React 中如何写注释？"></a>39. React 中如何写注释？</h2><p>React JSX 中的注释和 JavaScript 的多行注释很像，但是用大括号括起来。</p><p><strong>单行注释：</strong></p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;div&gt;</span><br><span class="line">  &#123;<span class="comment">/* 这里是单行注释 */</span>&#125;</span><br><span class="line">  &#123;<span class="string">`Welcome <span class="subst">$&#123;user&#125;</span>, let&#x27;s play React`</span>&#125;</span><br><span class="line">&lt;/div&gt;</span><br></pre></td></tr></table></figure><p><strong>多行注释：</strong></p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&lt;div&gt;</span><br><span class="line">  &#123;<span class="comment">/* Multi-line comments for more than</span></span><br><span class="line"><span class="comment">   one line */</span>&#125;</span><br><span class="line">  &#123;<span class="string">`Welcome <span class="subst">$&#123;user&#125;</span>, let&#x27;s play React`</span>&#125;</span><br><span class="line">&lt;/div&gt;</span><br></pre></td></tr></table></figure><h2 id="40-在-constructor-中给-super-函数传递-props-的目的是什么？"><a href="#40-在-constructor-中给-super-函数传递-props-的目的是什么？" class="headerlink" title="40. 在 constructor 中给 super 函数传递 props 的目的是什么？"></a>40. 在 constructor 中给 <code>super</code> 函数传递 props 的目的是什么？</h2><p>一个子类构造器在 <code>super()</code> 方法调用之前是无法拿到 <code>this</code> 引用的。这同样也适用于 ES6 中的 sub-classes。调用 <code>super()</code> 时传递 props 的主要是为了在子类的构造器中访问 <code>this.props</code>。</p><p><strong>传递 props：</strong></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyComponent</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">props</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(props);</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">props</span>); <span class="comment">// 打印 &#123; name: &#x27;John&#x27;, age: 42 &#125;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>不传递 props：</strong></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyComponent</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">props</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>();</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">props</span>); <span class="comment">// 打印 undefined</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 但是 props 参数依然可以访问</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(props); <span class="comment">// 打印 &#123; name: &#x27;John&#x27;, age: 42 &#125;</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">// 在 constructor 之外没有影响</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">props</span>); <span class="comment">// 打印 &#123; name: &#x27;John&#x27;, age: 42 &#125;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面的代码片段揭示了 <code>this.props</code> 仅在构造函数中有所不同。在构造函数外部表现将是相同的。</p><p>更多信息可以参考 <a href="https://overreacted.io/zh-hans/why-do-we-write-super-props/">为什么我们要写 super(props) ？</a></p><p>大多数场景中，我们建议使用受控组件来代替表单组件。</p><h2 id="41-协调（reconciliation）是什么？"><a href="#41-协调（reconciliation）是什么？" class="headerlink" title="41. 协调（reconciliation）是什么？"></a>41. 协调（reconciliation）是什么？</h2><p>当一个组件的 props 或 state 发生变化时，React 通过比较新返回的元素和之前渲染的元素来决定是否有必要进行实际的 DOM 更新。当它们不相等时，React 将更新 DOM。这个过程被称为 <em>协调（reconciliation）</em>。</p><h2 id="42-如何用一个动态键名来设置状态？"><a href="#42-如何用一个动态键名来设置状态？" class="headerlink" title="42. 如何用一个动态键名来设置状态？"></a>42. 如何用一个动态键名来设置状态？</h2><p>如果你使用 ES6 或 Babel 转码器来转换你的 JSX 代码，那么你可以用计算属性命名完成。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">handleInputChange</span>(<span class="params">event</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123; [event.<span class="property">target</span>.<span class="property">id</span>]: event.<span class="property">target</span>.<span class="property">value</span> &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="43-每次组件渲染时，函数被调用的常见错误是什么？"><a href="#43-每次组件渲染时，函数被调用的常见错误是什么？" class="headerlink" title="43. 每次组件渲染时，函数被调用的常见错误是什么？"></a>43. 每次组件渲染时，函数被调用的常见错误是什么？</h2><p>你需要确保在传递函数作为参数时，没有调用该函数。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">// 错误❌： handleClick 被调用而不是作为引用被传入</span></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.handleClick()&#125;</span>&gt;</span>&#123;&#x27;Click Me&#x27;&#125;<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>取而代之的是传递函数本身，不加圆括号。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">// 正确：handleClick 是作为一个引用传递的!</span></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.handleClick&#125;</span>&gt;</span>&#123;&#x27;Click Me&#x27;&#125;<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="44-lazy-函数是否支持命名导出？"><a href="#44-lazy-函数是否支持命名导出？" class="headerlink" title="44. lazy 函数是否支持命名导出？"></a>44. lazy 函数是否支持命名导出？</h2><p>不，目前 <code>React.lazy</code> 函数只支持默认出口。如果你想导入被命名导出的模块，你可以创建一个中间模块，将其作为默认出口。这也保证了摇树的工作，不会拉取未使用的组件。</p><p>让我们来看看一个导出多个命名组件的组件文件。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// MoreComponents.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title class_">SomeComponent</span> = <span class="comment">/* ... */</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title class_">UnusedComponent</span> = <span class="comment">/* ... */</span>;</span><br></pre></td></tr></table></figure><p>并在一个中间文件 <code>IntermediateComponent.js</code> 中重新导出 <code>MoreComponents.js</code> 组件</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// IntermediateComponent.js</span></span><br><span class="line"><span class="keyword">export</span> &#123; <span class="title class_">SomeComponent</span> <span class="keyword">as</span> <span class="keyword">default</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;./MoreComponents.js&#x27;</span>;</span><br></pre></td></tr></table></figure><p>现在你可以使用下面的 lazy 函数导入该模块。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span>, &#123; lazy &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">SomeComponent</span> = <span class="title function_">lazy</span>(<span class="function">() =&gt;</span> <span class="keyword">import</span>(<span class="string">&#x27;./IntermediateComponent.js&#x27;</span>));</span><br></pre></td></tr></table></figure><h2 id="45-为什么-React-使用-className-而不是-class-属性？"><a href="#45-为什么-React-使用-className-而不是-class-属性？" class="headerlink" title="45. 为什么 React 使用 className 而不是 class 属性？"></a>45. 为什么 React 使用 <code>className</code> 而不是 <code>class</code> 属性？</h2><p><code>class</code> 是 JavaScript 的一个关键字，而 JSX 是 JavaScript 的一个扩展。这就是为什么 React 使用 <code>className</code> 而不是 <code>class</code> 的主要原因。传递一个字符串作为 <code>className</code> prop。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">span</span> <span class="attr">className</span>=<span class="string">&#123;</span>&#x27;<span class="attr">menu</span> <span class="attr">navigation-menu</span>&#x27;&#125;&gt;</span>&#123;&#x27;Menu&#x27;&#125;<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="46-片段（fragments）是什么？"><a href="#46-片段（fragments）是什么？" class="headerlink" title="46. 片段（fragments）是什么？"></a>46. 片段（fragments）是什么？</h2><p>这是 React 中常见的模式，用于一个组件返回多个元素。片段让你可以对一个 children 的列表进行分组，而无需在 DOM 中添加额外的节点。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">React.Fragment</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">ChildA</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">ChildB</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">ChildC</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">React.Fragment</span>&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里还有一个短语法可以用，但是很多工具不支持：</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">ChildA</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">ChildB</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">ChildC</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="47-为什么片段（fragments）比-div-容器要好？"><a href="#47-为什么片段（fragments）比-div-容器要好？" class="headerlink" title="47. 为什么片段（fragments）比 div 容器要好？"></a>47. 为什么片段（fragments）比 div 容器要好？</h2><ol><li>片段的速度更快一些，并且由于没有创建额外的 DOM 节点而使用更少的内存。这只有在非常大和深的树上才会体现出真正的好处。</li><li>一些 CSS 机制，如 Flexbox 和 CSS Grid 有一个特殊的父子关系，在中间添加 div 会使其难以保持所需的布局。</li><li>DOM 检查器不那么杂乱。</li></ol><h2 id="48-什么是-React-中的传递门（Portal）？"><a href="#48-什么是-React-中的传递门（Portal）？" class="headerlink" title="48. 什么是 React 中的传递门（Portal）？"></a>48. 什么是 React 中的传递门（Portal）？</h2><p>传递门是一种推荐的方式，可以将子节点渲染到父组件的 DOM 层次结构之外的 DOM 节点中。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">ReactDOM</span>.<span class="title function_">createPortal</span>(child, container);</span><br></pre></td></tr></table></figure><p>第一个参数是任何可渲染的 React children，比如一个元素、字符串或片段。第二个参数是一个 DOM 元素。</p><h2 id="49-什么是无状态组件"><a href="#49-什么是无状态组件" class="headerlink" title="49. 什么是无状态组件?"></a>49. 什么是无状态组件?</h2><p>如果行为是独立于其状态的，那么它可以是一个无状态组件。你可以使用函数或类来创建无状态组件。但除非你需要在你的组件中使用生命周期钩子，否则你应该选择函数组件。如果你决定在这里使用函数组件，会有很多好处；它们易于编写、理解和测试，速度稍快，而且你可以完全避免使用 <code>this</code> 关键字。</p><h2 id="50-什么是状态组件"><a href="#50-什么是状态组件" class="headerlink" title="50. 什么是状态组件?"></a>50. 什么是状态组件?</h2><p>如果一个组件的行为依赖于该组件的状态（state），那么它可以被称为有状态的组件。这些有状态的组件总是类组件，并且有一个在构造器（<code>constructor</code>）中被初始化的状态。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">App</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Component</span> &#123;</span><br><span class="line">  <span class="comment">// 也可以使用类字段语法</span></span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">props</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(props);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">state</span> = &#123; <span class="attr">count</span>: <span class="number">0</span> &#125;;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>React 16.8 更新：</strong></p><p>Hooks 让你在不写类的情况下使用状态和其他 React 功能。</p><p>等效的函数组件</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span>, &#123;useState&#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">App</span> = (<span class="params">props</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> [count, setCount] = <span class="title function_">useState</span>(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="comment">// JSX</span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="51-如何在-React-中对-props-进行验证？"><a href="#51-如何在-React-中对-props-进行验证？" class="headerlink" title="51. 如何在 React 中对 props 进行验证？"></a>51. 如何在 React 中对 props 进行验证？</h2><p>当应用程序运行在开发模式时，React 会自动检查我们在组件上设置的所有 props，以确保它们具有正确的类型。如果类型不正确，React 会在控制台生成警告信息。由于对性能的影响，它在生产模式中被禁用。必需 props 是用 <code>isRequired</code> 定义的。</p><p>预定义的 props 类型集合。</p><ol><li><code>PropTypes.number</code></li><li><code>PropTypes.string</code></li><li><code>PropTypes.array</code></li><li><code>PropTypes.object</code></li><li><code>PropTypes.func</code></li><li><code>PropTypes.node</code></li><li><code>PropTypes.element</code></li><li><code>PropTypes.bool</code></li><li><code>PropTypes.symbol</code></li><li><code>PropTypes.any</code></li></ol><p>我们可以为 <code>User</code> 组件定义 <code>propTypes</code>，如下所示。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">PropTypes</span> <span class="keyword">from</span> <span class="string">&#x27;prop-types&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">User</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="keyword">static</span> propTypes = &#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="title class_">PropTypes</span>.<span class="property">string</span>.<span class="property">isRequired</span>,</span><br><span class="line">    <span class="attr">age</span>: <span class="title class_">PropTypes</span>.<span class="property">number</span>.<span class="property">isRequired</span>,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;`Welcome, $&#123;this.props.name&#125;`&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">h2</span>&gt;</span>&#123;`Age, $&#123;this.props.age&#125;`&#125;<span class="tag">&lt;/<span class="name">h2</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>注意：在 React v15.5 中，<code>PropTypes</code> 被从 <code>React.PropTypes</code> 移到 <code>prop-types</code>库中。</p></blockquote><p>等效的函数式组件：</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">PropTypes</span> <span class="keyword">from</span> <span class="string">&#x27;prop-types&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">User</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;`Welcome, $&#123;this.props.name&#125;`&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h2</span>&gt;</span>&#123;`Age, $&#123;this.props.age&#125;`&#125;<span class="tag">&lt;/<span class="name">h2</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">User</span>.<span class="property">propTypes</span> = &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="title class_">PropTypes</span>.<span class="property">string</span>.<span class="property">isRequired</span>,</span><br><span class="line">  <span class="attr">age</span>: <span class="title class_">PropTypes</span>.<span class="property">number</span>.<span class="property">isRequired</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="52-React-的优势是什么？"><a href="#52-React-的优势是什么？" class="headerlink" title="52. React 的优势是什么？"></a>52. React 的优势是什么？</h2><p>以下是 React的 主要优势。</p><ol><li>通过虚拟 DOM 提高应用程序的性能。</li><li>JSX 使代码易于阅读和编写。</li><li>它在客户端和服务器端都能进行渲染（SSR）。</li><li>易于与框架（Angular, Backbone）集成，因为它只是一个视图库。</li><li>使用 Jest 等工具容易编写单元和集成测试。</li></ol><h2 id="53-React-的局限性是什么？"><a href="#53-React-的局限性是什么？" class="headerlink" title="53. React 的局限性是什么？"></a>53. React 的局限性是什么？</h2><p>除了优点之外，React 也有一些限制。</p><ol><li>React 只是一个视图库，不是一个完整的框架。</li><li>对于刚接触网络开发的初学者来说，有一个学习曲线。</li><li>将 React 整合到传统的 MVC 框架中需要一些额外的配置。</li><li>代码的复杂性随着内联模板和 JSX 的增加而增加。</li><li>太多的小组件导致了过度工程化或模板化。</li></ol><h2 id="54-什么是-React-v16-中的错误边界（Error-Boundary）？"><a href="#54-什么是-React-v16-中的错误边界（Error-Boundary）？" class="headerlink" title="54. 什么是 React v16 中的错误边界（Error Boundary）？"></a>54. 什么是 React v16 中的错误边界（Error Boundary）？</h2><p>错误边界是指在其子组件树的任何地方捕获 JavaScript 错误的组件，记录这些错误，并显示一个后备 UI ，而不是崩溃的组件树。</p><p>如果一个类组件定义了一个新的生命周期方法 <code>componentDidCatch(error, info)</code> 或 <code>static getDerivedStateFromError()</code> ，它就成为一个错误边界。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ErrorBoundary</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">props</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(props);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">state</span> = &#123; <span class="attr">hasError</span>: <span class="literal">false</span> &#125;;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">componentDidCatch</span>(<span class="params">error, info</span>) &#123;</span><br><span class="line">    <span class="comment">// 你也可以把错误记录到一个错误报告服务中去</span></span><br><span class="line">    <span class="title function_">logErrorToMyService</span>(error, info)。</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">static</span> <span class="title function_">getDerivedStateFromError</span>(<span class="params">error</span>) &#123;</span><br><span class="line">    <span class="comment">// 更新状态，以便下次渲染时显示回退的用户界面。</span></span><br><span class="line">    <span class="keyword">return</span> &#123; <span class="attr">hasError</span>: <span class="literal">true</span> &#125;;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">hasError</span>) &#123;</span><br><span class="line">      <span class="comment">// 你可以渲染任何自定义的回退用户界面</span></span><br><span class="line">      <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;&#x27;Something went wrong.&#x27;&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">props</span>.<span class="property">children</span>。</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>之后把它作为一个普通的组件使用。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&lt;<span class="title class_">ErrorBoundary</span>&gt;</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">MyWidget</span> /&gt;</span></span></span><br><span class="line">&lt;/<span class="title class_">ErrorBoundary</span>&gt;</span><br></pre></td></tr></table></figure><h2 id="55-React-v15-中是如何处理错误边界的？"><a href="#55-React-v15-中是如何处理错误边界的？" class="headerlink" title="55. React v15 中是如何处理错误边界的？"></a>55. React v15 中是如何处理错误边界的？</h2><p>React v15 使用 <code>unstable_handleError</code> 方法为错误边界提供了非常基本的支持。在 React v16 中，它已经被重新命名为 <code>componentDidCatch</code>。</p><h2 id="56-静态类型检查的推荐方式是什么？"><a href="#56-静态类型检查的推荐方式是什么？" class="headerlink" title="56. 静态类型检查的推荐方式是什么？"></a>56. 静态类型检查的推荐方式是什么？</h2><p>通常我们使用 PropTypes 库（<code>React.PropTypes</code> 从 React v15.5 开始转移到 <code>prop-types</code> 包）来进行 React 应用中的类型检查。对于大型代码库，建议使用静态类型检查器，如 Flow 或 TypeScript，在编译时进行类型检查并提供自动补全功能。</p><h2 id="57-react-dom-包有什么用？"><a href="#57-react-dom-包有什么用？" class="headerlink" title="57. react-dom 包有什么用？"></a>57. <code>react-dom</code> 包有什么用？</h2><p><code>react-dom</code> 包提供了 DOM 特定的方法，可以在你的应用程序的顶层使用。大多数组件不需要使用此模块。这个包的一些方法是：</p><ol><li><code>render()</code></li><li><code>hydrate()</code></li><li><code>unmountComponentAtNode()</code></li><li><code>findDOMNode()</code></li><li><code>createPortal()</code></li></ol><h2 id="58-react-dom-的-render-方法的目的是什么？"><a href="#58-react-dom-的-render-方法的目的是什么？" class="headerlink" title="58. react-dom 的 render 方法的目的是什么？"></a>58. <code>react-dom</code> 的 render 方法的目的是什么？</h2><p>此方法用于将 React 元素渲染到提供的容器中的 DOM 中，并返回对组件的引用。如果 React 元素之前已渲染到容器中，它将对其执行更新，并且仅在必要时更改 DOM 以反映最新更改。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">ReactDOM</span>.<span class="title function_">render</span>(element, container[, callback])</span><br></pre></td></tr></table></figure><p>如果提供了可选的回调，它将在组件渲染或更新后执行。</p><h2 id="59-什么是-ReactDOMServer？"><a href="#59-什么是-ReactDOMServer？" class="headerlink" title="59. 什么是 ReactDOMServer？"></a>59. 什么是 ReactDOMServer？</h2><p><code>ReactDOMServer</code> 对象使你能够将组件呈现为静态标记（通常用于节点服务器）。该对象主要用于服务器端渲染（SSR）。以下方法可用于服务器和浏览器环境：</p><ol><li><code>renderToString()</code></li><li><code>renderToStaticMarkup()</code></li></ol><p>例如，你通常运行基于 Node 的 Web 服务器（如 Express、Hapi 或 Koa），然后调用 <code>renderToString</code> 将根组件渲染为字符串，然后将其作为响应发送。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用 Express</span></span><br><span class="line"><span class="keyword">import</span> &#123; renderToString &#125; <span class="keyword">from</span> <span class="string">&#x27;react-dom/server&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">MyPage</span> <span class="keyword">from</span> <span class="string">&#x27;./MyPage&#x27;</span>;</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">get</span>(<span class="string">&#x27;/&#x27;</span>, <span class="function">(<span class="params">req, res</span>) =&gt;</span> &#123;</span><br><span class="line">  res.<span class="title function_">write</span>(<span class="string">&#x27;&lt;!DOCTYPE html&gt;&lt;html&gt;&lt;head&gt;&lt;title&gt;My Page&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&#x27;</span>);</span><br><span class="line">  res.<span class="title function_">write</span>(<span class="string">&#x27;&lt;div id=&quot;content&quot;&gt;&#x27;</span>);</span><br><span class="line">  res.<span class="title function_">write</span>(<span class="title function_">renderToString</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">MyPage</span> /&gt;</span></span>));</span><br><span class="line">  res.<span class="title function_">write</span>(<span class="string">&#x27;&lt;/div&gt;&lt;/body&gt;&lt;/html&gt;&#x27;</span>);</span><br><span class="line">  res.<span class="title function_">end</span>();</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="60-如何在-React-中使用-innerHTML？"><a href="#60-如何在-React-中使用-innerHTML？" class="headerlink" title="60. 如何在 React 中使用 innerHTML？"></a>60. 如何在 React 中使用 innerHTML？</h2><p><code>dangerouslySetInnerHTML</code> 属性是 React 在浏览器 DOM 中使用 <code>innerHTML</code> 的替代品。就像 <code>innerHTML</code> 一样，考虑到跨站点脚本 (XSS) 攻击，使用此属性是有风险的。你只需要传递一个 <code>__html</code> 对象作为键和 HTML 文本作为值。</p><p>在这个例子中，MyComponent 使用 <code>dangerouslySetInnerHTML</code> 属性来设置 HTML 标记：</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">createMarkup</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> &#123; <span class="attr">__html</span>: <span class="string">&#x27;First &amp;middot; Second&#x27;</span> &#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">MyComponent</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">dangerouslySetInnerHTML</span>=<span class="string">&#123;createMarkup()&#125;</span> /&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="61-如何在-React-使用样式？"><a href="#61-如何在-React-使用样式？" class="headerlink" title="61. 如何在 React 使用样式？"></a>61. 如何在 React 使用样式？</h2><p><code>style</code> 属性接受一个小驼峰命名法属性的 JavaScript 对象，而不是一个 CSS 字符串。这与 DOM 风格的 JavaScript 属性一致，更有效率，并能防止 XSS 安全漏洞。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> divStyle = &#123;</span><br><span class="line">  <span class="attr">color</span>: <span class="string">&#x27;blue&#x27;</span>,</span><br><span class="line">  <span class="attr">backgroundImage</span>: <span class="string">`url(<span class="subst">$&#123;imgUrl&#125;</span>)`</span>,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">HelloWorldComponent</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">style</span>=<span class="string">&#123;divStyle&#125;</span>&gt;</span>Hello World!<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>样式键名是符合驼峰命名法的，以便与在 JavaScript 中访问 DOM 节点的属性相一致（例如 <code>node.style.backgroundImage</code>）。</p><h2 id="62-事件在-React-中有何不同？"><a href="#62-事件在-React-中有何不同？" class="headerlink" title="62. 事件在 React 中有何不同？"></a>62. 事件在 React 中有何不同？</h2><p>Handling events in React elements has some syntactic differences:</p><p>在 React 元素上处理事件有一些语法上的不同：</p><ol><li>React 事件处理程序使用小驼峰命名，而不是小写。</li><li>使用 JSX，你传递一个函数作为事件处理程序，而不是一个字符串。</li></ol><h2 id="63-如果你在构造函数中使用-setState-，会发生什么？"><a href="#63-如果你在构造函数中使用-setState-，会发生什么？" class="headerlink" title="63. 如果你在构造函数中使用 setState()，会发生什么？"></a>63. 如果你在构造函数中使用 <code>setState()</code>，会发生什么？</h2><p>当你使用 <code>setState()</code> 时，除了分配给对象的状态外，React 还重新渲染组件和它的所有子组件。你会得到这样的错误：只能更新一个已挂载或正在挂载的组件。所以我们需要使用 <code>this.state</code> 来初始化构造函数中的变量。</p><h2 id="64-索引作为键的影响是什么？"><a href="#64-索引作为键的影响是什么？" class="headerlink" title="64. 索引作为键的影响是什么？"></a>64. 索引作为键的影响是什么？</h2><p>键应该是稳定的、可预测的和唯一的，这样 React 就可以跟踪元素。</p><p>在下面的代码片段中，每个元素的键都是基于索引的，而不是与被表示的数据相联系。这限制了 React 可以做的优化。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  todos.<span class="title function_">map</span>(<span class="function">(<span class="params">todo, index</span>) =&gt;</span> <span class="language-xml"><span class="tag">&lt;<span class="name">Todo</span> &#123;<span class="attr">...todo</span>&#125; <span class="attr">key</span>=<span class="string">&#123;index&#125;</span> /&gt;</span></span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果你使用元素数据作为唯一键，假设 <code>todo.id</code> 在这个列表中是唯一的，并且是稳定的，React 将能够对元素进行重新排序，而不需要像以前那样重新计算它们。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  todos.<span class="title function_">map</span>(<span class="function"><span class="params">todo</span> =&gt;</span> <span class="language-xml"><span class="tag">&lt;<span class="name">Todo</span> &#123;<span class="attr">...todo</span>&#125; <span class="attr">key</span>=<span class="string">&#123;todo.id&#125;</span> /&gt;</span></span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="65-在-componentWillMount-方法中使用-setState-真的好吗"><a href="#65-在-componentWillMount-方法中使用-setState-真的好吗" class="headerlink" title="65. 在 componentWillMount() 方法中使用 setState 真的好吗?"></a>65. 在 <code>componentWillMount()</code> 方法中使用 <code>setState</code> 真的好吗?</h2><p>是的，在 <code>componentWillMount()</code> 方法中使用 <code>setState()</code> 是安全的。但同时，建议避免在<code>componentWillMount()</code> 生命周期方法中进行异步初始化。<code>componentWillMount()</code> 在挂载发生前立即被调用。它在 <code>render()</code> 之前被调用，因此在这个方法中设置状态不会触发重新渲染。避免在这个方法中引入任何副作用或订阅。我们需要确保组件初始化的异步调用发生在 <code>componentDidMount()</code> 而不是 <code>componentWillMount()</code>。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">componentDidMount</span>(<span class="params"></span>) &#123;</span><br><span class="line">  axios.<span class="title function_">get</span>(<span class="string">`api/todos`</span>).<span class="title function_">then</span>(<span class="function">(<span class="params">result</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">      <span class="attr">messages</span>: [...result.<span class="property">data</span>]</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="66-如果你在初始状态下使用-props，会发生什么？"><a href="#66-如果你在初始状态下使用-props，会发生什么？" class="headerlink" title="66. 如果你在初始状态下使用 props，会发生什么？"></a>66. 如果你在初始状态下使用 props，会发生什么？</h2><p>如果组件上的 props 被改变而组件没有被刷新，新的 props 值将永远不会被显示，因为构造函数永远不会更新组件的当前状态。来自 props 的状态初始化只在组件第一次被创建时运行。</p><p>下面这个组件就不会显示更新的输入值。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyComponent</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">props</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(props);</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">state</span> = &#123;</span><br><span class="line">      <span class="attr">records</span>: [],</span><br><span class="line">      <span class="attr">inputValue</span>: <span class="variable language_">this</span>.<span class="property">props</span>.<span class="property">inputValue</span>,</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>&#123;this.state.inputValue&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 render 方法中使用 props 将更新数值。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyComponent</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">props</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(props);</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">state</span> = &#123;</span><br><span class="line">      <span class="attr">record</span>: [],</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>&#123;this.props.inputValue&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="67-你如何有条件地渲染组件？"><a href="#67-你如何有条件地渲染组件？" class="headerlink" title="67. 你如何有条件地渲染组件？"></a>67. 你如何有条件地渲染组件？</h2><p>在某些情况下，你想根据一些状态来渲染不同的组件。JSX 不渲染 <code>false</code> 或 <code>undefined</code>，所以你可以使用条件性短路来渲染你的组件的某一部分，只有当某个条件为真时。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">MyComponent</span> = (<span class="params">&#123; name, address &#125;</span>) =&gt; (</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">h2</span>&gt;</span>&#123;name&#125;<span class="tag">&lt;/<span class="name">h2</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    &#123;address &amp;&amp; <span class="tag">&lt;<span class="name">p</span>&gt;</span>&#123;address&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span>&#125;</span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>如果你需要一个 <code>if-else</code> 条件，则使用三元运算符。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">MyComponent</span> = (<span class="params">&#123; name, address &#125;</span>) =&gt; (</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">h2</span>&gt;</span>&#123;name&#125;<span class="tag">&lt;/<span class="name">h2</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    &#123;address ? <span class="tag">&lt;<span class="name">p</span>&gt;</span>&#123;address&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span> : <span class="tag">&lt;<span class="name">p</span>&gt;</span>&#123;&#x27;Address is not available&#x27;&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span>&#125;</span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><h2 id="68-为什么我们在-DOM-元素上传递-props-时需要谨慎？"><a href="#68-为什么我们在-DOM-元素上传递-props-时需要谨慎？" class="headerlink" title="68. 为什么我们在 DOM 元素上传递 props 时需要谨慎？"></a>68. 为什么我们在 DOM 元素上传递 props 时需要谨慎？</h2><p>当我们传递 props 时，我们会遇到添加未知的 HTML 属性的风险，这是一个不好的做法。相反，我们可以使用带有 <code>...rest</code> 操作符的 prop 解构，所以它将只添加需要的 prop。</p><p>比如说。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">ComponentA</span> = (<span class="params"></span>) =&gt; (</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">ComponentB</span> <span class="attr">isDisplay</span>=<span class="string">&#123;true&#125;</span> <span class="attr">className</span>=<span class="string">&#123;</span>&#x27;<span class="attr">componentStyle</span>&#x27;&#125; /&gt;</span></span></span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">ComponentB</span> = (<span class="params">&#123; isDisplay, ...domProps &#125;</span>) =&gt; (</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> &#123;<span class="attr">...domProps</span>&#125;&gt;</span>&#123;&#x27;ComponentB&#x27;&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><h2 id="69-如何在-React-中使用装饰器？"><a href="#69-如何在-React-中使用装饰器？" class="headerlink" title="69. 如何在 React 中使用装饰器？"></a>69. 如何在 React 中使用装饰器？</h2><p>你可以对你的类组件进行装饰，这与将组件传入一个函数是一样的。<strong>装饰器</strong>是修改组件功能的灵活和可读的方式。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">@<span class="title function_">setTitle</span>(<span class="string">&#x27;Profile&#x27;</span>)</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Profile</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="comment">//....</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">title 是一个字符串，将被设置为文档标题。WrappedComponent 是我们的装饰器在以下情况下会收到的东西直接放在一个组件类上面时，我们的装饰器会收到这样的信息，如上面的例子所示</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">setTitle</span> = title =&gt; <span class="function"><span class="params">WrappedComponent</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">class</span> <span class="title class_">extends</span> <span class="title class_">React</span>.<span class="property">Component</span> &#123;</span><br><span class="line">    <span class="title function_">componentDidMount</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="variable language_">document</span>.<span class="property">title</span> = title;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">WrappedComponent</span> &#123;<span class="attr">...this.props</span>&#125; /&gt;</span></span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>注意：</strong> 装饰器是一个没有进入 ES7 的功能，但目前是一个第二阶段的建议。</p><h2 id="70-如何-memo-化一个组件？"><a href="#70-如何-memo-化一个组件？" class="headerlink" title="70. 如何 memo 化一个组件？"></a>70. 如何 memo 化一个组件？</h2><p>有一些可用的缓存库，可以用于函数组件。</p><p>例如，<code>moize</code> 库可以在另一个组件中对组件进行 memo 化。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> moize <span class="keyword">from</span> <span class="string">&#x27;moize&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Component</span> <span class="keyword">from</span> <span class="string">&#x27;./components/Component&#x27;</span>; <span class="comment">// 本模块导出一个非 memo 组件</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">MemoizedFoo</span> = moize.<span class="title function_">react</span>(<span class="title class_">Component</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">Consumer</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    &#123;&#x27;I will memoize the following entry:&#x27;&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">MemoizedFoo</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>更新：</strong> 从 React v16.6.0 开始，我们有一个 <code>React.memo</code>。它提供了一个更高阶的组件，除非 props 发生变化，否则会将组件缓存。要使用它，只需在使用前用 <code>React.memo</code> 包住组件。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">MemoComponent</span> = <span class="title class_">React</span>.<span class="title function_">memo</span>(<span class="keyword">function</span> <span class="title function_">MemoComponent</span>(<span class="params">props</span>) &#123;</span><br><span class="line">  <span class="comment">/* render using props */</span></span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">// 或者</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">React</span>.<span class="title function_">memo</span>(<span class="title class_">MyFunctionComponent</span>);</span><br></pre></td></tr></table></figure><h2 id="71-你如何实现服务器端渲染或SSR？"><a href="#71-你如何实现服务器端渲染或SSR？" class="headerlink" title="71. 你如何实现服务器端渲染或SSR？"></a>71. 你如何实现服务器端渲染或SSR？</h2><p>React 已经具备了在 Nod e服务器上处理渲染的能力。有一个特殊版本的 DOM 渲染器，它与客户端的模式相同。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">ReactDOMServer</span> <span class="keyword">from</span> <span class="string">&#x27;react-dom/server&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">App</span> <span class="keyword">from</span> <span class="string">&#x27;./App&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title class_">ReactDOMServer</span>.<span class="title function_">renderToString</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">App</span> /&gt;</span></span>)。</span><br></pre></td></tr></table></figure><p>这个方法将把常规的 HTML 输出为一个字符串，然后可以作为服务器响应的一部分放在页面主体内。在客户端，React 检测到预渲染的内容，并无缝地衔接该内容。</p><h2 id="72-如何在-React-中启用生产模式？"><a href="#72-如何在-React-中启用生产模式？" class="headerlink" title="72. 如何在 React 中启用生产模式？"></a>72. 如何在 React 中启用生产模式？</h2><p>你应该使用 Webpack 的 <code>DefinePlugin</code> 方法来设置 <code>NODE_ENV</code> 为 <code>production</code>，通过它来剥离诸如 propType 验证和额外警告的东西。除此之外，如果你对代码进行最小化处理，例如 Uglify 的无效代码消除法，剥离出只用于开发的代码和注释，这将极大地减少你的包的大小。</p><h2 id="73-什么是-CRA-以及它的好处？"><a href="#73-什么是-CRA-以及它的好处？" class="headerlink" title="73. 什么是 CRA 以及它的好处？"></a>73. 什么是 CRA 以及它的好处？</h2><p><code>create-react-app</code> CLI 工具允许你快速创建和运行React应用程序，无需配置步骤。</p><p>让我们使用 CRA 创建 Todo 应用程序。</p><figure class="highlight console"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">安装</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">npm install -g create-react-app</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">创建新项目</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">create-react-app todo-app</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">cd</span> todo-app</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">构建、测试、运行</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">npm run build</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">npm run <span class="built_in">test</span></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">npm start</span></span><br></pre></td></tr></table></figure><p>它包括我们建立一个 React 应用程序所需要的一切。</p><ol><li>支持 React、JSX、ES6 和 Flow 语法。</li><li>超越 ES6 的语言额外功能，如对象传播操作者。</li><li>自动前缀的 CSS，所以你不需要 <code>-webkit-</code> 或其他前缀。</li><li>一个快速的交互式单元测试运行器，内置支持覆盖率报告。</li><li>一个实时的开发服务器，对常见的错误发出警告。</li><li>一个构建脚本，用于捆绑 JS、CSS 和图片，并提供哈希和源码图。</li></ol><h2 id="74-安装中的生命周期方法的顺序是什么？"><a href="#74-安装中的生命周期方法的顺序是什么？" class="headerlink" title="74. 安装中的生命周期方法的顺序是什么？"></a>74. 安装中的生命周期方法的顺序是什么？</h2><p>当一个组件的实例被创建并插入到 DOM 中时，生命周期方法按以下顺序被调用。</p><ol><li><code>constructor()</code></li><li><code>static getDerivedStateFromProps()</code></li><li><code>render()</code></li><li><code>componentDidMount()</code></li></ol><h2 id="75-在-React-v16-中，有哪些生命周期方法将被废弃？"><a href="#75-在-React-v16-中，有哪些生命周期方法将被废弃？" class="headerlink" title="75. 在 React v16 中，有哪些生命周期方法将被废弃？"></a>75. 在 React v16 中，有哪些生命周期方法将被废弃？</h2><p>以下生命周期方法将是不安全的编码做法，在异步渲染中会出现更多问题。</p><ol><li><code>componentWillMount()</code></li><li><code>componentWillReceiveProps()</code></li><li><code>componentWillUpdate()</code></li></ol><p>从 React v16.3 开始，这些方法被别名为 <code>UNSAFE_</code> 前缀，未加前缀的版本将在 React v17 中被移除。</p><h2 id="76-getDerivedStateFromProps-生命周期方法的目的是什么？"><a href="#76-getDerivedStateFromProps-生命周期方法的目的是什么？" class="headerlink" title="76. getDerivedStateFromProps() 生命周期方法的目的是什么？"></a>76. <code>getDerivedStateFromProps()</code> 生命周期方法的目的是什么？</h2><p>新的静态的 <code>getDerivedStateFromProps()</code> 生命周期方法在一个组件实例化后以及重新渲染前被调用。它可以返回一个对象来更新状态，也可以返回 <code>null</code> 来表示新的 props 不需要任何状态更新。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyComponent</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="keyword">static</span> <span class="title function_">getDerivedStateFromProps</span>(<span class="params">props, state</span>) &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个生命周期方法与 <code>componentDidUpdate()</code> 一起涵盖了 <code>componentWillReceiveProps()</code> 的所有用例。</p><h2 id="77-getSnapshotBeforeUpdate-生命周期方法的目的是什么？"><a href="#77-getSnapshotBeforeUpdate-生命周期方法的目的是什么？" class="headerlink" title="77. getSnapshotBeforeUpdate() 生命周期方法的目的是什么？"></a>77. <code>getSnapshotBeforeUpdate()</code> 生命周期方法的目的是什么？</h2><p>新的 <code>getSnapshotBeforeUpdate()</code> 生命周期方法会在 DOM 更新前被调用。这个方法的返回值将作为第三个参数传递给 <code>componentDidUpdate()</code>。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyComponent</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">getSnapshotBeforeUpdate</span>(<span class="params">prevProps, prevState</span>) &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个生命周期方法与 <code>componentDidUpdate()</code> 一起涵盖了 <code>componentWillUpdate()</code> 的所有用例。</p><h2 id="78-Hooks-是否取代了渲染-props-和高阶组件？"><a href="#78-Hooks-是否取代了渲染-props-和高阶组件？" class="headerlink" title="78. Hooks 是否取代了渲染 props 和高阶组件？"></a>78. Hooks 是否取代了渲染 props 和高阶组件？</h2><p>渲染 props 和高阶组件都只渲染一个 children，但在大多数情况下，Hooks 是一种更简单的方式，通过减少树中的嵌套来达到这个目的。</p><h2 id="79-推荐用什么方式来命名组件？"><a href="#79-推荐用什么方式来命名组件？" class="headerlink" title="79. 推荐用什么方式来命名组件？"></a>79. 推荐用什么方式来命名组件？</h2><p>建议通过引用来命名组件，而不是使用 <code>displayName</code>。</p><p>使用 <code>displayName</code> 来命名组件。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">React</span>.<span class="title function_">createClass</span>(&#123;</span><br><span class="line">  <span class="attr">displayName</span>: <span class="string">&#x27;TodoApp&#x27;</span>,</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>推荐的方法。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">class</span> <span class="title class_">TodoApp</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="80-建议在组件类中方法的排序是什么？"><a href="#80-建议在组件类中方法的排序是什么？" class="headerlink" title="80. 建议在组件类中方法的排序是什么？"></a>80. 建议在组件类中方法的排序是什么？</h2><p>建议从安装到渲染阶段的方法的排序。</p><ol><li><code>static</code> 方法</li><li><code>constructor()</code></li><li><code>getChildContext()</code></li><li><code>componentWillMount()</code></li><li><code>componentDidMount()</code></li><li><code>componentWillReceiveProps()</code></li><li><code>shouldComponentUpdate()</code></li><li><code>componentWillUpdate()</code></li><li><code>componentDidUpdate()</code></li><li><code>componentWillUnmount()</code></li><li>点击处理程序或事件处理程序，如 <code>onClickSubmit()</code> 或 <code>onChangeDescription()</code></li><li>渲染的 getter 方法，如 <code>getSelectReason()</code> 或 <code>getFooterContent()</code></li><li>可选的渲染方法，如 <code>renderNavigation()</code> 或 <code>renderProfilePicture()</code></li><li>render()</li></ol><h2 id="81-什么是切换组件？"><a href="#81-什么是切换组件？" class="headerlink" title="81. 什么是切换组件？"></a>81. 什么是切换组件？</h2><p>切换组件是一个渲染许多组件中的一个组件。我们需要使用对象来将 props 值映射到组件。</p><p>例如，一个切换组件可以根据 <code>page</code> props 显示不同的页面。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">HomePage</span> <span class="keyword">from</span> <span class="string">&#x27;./HomePage&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">AboutPage</span> <span class="keyword">from</span> <span class="string">&#x27;./AboutPage&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">ServicesPage</span> <span class="keyword">from</span> <span class="string">&#x27;./ServicesPage&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">ContactPage</span> <span class="keyword">from</span> <span class="string">&#x27;./ContactPage&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">PAGES</span> = &#123;</span><br><span class="line">  <span class="attr">home</span>: <span class="title class_">HomePage</span>,</span><br><span class="line">  <span class="attr">about</span>: <span class="title class_">AboutPage</span>,</span><br><span class="line">  <span class="attr">services</span>: <span class="title class_">ServicesPage</span>,</span><br><span class="line">  <span class="attr">contact</span>: <span class="title class_">ContactPage</span>,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">Page</span> = props =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> <span class="title class_">Handler</span> = <span class="variable constant_">PAGES</span>[props.<span class="property">page</span>] || <span class="title class_">ContactPage</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">Handler</span> &#123;<span class="attr">...props</span>&#125; /&gt;</span></span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// PAGES 对象的键可以在 props 类型中使用，以捕捉开发时间错误。</span></span><br><span class="line"><span class="title class_">Page</span>.<span class="property">propTypes</span> = &#123;</span><br><span class="line">  <span class="attr">page</span>: <span class="title class_">PropTypes</span>.<span class="title function_">oneOf</span>(<span class="title class_">Object</span>.<span class="title function_">keys</span>(<span class="variable constant_">PAGES</span>)).<span class="property">isRequired</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="82-为什么我们需要向-setState-传递一个函数？"><a href="#82-为什么我们需要向-setState-传递一个函数？" class="headerlink" title="82. 为什么我们需要向 setState() 传递一个函数？"></a>82. 为什么我们需要向 <code>setState()</code> 传递一个函数？</h2><p>这背后的原因是，<code>setState()</code> 是一个异步操作。出于性能的考虑，React 会对状态变化进行批处理，所以在调用 <code>setState()</code> 后，状态可能不会立即发生变化。这意味着你在调用 <code>setState()</code> 时不应该依赖当前的状态，因为你不能确定这个状态会是什么。解决办法是将一个函数传递给 <code>setState()</code>，并将之前的状态作为参数。通过这样做，你可以避免由于 <code>setState()</code> 的异步性而导致用户在访问时获得旧的状态值的问题。</p><p>假设初始计数值为 0。在连续三次递增操作后，该值将只递增一个。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 假设 this.state.count === 0</span></span><br><span class="line"><span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123; <span class="attr">count</span>: <span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">count</span> + <span class="number">1</span> &#125;);</span><br><span class="line"><span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123; <span class="attr">count</span>: <span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">count</span> + <span class="number">1</span> &#125;);</span><br><span class="line"><span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123; <span class="attr">count</span>: <span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">count</span> + <span class="number">1</span> &#125;);</span><br><span class="line"><span class="comment">// this.state.count === 1，而不是 3</span></span><br></pre></td></tr></table></figure><p>如果我们给 <code>setState()</code> 传递一个函数，计数就会被正确地递增。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">this</span>.<span class="title function_">setState</span>(<span class="function">(<span class="params">prevState, props</span>) =&gt;</span> (&#123;</span><br><span class="line">  <span class="attr">count</span>: prevState.<span class="property">count</span> + props.<span class="property">increment</span>,</span><br><span class="line">&#125;));</span><br><span class="line"><span class="comment">// this.state.count === 3</span></span><br></pre></td></tr></table></figure><h2 id="83-为什么在-setState-中首选函数而不是对象？"><a href="#83-为什么在-setState-中首选函数而不是对象？" class="headerlink" title="83. 为什么在 setState() 中首选函数而不是对象？"></a>83. 为什么在 <code>setState()</code> 中首选函数而不是对象？</h2><p>React 可以将多个 <code>setState()</code> 的调用批量化为一次更新，以提高性能。因为 <code>this.props</code> 和 <code>this.state</code> 可能被异步更新，你不应该依赖它们的值来计算下一个状态。</p><p>这个计数器的例子将无法按预期更新。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 错误❌</span></span><br><span class="line"><span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">  <span class="attr">counter</span>: <span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">counter</span> + <span class="variable language_">this</span>.<span class="property">props</span>.<span class="property">increment</span>,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>首选的方法是用函数而不是对象调用 <code>setState()</code>。该函数将接收先前的状态作为第一个参数，并将应用更新时的 props 作为第二个参数。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 正确✅</span></span><br><span class="line"><span class="variable language_">this</span>.<span class="title function_">setState</span>(<span class="function">(<span class="params">prevState, props</span>) =&gt;</span> (&#123;</span><br><span class="line">  <span class="attr">counter</span>: prevState.<span class="property">counter</span> + props.<span class="property">increment</span>,</span><br><span class="line">&#125;));</span><br></pre></td></tr></table></figure><h2 id="84-React-中的严格模式是什么？"><a href="#84-React-中的严格模式是什么？" class="headerlink" title="84. React 中的严格模式是什么？"></a>84. React 中的严格模式是什么？</h2><p><code>React.StrictMode</code> 是一个有用的组件，用于暴露应用程序中的潜在问题。就像 <code>&lt;Fragment&gt;</code>，<code>&lt;StrictMode&gt;</code>不会渲染任何额外的 DOM 元素。它为其后代激活了额外的检查和警告。这些检查只适用于开发模式。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">ExampleApplication</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Header</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">React.StrictMode</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">ComponentOne</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">ComponentTwo</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">React.StrictMode</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Footer</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在上面的例子中，严格模式检查只适用于 <code>&lt;ComponentOne&gt;</code> 和 <code>&lt;ComponentTwo&gt;</code> 组件。</p><h2 id="85-为什么-isMounted-是一个反模式，正确的解决方案是什么？"><a href="#85-为什么-isMounted-是一个反模式，正确的解决方案是什么？" class="headerlink" title="85. 为什么 isMounted() 是一个反模式，正确的解决方案是什么？"></a>85. 为什么 <code>isMounted()</code> 是一个反模式，正确的解决方案是什么？</h2><p><code>isMounted()</code> 的主要用例是避免在组件被卸载后调用 <code>setState()</code>，因为它会发出警告。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="title function_">isMounted</span>()) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;...&#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在调用 <code>setState()</code> 之前检查 <code>isMounted()</code> 确实可以消除警告，但这也违背了警告的目的。使用 <code>isMounted()</code> 是一种代码异味，因为你检查的唯一原因是你认为你可能在组件卸载后还持有一个引用。</p><p>一个最佳的解决方案是找到在组件卸载后可能调用 <code>setState()</code> 的地方，并修复它们。这种情况通常是由于回调引起的，当一个组件在等待一些数据时，在数据到达之前被卸载。理想情况下，任何回调都应该在 <code>componentWillUnmount()</code> 中取消（在解除挂载之前）。</p><blockquote><p>代码异味 (Code smell)：程序开发领域，代码中的任何可能导致深层次问题的症状都可以叫做代码异味。 通常，在对代码做简短的反馈迭代时，代码异味会暴露出一些深层次的问题，这里的反馈迭代，是指以一种小范围的、可控的方式重构代码。</p></blockquote><h2 id="86-React-中支持哪些指针事件？"><a href="#86-React-中支持哪些指针事件？" class="headerlink" title="86. React 中支持哪些指针事件？"></a>86. React 中支持哪些指针事件？</h2><p>指针事件提供了一个处理所有输入事件的统一方法。在过去，我们有一个鼠标和各自的事件监听器来处理它们，但现在我们有许多设备与拥有鼠标不相关，如带有触摸表面的手机或笔。我们需要记住，这些事件只能在支持 Pointer Events 规范的浏览器中工作。</p><p>以下事件类型现在在 React DOM 中可用。</p><ol><li><code>onPointerDown</code></li><li><code>onPointerMove</code></li><li><code>onPointerUp</code></li><li><code>onPointerCancel</code></li><li><code>onGotPointerCapture</code></li><li><code>onLostPointerCapture</code></li><li><code>onPointerEnter</code></li><li><code>onPointerLeave</code></li><li><code>onPointerOver</code></li><li><code>onPointerOut</code></li></ol><h2 id="87-为什么组件名称要以大写字母开头？"><a href="#87-为什么组件名称要以大写字母开头？" class="headerlink" title="87. 为什么组件名称要以大写字母开头？"></a>87. 为什么组件名称要以大写字母开头？</h2><p>如果你使用 JSX 渲染你的组件，该组件的名称必须以大写字母开头，否则 React 将抛出一个错误，即未识别的标签。这个惯例是因为只有 HTML 元素和 SVG 标签可以以小写字母开头。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">SomeComponent</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Component</span> &#123;</span><br><span class="line">  <span class="comment">// 掘金不止，代码不停</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>你可以定义名称以小写字母开头的组件类，但当它被导入时，它应该是大写字母。在这里，小写就可以了。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">myComponent</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Component</span> &#123;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> /&gt;</span></span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> myComponent;</span><br></pre></td></tr></table></figure><p>而当导入另一个文件时，它应该以大写字母开始。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">MyComponent</span> <span class="keyword">from</span> <span class="string">&#x27;./MyComponent&#x27;</span>;</span><br></pre></td></tr></table></figure><h3 id="关于-React-组件的命名，有哪些例外情况？"><a href="#关于-React-组件的命名，有哪些例外情况？" class="headerlink" title="关于 React 组件的命名，有哪些例外情况？"></a>关于 React 组件的命名，有哪些例外情况？</h3><p>组件名称应以大写字母开头，但这一惯例也有少数例外。带点的小写标签名（属性访问器）仍被认为是有效的组件名。</p><p>例如，下面的标签可以被编译成一个有效的组件。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">render</span>(<span class="params"></span>)&#123;</span><br><span class="line"><span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">obj.component</span> /&gt;</span></span> <span class="comment">// `React.createElement(obj.component)`</span></span><br><span class="line">   )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="88-React-v16-中支持自定义-DOM-属性吗？"><a href="#88-React-v16-中支持自定义-DOM-属性吗？" class="headerlink" title="88. React v16 中支持自定义 DOM 属性吗？"></a>88. React v16 中支持自定义 DOM 属性吗？</h2><p>是的，在过去，React 习惯于忽略未知的 DOM 属性。如果你写的 JSX 有一个 React 不认识的属性，React 会直接跳过它。</p><p>例如，让我们看一下下面的属性。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;div mycustomattribute=&#123;<span class="string">&#x27;something&#x27;</span>&#125; /&gt;</span><br></pre></td></tr></table></figure><p>用 React v15 渲染一个空的 div 到 DOM 上。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> /&gt;</span></span><br></pre></td></tr></table></figure><p>在 React v16 中，任何未知的属性最终都会出现在 DOM 中。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">mycustomattribute</span>=<span class="string">&quot;something&quot;</span> /&gt;</span></span><br></pre></td></tr></table></figure><p>这对于提供浏览器特定的非标准属性，尝试新的 DOM API，以及与有主见的第三方库集成是非常有用的。</p><h2 id="89-constructor-和-getInitialState-的区别是什么？"><a href="#89-constructor-和-getInitialState-的区别是什么？" class="headerlink" title="89. constructor 和 getInitialState 的区别是什么？"></a>89. constructor 和 getInitialState 的区别是什么？</h2><p>当使用 ES6 类时，你应该在构造函数中初始化状态，而当使用 <code>React.createClass()</code> 时，应该在 <code>getInitialState()</code> 方法中初始化状态。</p><p><strong>使用 ES6 类：</strong></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyComponent</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">props</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(props);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">state</span> = &#123;</span><br><span class="line">      <span class="comment">/* 初始化状态 */</span></span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>使用 <code>React.createClass()</code>：</strong></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">MyComponent</span> = <span class="title class_">React</span>.<span class="title function_">createClass</span>(&#123;</span><br><span class="line">  <span class="title function_">getInitialState</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      <span class="comment">/* 初始化状态 */</span></span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p><strong>注意：</strong> <code>React.createClass()</code> 在 React v16 中已被废弃并删除。请使用普通的 JavaScript 类来代替。</p><h2 id="90-你能在不调用-setState-的情况下强制一个组件重新渲染吗？"><a href="#90-你能在不调用-setState-的情况下强制一个组件重新渲染吗？" class="headerlink" title="90. 你能在不调用 setState 的情况下强制一个组件重新渲染吗？"></a>90. 你能在不调用 setState 的情况下强制一个组件重新渲染吗？</h2><p>默认情况下，当你的组件的状态或 props 改变时，你的组件会重新渲染。如果你的 <code>render()</code> 方法依赖于其他数据，你可以通过调用 <code>forceUpdate()</code> 告诉 React 该组件需要重新渲染。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">component.<span class="title function_">forceUpdate</span>(callback);</span><br></pre></td></tr></table></figure><p>建议避免使用 <code>forceUpdate()</code>，只在 <code>render()</code> 中读取<code>this.props</code> 和 <code>this.state</code>。</p><h2 id="91-在-React-中使用-ES6-类的，super-和-super-props-之间有什么区别？"><a href="#91-在-React-中使用-ES6-类的，super-和-super-props-之间有什么区别？" class="headerlink" title="91. 在  React 中使用 ES6 类的，super() 和 super(props) 之间有什么区别？"></a>91. 在  React 中使用 ES6 类的，<code>super()</code> 和 <code>super(props)</code> 之间有什么区别？</h2><p>当你想在 <code>constructor()</code> 中访问 <code>this.props</code> 时，你应该把 props 传给 <code>super()</code> 方法。</p><p><strong>使用 <code>super(props)</code>：</strong></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyComponent</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">props</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(props);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">props</span>); <span class="comment">// &#123; name: &#x27;John&#x27;, ... &#125;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>使用 <code>super()</code>：</strong></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyComponent</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">props</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>();</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">props</span>); <span class="comment">// undefined</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 <code>constructor()</code> 之外，两者都会显示相同的 <code>this.props</code> 的值。</p><h2 id="92-如何在-JSX-内循环？"><a href="#92-如何在-JSX-内循环？" class="headerlink" title="92. 如何在 JSX 内循环？"></a>92. 如何在 JSX 内循环？</h2><p>你可以简单地使用 <code>Array.prototype.map</code> 与 ES6 箭头函数语法。</p><p>例如，对象的 <code>items</code> 数组被映射成组件的数组。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&lt;tbody&gt;</span><br><span class="line">  &#123;items.<span class="title function_">map</span>(<span class="function"><span class="params">item</span> =&gt;</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">SomeComponent</span> <span class="attr">key</span>=<span class="string">&#123;item.id&#125;</span> <span class="attr">name</span>=<span class="string">&#123;item.name&#125;</span> /&gt;</span></span></span><br><span class="line">  ))&#125;</span><br><span class="line">&lt;/tbody&gt;</span><br></pre></td></tr></table></figure><p>但你不能用 <code>for</code> 循环来迭代。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&lt;tbody&gt;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; items.<span class="property">length</span>; i++) &#123;</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">SomeComponent</span> <span class="attr">key</span>=<span class="string">&#123;items[i].id&#125;</span> <span class="attr">name</span>=<span class="string">&#123;items[i].name&#125;</span> /&gt;</span></span></span><br><span class="line">&#125;</span><br><span class="line">&lt;/tbody&gt;</span><br></pre></td></tr></table></figure><p>这是因为 JSX 标签被转换为函数调用，而且你不能在表达式中使用语句。这可能会改变，因为 <code>do</code> 表达式是第一阶段的建议。</p><h2 id="93-你如何在属性引号中访问-props？"><a href="#93-你如何在属性引号中访问-props？" class="headerlink" title="93. 你如何在属性引号中访问 props？"></a>93. 你如何在属性引号中访问 props？</h2><p>React（或 JSX）不支持属性值内的变量插值。下面的表示方法就不能用了。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;img className=<span class="string">&quot;image&quot;</span> src=<span class="string">&quot;images/&#123;this.props.image&#125;&quot;</span> /&gt;</span><br></pre></td></tr></table></figure><p>但你可以把任何 JS 表达式放在大括号内作为整个属性值。所以下面的表达式是有效的。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;img className=<span class="string">&quot;image&quot;</span> src=&#123;<span class="string">&#x27;images/&#x27;</span> + <span class="variable language_">this</span>.<span class="property">props</span>.<span class="property">image</span>&#125; /&gt;</span><br></pre></td></tr></table></figure><p>使用模板字符串也可以。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;img className=<span class="string">&quot;image&quot;</span> src=&#123;<span class="string">`images/<span class="subst">$&#123;<span class="variable language_">this</span>.props.image&#125;</span>`</span>&#125; /&gt;</span><br></pre></td></tr></table></figure><h2 id="94-什么是带-shape-的-React-原型数组？"><a href="#94-什么是带-shape-的-React-原型数组？" class="headerlink" title="94. 什么是带 shape 的 React 原型数组？"></a>94. 什么是带 shape 的 React 原型数组？</h2><p>如果你想把一个对象数组传递给一个具有特定 shape 的组件，那么使用 <code>React.PropTypes.shape()</code> 作为 <code>React.PropTypes.arrayOf()</code> 的一个参数。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">ReactComponent</span>.<span class="property">propTypes</span> = &#123;</span><br><span class="line">  <span class="attr">arrayWithShape</span>: <span class="title class_">React</span>.<span class="property">PropTypes</span>.<span class="title function_">arrayOf</span>(</span><br><span class="line">    <span class="title class_">React</span>.<span class="property">PropTypes</span>.<span class="title function_">shape</span>(&#123;</span><br><span class="line">      <span class="attr">color</span>: <span class="title class_">React</span>.<span class="property">PropTypes</span>.<span class="property">string</span>.<span class="property">isRequired</span>,</span><br><span class="line">      <span class="attr">fontSize</span>: <span class="title class_">React</span>.<span class="property">PropTypes</span>.<span class="property">number</span>.<span class="property">isRequired</span>,</span><br><span class="line">    &#125;),</span><br><span class="line">  ).<span class="property">isRequired</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="95-如何有条件地应用类属性？"><a href="#95-如何有条件地应用类属性？" class="headerlink" title="95. 如何有条件地应用类属性？"></a>95. 如何有条件地应用类属性？</h2><p>你不应该在引号内使用大括号，因为它将被计算为一个字符串。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;div className=<span class="string">&quot;btn-panel &#123;this.props.visible ? &#x27;show&#x27; : &#x27;hidden&#x27;&#125;&quot;</span>&gt;</span><br></pre></td></tr></table></figure><p>相反，你需要把大括号移到外面（别忘了在类名之间包括空格）。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;div className=&#123;<span class="string">&#x27;btn-panel &#x27;</span> + (<span class="variable language_">this</span>.<span class="property">props</span>.<span class="property">visible</span> ? <span class="string">&#x27;show&#x27;</span> : <span class="string">&#x27;hidden&#x27;</span>)&#125;&gt;</span><br></pre></td></tr></table></figure><p>模板字符串也可以使用。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;div className=&#123;<span class="string">`btn-panel <span class="subst">$&#123;<span class="variable language_">this</span>.props.visible ? <span class="string">&#x27;show&#x27;</span> : <span class="string">&#x27;hidden&#x27;</span>&#125;</span>`</span>&#125;&gt;</span><br></pre></td></tr></table></figure><h2 id="96-React-和-ReactDOM-之间有什么区别？"><a href="#96-React-和-ReactDOM-之间有什么区别？" class="headerlink" title="96. React 和 ReactDOM 之间有什么区别？"></a>96. React 和 ReactDOM 之间有什么区别？</h2><p><code>react</code> 包包含 <code>React.createElement()</code>、<code>React.Component</code>、<code>React.Children</code>, 以及其他与元素和组件类相关的帮助函数。你可以把这些看作是你构建组件所需要的同构或通用助手。<code>react-dom</code> 包包含 <code>ReactDOM.render()</code>，在 <code>react-dom/server</code> 中，我们有 <code>ReactDOMServer.renderToString()</code> 和 <code>ReactDOMServer.renderToStaticMarkup()</code> 的服务器端渲染支持。</p><h2 id="97-为什么-ReactDOM-要从-React-中分离出来？"><a href="#97-为什么-ReactDOM-要从-React-中分离出来？" class="headerlink" title="97. 为什么 ReactDOM 要从 React 中分离出来？"></a>97. 为什么 ReactDOM 要从 React 中分离出来？</h2><p>React 团队致力于将所有与 DOM 相关的功能提取到一个单独的库中，称为 ReactDOM。React v0.14 是第一个分割库的版本。通过查看一些包，<code>react-native</code>、<code>react-art</code>、<code>react-canvas</code>和 <code>react-three</code>，已经很清楚，React 的优秀和本质与浏览器或 DOM 无关。</p><p>为了建立更多 React 可以渲染的环境，React 团队计划将主 React 包分成两个：<code>react</code> 和 <code>react-dom</code>。这就为编写可以在网络版 React 和 React Native 之间共享的组件铺平了道路。</p><h2 id="98-如何使用-React-label-元素？"><a href="#98-如何使用-React-label-元素？" class="headerlink" title="98. 如何使用 React label 元素？"></a>98. 如何使用 React label 元素？</h2><p>如果你试图用标准的 <code>for</code> 属性渲染一个绑定在文本输入上的 <code>&lt;label&gt;</code> 元素，那么它产生的 HTML 会缺少该属性，并在控制台打印出警告。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&lt;label <span class="keyword">for</span>=&#123;<span class="string">&#x27;user&#x27;</span>&#125;&gt;&#123;<span class="string">&#x27;User&#x27;</span>&#125;&lt;/label&gt;</span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&#123;</span>&#x27;<span class="attr">text</span>&#x27;&#125; <span class="attr">id</span>=<span class="string">&#123;</span>&#x27;<span class="attr">user</span>&#x27;&#125; /&gt;</span></span></span><br></pre></td></tr></table></figure><p>由于 <code>for</code> 在 JavaScript 中是一个保留关键字，我们可以使用 <code>htmlFor</code> 代替。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&lt;label htmlFor=&#123;<span class="string">&#x27;user&#x27;</span>&#125;&gt;&#123;<span class="string">&#x27;User&#x27;</span>&#125;&lt;/label&gt;</span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&#123;</span>&#x27;<span class="attr">text</span>&#x27;&#125; <span class="attr">id</span>=<span class="string">&#123;</span>&#x27;<span class="attr">user</span>&#x27;&#125; /&gt;</span></span></span><br></pre></td></tr></table></figure><h2 id="99-如何组合多个内联样式对象？"><a href="#99-如何组合多个内联样式对象？" class="headerlink" title="99. 如何组合多个内联样式对象？"></a>99. 如何组合多个内联样式对象？</h2><p>你可以在常规 React 中使用展开语法。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&lt;button style=&#123;&#123; ...styles.<span class="property">panel</span>.<span class="property">button</span>, ...styles.<span class="property">panel</span>.<span class="property">submitButton</span> &#125;&#125;&gt;</span><br><span class="line">  &#123;<span class="string">&#x27;Submit&#x27;</span>&#125;</span><br><span class="line">&lt;/button&gt;</span><br></pre></td></tr></table></figure><p>如果你使用的是 React Native，那么你可以使用数组符号。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&lt;button style=&#123;[styles.<span class="property">panel</span>.<span class="property">button</span>, styles.<span class="property">panel</span>.<span class="property">submitButton</span>]&#125;&gt;</span><br><span class="line">  &#123;<span class="string">&#x27;Submit&#x27;</span>&#125;</span><br><span class="line">&lt;/button&gt;</span><br></pre></td></tr></table></figure><h2 id="100-如何在浏览器调整大小时重新渲染视图？"><a href="#100-如何在浏览器调整大小时重新渲染视图？" class="headerlink" title="100. 如何在浏览器调整大小时重新渲染视图？"></a>100. 如何在浏览器调整大小时重新渲染视图？</h2><p>你可以在 <code>componentDidMount()</code> 中监听 <code>resize</code> 事件，然后更新尺寸（<code>width</code> 和 <code>height</code>）。你应该在 <code>componentWillUnmount()</code> 方法中移除监听器。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">WindowDimensions</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">componentWillMount</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">updateDimensions</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">componentDidMount</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;resize&#x27;</span>, <span class="variable language_">this</span>.<span class="property">updateDimensions</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">componentWillUnmount</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">window</span>.<span class="title function_">removeEventListener</span>(<span class="string">&#x27;resize&#x27;</span>, <span class="variable language_">this</span>.<span class="property">updateDimensions</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  updateDimensions = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123; <span class="attr">width</span>: <span class="variable language_">window</span>.<span class="property">innerWidth</span>, <span class="attr">height</span>: <span class="variable language_">window</span>.<span class="property">innerHeight</span> &#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">span</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        &#123;this.state.width&#125; x &#123;this.state.height&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">span</span>&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="101-setState-和-replaceState-方法之间的区别是什么？"><a href="#101-setState-和-replaceState-方法之间的区别是什么？" class="headerlink" title="101. setState() 和 replaceState() 方法之间的区别是什么？"></a>101. <code>setState()</code> 和 <code>replaceState()</code> 方法之间的区别是什么？</h2><p>当你使用 <code>setState()</code> 时，当前和之前的状态被合并。 <code>replaceState()</code> 抛出当前的状态，只用你提供的内容来替换它。通常 <code>setState()</code> 会被使用，除非你真的因为某些原因需要删除所有之前的键。你也可以在 <code>setState()</code> 中把状态设置为 <code>false</code>&#x2F;<code>null</code>，而不是使用 <code>replaceState()</code>。</p><h2 id="102-如何监听状态变化？"><a href="#102-如何监听状态变化？" class="headerlink" title="102. 如何监听状态变化？"></a>102. 如何监听状态变化？</h2><p>当状态发生变化时，<code>componentDidUpdate</code> 生命周期方法将被调用。你可以将提供的状态和 props 值与当前的状态和 props 进行比较，以确定是否有意义的变化。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">componentDidUpdate(object prevProps, object prevState)</span><br></pre></td></tr></table></figure><p><strong>注意：</strong> 以前的 ReactJS 版本也使用 <code>componentWillUpdate(object nextProps, object nextState)</code> 监听状态改变。在最新的版本中，它已被弃用。</p><h2 id="103-在-React-状态下，删除数组元素的推荐方法是什么？"><a href="#103-在-React-状态下，删除数组元素的推荐方法是什么？" class="headerlink" title="103. 在 React 状态下，删除数组元素的推荐方法是什么？"></a>103. 在 React 状态下，删除数组元素的推荐方法是什么？</h2><p>更好的方法是使用 <code>Array.prototype.filter()</code> 方法。</p><p>例如，让我们创建一个 <code>removeItem()</code> 方法来更新状态。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">removeItem</span>(<span class="params">index</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">    <span class="attr">data</span>: <span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">data</span>.<span class="title function_">filter</span>(<span class="function">(<span class="params">item, i</span>) =&gt;</span> i !== index)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="104-有没有可能在不渲染-HTML-的情况下使用-React-呢？"><a href="#104-有没有可能在不渲染-HTML-的情况下使用-React-呢？" class="headerlink" title="104. 有没有可能在不渲染 HTML 的情况下使用 React 呢？"></a>104. 有没有可能在不渲染 HTML 的情况下使用 React 呢？</h2><p>在最新版本（&gt;&#x3D;16.2）中可以实现。以下是可用选项。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="literal">null</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> []</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">React.Fragment</span>&gt;</span><span class="tag">&lt;/<span class="name">React.Fragment</span>&gt;</span></span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;&gt;</span><span class="tag">&lt;/&gt;</span></span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>返回 <code>undefined</code> 是不行的。</p><h2 id="105-如何用-React-打印漂亮的-JSON？"><a href="#105-如何用-React-打印漂亮的-JSON？" class="headerlink" title="105. 如何用 React 打印漂亮的 JSON？"></a>105. 如何用 React 打印漂亮的 JSON？</h2><p>我们可以使用 <code>&lt;pre&gt;</code> 标签，这样可以保留 <code>JSON.stringify()</code> 的格式。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> data = &#123; <span class="attr">name</span>: <span class="string">&#x27;John&#x27;</span>, <span class="attr">age</span>: <span class="number">42</span> &#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">User</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">pre</span>&gt;</span>&#123;JSON.stringify(data, null, 2)&#125;<span class="tag">&lt;/<span class="name">pre</span>&gt;</span></span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">React</span>.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">User</span> /&gt;</span></span>, <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;container&#x27;</span>));</span><br></pre></td></tr></table></figure><h2 id="106-为什么你不能在-React-中更新-props？"><a href="#106-为什么你不能在-React-中更新-props？" class="headerlink" title="106. 为什么你不能在 React 中更新 props？"></a>106. 为什么你不能在 React 中更新 props？</h2><p>React 的理念是，props 应该是<strong>不可变的</strong>和<strong>自上而下</strong>的。这意味着父组件可以向子组件发送任何 props 值，但子组件不能修改收到的 props。</p><h2 id="107-如何在页面加载时聚焦一个输入框？"><a href="#107-如何在页面加载时聚焦一个输入框？" class="headerlink" title="107. 如何在页面加载时聚焦一个输入框？"></a>107. 如何在页面加载时聚焦一个输入框？</h2><p>你可以通过为 <code>input</code> 元素创建 ref 并在 <code>componentDidMount()</code> 中使用它。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">App</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">componentDidMount</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">nameInput</span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">nameInput</span>.<span class="title function_">focus</span>();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">input</span> <span class="attr">defaultValue</span>=<span class="string">&#123;</span>&quot;<span class="attr">Won</span>&#x27;<span class="attr">t</span> <span class="attr">focus</span>&quot;&#125; /&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">input</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">ref</span>=<span class="string">&#123;input</span> =&gt;</span> (this.nameInput = input)&#125;</span></span><br><span class="line"><span class="language-xml">          defaultValue=&#123;&#x27;Will focus&#x27;&#125;</span></span><br><span class="line"><span class="language-xml">        /&gt;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">ReactDOM</span>.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">App</span> /&gt;</span></span>, <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;app&#x27;</span>));</span><br></pre></td></tr></table></figure><h2 id="108-更新状态中的对象的方式有哪些？"><a href="#108-更新状态中的对象的方式有哪些？" class="headerlink" title="108. 更新状态中的对象的方式有哪些？"></a>108. 更新状态中的对象的方式有哪些？</h2><ol><li><strong>合并状态和对象后调用 <code>setState()</code>：</strong></li></ol><ul><li>使用 <code>Object.assign()</code> 创建对象的拷贝：</li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> user = <span class="title class_">Object</span>.<span class="title function_">assign</span>(&#123;&#125;, <span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">user</span>, &#123; <span class="attr">age</span>: <span class="number">42</span> &#125;);</span><br><span class="line"><span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123; user &#125;);</span><br></pre></td></tr></table></figure><ul><li>使用展开操作符：</li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> user = &#123; ...<span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">user</span>, <span class="attr">age</span>: <span class="number">42</span> &#125;;</span><br><span class="line"><span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123; user &#125;);</span><br></pre></td></tr></table></figure><ol start="2"><li><strong>调用 <code>setState()</code> 时传入函数：</strong></li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">this</span>.<span class="title function_">setState</span>(<span class="function"><span class="params">prevState</span> =&gt;</span> (&#123;</span><br><span class="line">  <span class="attr">user</span>: &#123;</span><br><span class="line">    ...prevState.<span class="property">user</span>,</span><br><span class="line">    <span class="attr">age</span>: <span class="number">42</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;));</span><br></pre></td></tr></table></figure><h2 id="109-我们如何在浏览器中查看运行时的-React-的版本？"><a href="#109-我们如何在浏览器中查看运行时的-React-的版本？" class="headerlink" title="109. 我们如何在浏览器中查看运行时的 React 的版本？"></a>109. 我们如何在浏览器中查看运行时的 React 的版本？</h2><p>你可以使用 <code>React.version</code> 来获取版本。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">REACT_VERSION</span> = <span class="title class_">React</span>.<span class="property">version</span>;</span><br><span class="line"></span><br><span class="line"><span class="title class_">ReactDOM</span>.<span class="title function_">render</span>(</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>&#123;`React version: $&#123;REACT_VERSION&#125;`&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>,</span><br><span class="line">  <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;app&#x27;</span>),</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h2 id="110-在-create-react-app-中包含-polyfills-的方法是什么？"><a href="#110-在-create-react-app-中包含-polyfills-的方法是什么？" class="headerlink" title="110. 在 create-react-app 中包含 polyfills 的方法是什么？"></a>110. 在 <code>create-react-app</code> 中包含 polyfills 的方法是什么？</h2><p>有一些方法可以在 create-react-app 中包含 polyfills。</p><ol><li><strong>手动从 <code>core-js</code> 引入：</strong></li></ol><p>创建一个名为（类似）<code>polyfills.js</code> 的文件并将其导入根 <code>index.js</code> 文件。运行 <code>npm install core-js</code> 或 <code>yarn add core-js</code> 并导入你所需要的特定功能。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">&#x27;core-js/fn/array/find&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">&#x27;core-js/fn/array/includes&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">&#x27;core-js/fn/number/is-nan&#x27;</span>;</span><br></pre></td></tr></table></figure><ol start="2"><li><strong>使用 Polyfill 服务：</strong></li></ol><p>使用 polyfill.io CDN，通过在 <code>index.html</code> 中添加这一行来检索自定义的、针对浏览器的 polyfills。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://cdn.polyfill.io/v2/polyfill.min.js?features=default,Array.prototype.includes&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>在上面的脚本中，我们必须明确请求 <code>Array.prototype.includes</code> 功能，因为它不包括在默认功能集中。</p><h2 id="111-如何在-create-react-app-中使用-https-而不是-http？"><a href="#111-如何在-create-react-app-中使用-https-而不是-http？" class="headerlink" title="111. 如何在 create-react-app 中使用 https 而不是 http？"></a>111. 如何在 create-react-app 中使用 https 而不是 http？</h2><p>你只需要是用 <code>HTTPS=true</code> 配置。你可以编辑 <code>package.json</code> scripts 部分：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;start&quot;</span><span class="punctuation">:</span> <span class="string">&quot;set HTTPS=true &amp;&amp; react-scripts start&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>或者运行 <code>set HTTPS=true &amp;&amp; npm start</code></p><h2 id="112-如何避免在-create-react-app-中使用相对路径导入？"><a href="#112-如何避免在-create-react-app-中使用相对路径导入？" class="headerlink" title="112. 如何避免在 create-react-app 中使用相对路径导入？"></a>112. 如何避免在 create-react-app 中使用相对路径导入？</h2><p>在项目里根目录创建一个叫 <code>.env</code> 的文件并写入导入的路径：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">NODE_PATH=src/app</span><br></pre></td></tr></table></figure><p>然后重启调试服务器。现在你应该能够在 <code>src/app</code> 目录下不使用相对路径导入任何东西。</p><h2 id="113-如何在-React-Router-中添加-Google-Analytics？"><a href="#113-如何在-React-Router-中添加-Google-Analytics？" class="headerlink" title="113. 如何在 React Router 中添加 Google Analytics？"></a>113. 如何在 React Router 中添加 Google Analytics？</h2><p>在 <code>history</code> 对象上添加一个监听器，以记录每个页面的浏览。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">history.<span class="title function_">listen</span>(<span class="keyword">function</span>(<span class="params">location</span>) &#123;</span><br><span class="line">  <span class="variable language_">window</span>.<span class="title function_">ga</span>(<span class="string">&#x27;set&#x27;</span>, <span class="string">&#x27;page&#x27;</span>, location.<span class="property">pathname</span> + location.<span class="property">search</span>);</span><br><span class="line">  <span class="variable language_">window</span>.<span class="title function_">ga</span>(<span class="string">&#x27;send&#x27;</span>, <span class="string">&#x27;pageview&#x27;</span>, location.<span class="property">pathname</span> + location.<span class="property">search</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="114-如何每秒更新一次组件？"><a href="#114-如何每秒更新一次组件？" class="headerlink" title="114. 如何每秒更新一次组件？"></a>114. 如何每秒更新一次组件？</h2><p>你需要使用 <code>setInterval()</code> 来触发变化，但你也需要在组件卸载时清除计时器以防止错误和内存泄漏。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">componentDidMount</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">interval</span> = <span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123; <span class="attr">time</span>: <span class="title class_">Date</span>.<span class="title function_">now</span>() &#125;), <span class="number">1000</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">componentWillUnmount</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="built_in">clearInterval</span>(<span class="variable language_">this</span>.<span class="property">interval</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="115-如何在-React-中对内联样式使用-CSS-厂商前缀？"><a href="#115-如何在-React-中对内联样式使用-CSS-厂商前缀？" class="headerlink" title="115. 如何在 React 中对内联样式使用 CSS 厂商前缀？"></a>115. 如何在 React 中对内联样式使用 CSS 厂商前缀？</h2><p>React 不会自动应用 CSS 厂商前缀。你需要手动添加 CSS 厂商前缀。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&lt;div</span><br><span class="line">  style=&#123;&#123;</span><br><span class="line">    <span class="attr">transform</span>: <span class="string">&#x27;rotate(90deg)&#x27;</span>,</span><br><span class="line">    <span class="title class_">WebkitTransform</span>: <span class="string">&#x27;rotate(90deg)&#x27;</span>, <span class="comment">// 注意大写 &#x27;W&#x27;</span></span><br><span class="line">    <span class="attr">msTransform</span>: <span class="string">&#x27;rotate(90deg)&#x27;</span>, <span class="comment">// &#x27;ms&#x27; 是全小写</span></span><br><span class="line">  &#125;&#125;</span><br><span class="line">/&gt;</span><br></pre></td></tr></table></figure><h2 id="116-如何使用-React-和-ES6-导入和导出组件？"><a href="#116-如何使用-React-和-ES6-导入和导出组件？" class="headerlink" title="116. 如何使用 React 和 ES6 导入和导出组件？"></a>116. 如何使用 React 和 ES6 导入和导出组件？</h2><p>你应该使用默认值来导出组件</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">User</span> <span class="keyword">from</span> <span class="string">&#x27;user&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">class</span> <span class="title class_">MyProfile</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">User</span> <span class="attr">type</span>=<span class="string">&quot;customer&quot;</span>&gt;</span>//...<span class="tag">&lt;/<span class="name">User</span>&gt;</span></span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>有了导出指定符，MyProfile 将成为成员并导出到这个模块，同样可以在其他组件中导入而不提及名称。</p><h2 id="117-为什么组件构造器只会被调用一次？"><a href="#117-为什么组件构造器只会被调用一次？" class="headerlink" title="117. 为什么组件构造器只会被调用一次？"></a>117. 为什么组件构造器只会被调用一次？</h2><p>React 的 reconciliation（协调） 算法假定，在没有任何相反信息的情况下，如果一个自定义组件在随后的渲染中出现在相同的地方，它就是之前的那个组件，所以 React 重用之前的实例而不是创建一个新的。</p><h2 id="118-如何在-React-中定义常量？"><a href="#118-如何在-React-中定义常量？" class="headerlink" title="118. 如何在 React 中定义常量？"></a>118. 如何在 React 中定义常量？</h2><p>你可以使用 ES7 的 <code>静态</code> 字段来定义常量。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyComponent</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="keyword">static</span> <span class="variable constant_">DEFAULT_PAGINATION</span> = <span class="number">10</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>静态字段是类字段第三阶段提案的一部分。</p><h2 id="119-如何在-React-中以编程方式触发点击事件？"><a href="#119-如何在-React-中以编程方式触发点击事件？" class="headerlink" title="119. 如何在 React 中以编程方式触发点击事件？"></a>119. 如何在 React 中以编程方式触发点击事件？</h2><p>你可以使用 ref props 通过回调获得对底层 <code>HTMLInputElement</code> 对象的引用，将该引用存储为类属性，然后使用该引用从事件处理程序中使用 <code>HTMLElement.click</code> 方法触发点击。</p><p>这可以分两步进行。</p><ol><li>在 render 方法中创建 ref：</li></ol><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;input ref=&#123;<span class="function"><span class="params">input</span> =&gt;</span> (<span class="variable language_">this</span>.<span class="property">inputElement</span> = input)&#125; /&gt;</span><br></pre></td></tr></table></figure><ol start="2"><li>在你的事件处理程序中应用点击事件。</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">this</span>.<span class="property">inputElement</span>.<span class="title function_">click</span>();</span><br></pre></td></tr></table></figure><h2 id="120-有可能在纯-React-中使用-async-await-吗？"><a href="#120-有可能在纯-React-中使用-async-await-吗？" class="headerlink" title="120. 有可能在纯 React 中使用 async&#x2F;await 吗？"></a>120. 有可能在纯 React 中使用 async&#x2F;await 吗？</h2><p>如果你想在 React 中使用 <code>async</code>&#x2F;<code>await</code>，你将需要 Babel 和 <a href="https://babeljs.io/docs/en/babel-plugin-transform-async-to-generator">transform-async-to-generator</a> 插件。React Native 已经包含了 Babel 和一系列的转换功能。</p><details class="folding-tag" green><summary> 121. React 的常见文件夹结构是什么？ </summary>              <div class='content'>              <p>React 项目文件结构有两种常见做法。</p><ol><li><strong>按特性或路由分组：</strong>*</li></ol><p>一种常见的项目结构方式是将 CSS、JS 和测试放在一起，按特性或路由分组。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">common/</span><br><span class="line">├─ Avatar.js</span><br><span class="line">├─ Avatar.css</span><br><span class="line">├─ APIUtils.js</span><br><span class="line">└─ APIUtils.test.js</span><br><span class="line">feed/</span><br><span class="line">├─ index.js</span><br><span class="line">├─ Feed.js</span><br><span class="line">├─ Feed.css</span><br><span class="line">├─ FeedStory.js</span><br><span class="line">├─ FeedStory.test.js</span><br><span class="line">└─ FeedAPI.js</span><br><span class="line">profile/</span><br><span class="line">├─ index.js</span><br><span class="line">├─ Profile.js</span><br><span class="line">├─ ProfileHeader.js</span><br><span class="line">├─ ProfileHeader.css</span><br><span class="line">└─ ProfileAPI.js</span><br></pre></td></tr></table></figure><ol start="2"><li><strong>按文件类型分组：</strong></li></ol><p>另一种流行的项目结构方式是将类似的文件分组。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">api/</span><br><span class="line">├─ APIUtils.js</span><br><span class="line">├─ APIUtils.test.js</span><br><span class="line">├─ ProfileAPI.js</span><br><span class="line">└─ UserAPI.js</span><br><span class="line">components/</span><br><span class="line">├─ Avatar.js</span><br><span class="line">├─ Avatar.css</span><br><span class="line">├─ Feed.js</span><br><span class="line">├─ Feed.css</span><br><span class="line">├─ FeedStory.js</span><br><span class="line">├─ FeedStory.test.js</span><br><span class="line">├─ Profile.js</span><br><span class="line">├─ ProfileHeader.js</span><br><span class="line">└─ ProfileHeader.css</span><br></pre></td></tr></table></figure>              </div>            </details><details class="folding-tag" green><summary> 122. 有哪些流行的动画包？ </summary>              <div class='content'>              <p>React Transition Group 和 React Motion 是 React 生态系统中流行的动画包。</p>              </div>            </details><details class="folding-tag" green><summary> 123. 样式模块的好处是什么？ </summary>              <div class='content'>              <p>我们建议避免在组件中硬编码样式值。任何可能在不同的 UI 组件中使用的值都应该被提取到它们自己的模块中。</p><p>例如，这些样式可以被提取到一个单独的组件中。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> colors = &#123;</span><br><span class="line">  white,</span><br><span class="line">  black,</span><br><span class="line">  blue,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> space = [<span class="number">0</span>, <span class="number">8</span>, <span class="number">16</span>, <span class="number">32</span>, <span class="number">64</span>];</span><br></pre></td></tr></table></figure><p>然后在其他组件中单独导入。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; space, colors &#125; <span class="keyword">from</span> <span class="string">&#x27;./styles&#x27;</span>;</span><br></pre></td></tr></table></figure>              </div>            </details><details class="folding-tag" green><summary> 124. 有哪些流行的 React 专用 linter？ </summary>              <div class='content'>              <p>ESLint 是一个很流行的 JavaScript linter。有一些插件可以用来分析特定的代码风格。其中最常见的 React 插件是一个名为 <code>eslint-plugin-react</code> 的 npm 包。默认情况下，它将检查一些最佳实践，其规则是检查从迭代器中的键到一整套道具类型的东西。</p><p>另一个流行的插件是 <code>eslint-plugin-jsx-a11y</code>，它将帮助修复可访问性方面的常见问题。由于 JSX 提供了与常规 HTML 稍有不同的语法，例如 <code>alt</code> 文本和 <code>tabindex</code> 的问题将不会被常规插件发现。</p>              </div>            </details><details class="folding-tag" green><summary> 125. 如何进行 AJAX 调用，应该在哪个组件的生命周期方法中进行 AJAX 调用？ </summary>              <div class='content'>              <p>你可以使用 AJAX 库，如 Axios、jQuery AJAX，以及浏览器内置的 <code>fetch</code>。你应该在 <code>componentDidMount()</code> 生命周期方法中获取数据。这样你就可以在获取数据时使用 <code>setState()</code> 来更新你的组件。</p><p>例如，从 API 获取的雇员名单并设置本地状态。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyComponent</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">props</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(props);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">state</span> = &#123;</span><br><span class="line">      <span class="attr">employees</span>: [],</span><br><span class="line">      <span class="attr">error</span>: <span class="literal">null</span>,</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">componentDidMount</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="title function_">fetch</span>(<span class="string">&#x27;https://api.example.com/items&#x27;</span>)</span><br><span class="line">      .<span class="title function_">then</span>(<span class="function"><span class="params">res</span> =&gt;</span> res.<span class="title function_">json</span>())</span><br><span class="line">      .<span class="title function_">then</span>(</span><br><span class="line">        <span class="function"><span class="params">result</span> =&gt;</span> &#123;</span><br><span class="line">          <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">            <span class="attr">employees</span>: result.<span class="property">employees</span>,</span><br><span class="line">          &#125;);</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="function"><span class="params">error</span> =&gt;</span> &#123;</span><br><span class="line">          <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123; error &#125;);</span><br><span class="line">        &#125;,</span><br><span class="line">      );</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; error, employees &#125; = <span class="variable language_">this</span>.<span class="property">state</span>;</span><br><span class="line">    <span class="keyword">if</span> (error) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>Error: &#123;error.message&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> (</span><br><span class="line">        <span class="language-xml"><span class="tag">&lt;<span class="name">ul</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          &#123;employees.map(employee =&gt; (</span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">li</span> <span class="attr">key</span>=<span class="string">&#123;employee.name&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">              &#123;employee.name&#125;-&#123;employee.experience&#125;</span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          ))&#125;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span></span><br><span class="line">      );</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>              </div>            </details><details class="folding-tag" green><summary> 126. 什么是 render props？ </summary>              <div class='content'>              <p><strong>render props</strong> 是一种简单的技术，使用一个 props 在组件之间共享代码，其值是一个函数。下面的组件使用渲染 props，它返回一个 React 元素。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;<span class="title class_">DataProvider</span> render=&#123;<span class="function"><span class="params">data</span> =&gt;</span> <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;`Hello $&#123;data.target&#125;`&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span>&#125; /&gt;</span><br></pre></td></tr></table></figure><p>React Router 和 DownShift 等库正在使用这种模式。</p>              </div>            </details>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/220ed3fe9370/</id>
    <link href="https://blog.zisheng.pro/posts/220ed3fe9370/"/>
    <published>2023-03-06T11:30:00.000Z</published>
    <summary>前 500 个 ReactJS 面试必知必会问题与答案，是紫升诚意翻译的前端 React 面试必备系列。</summary>
    <title>2023 React 面试题与答案</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React/"/>
    <category term="React" scheme="https://blog.zisheng.pro/tags/React/"/>
    <category term="性能优化" scheme="https://blog.zisheng.pro/tags/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
    <category term="React.memo" scheme="https://blog.zisheng.pro/tags/React-memo/"/>
    <category term="useMemo" scheme="https://blog.zisheng.pro/tags/useMemo/"/>
    <content>
      <![CDATA[<h2 id="React-Profiler"><a href="#React-Profiler" class="headerlink" title="React Profiler"></a>React Profiler</h2><p>React 16.5 添加了对开发者工具的 Profiler 插件的支持。该插件使用了 React 的 Profiler API 来收集所有组件渲染的耗时，目的是为了找出 React 应用程序的性能瓶颈。</p><p>这个“Profiler”面板初始为空，你可以点击 record 按钮开始分析：</p><p><img src="https://zh-hans.reactjs.org/static/bae8d10e17f06eeb8c512c91c0153cff/ad997/start-profiling.png" alt="紫升"></p><p>当你开始记录之后，开发者工具将在每次应用程序渲染时自动收集性能数据。 你可以和平常一样使用你的应用程序， 当你完成分析之后，请点击“Stop”按钮。</p><p><img src="https://zh-hans.reactjs.org/static/45619de03bed468869f7a0878f220586/ad997/stop-profiling.png" alt="紫升"></p><h2 id="优化法则"><a href="#优化法则" class="headerlink" title="优化法则"></a>优化法则</h2><ul><li>法则一：动静分离，将变的部分与不变的部分分离。</li><li>法则二：缓存，复杂计算和有昂贵消耗的组件 memo 化，比如 React 的 useMemo、useCallback，Redux 的 useSelector。</li></ul><p>下面我们将根据这些法则结合实际开发中案例进行分析。</p><h2 id="案例一"><a href="#案例一" class="headerlink" title="案例一"></a>案例一</h2><p>我们都知道，当 state 更新后，整个组件以及它的子组件都会重新更新，尽管子组件没有依赖任何 state，下面的例子就能很好地展示这个问题。</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useState &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Random</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;Math.random()&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Count</span>(<span class="params">props: &#123; count: <span class="built_in">number</span>&#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">h2</span>&gt;</span>&#123;props.count&#125;<span class="tag">&lt;/<span class="name">h2</span>&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [count, setCount] = <span class="title function_">useState</span>(<span class="number">0</span>)</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">onAdd</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="title function_">setCount</span>(count + <span class="number">1</span>)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">onMinus</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="title function_">setCount</span>(count - <span class="number">1</span>)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">&quot;App&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Random</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Count</span> <span class="attr">count</span>=<span class="string">&#123;count&#125;/</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;onMinus&#125;</span>&gt;</span>➖<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;onAdd&#125;</span>&gt;</span>➕<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure><p>在以上示例中，我们声明了一个显示随机数的组件和一个依赖 count 状态的数字显示组件，使用 React Profiler 工具分析如下：</p><p><img src="https://s2.loli.net/2022/09/25/zKn9HslamJi6Ro5.png" alt="紫升"></p><p>就像预期的那样，App、Count 和 Random 组件都更新了，从工具中我们可以看到它们更新的原因：</p><ul><li>App：Hook 1 changed.</li><li>Count：Props changed（count）.</li><li>Random：The parent component rendered.</li></ul><p>我们可以看到这里边除了 Count 是因为 count 更新之外，其他组件的更新都是被无辜牵连的。根据法则一，我们可以尝试将 Count 组件和它依赖的状态封装起来。</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useState &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Random</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;Math.random()&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Count</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [count, setCount] = <span class="title function_">useState</span>(<span class="number">0</span>)</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">onAdd</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="title function_">setCount</span>(count + <span class="number">1</span>)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">onMinus</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="title function_">setCount</span>(count - <span class="number">1</span>)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h2</span>&gt;</span>&#123;count&#125;<span class="tag">&lt;/<span class="name">h2</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;onMinus&#125;</span>&gt;</span>➖<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;onAdd&#125;</span>&gt;</span>➕<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">&quot;App&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Random</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Count</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure><p>从下图 Profiler 分析可以看到当 count 更新时，只有 Count 组件更新了。</p><p><img src="https://s2.loli.net/2022/09/25/YheAM9ZuviaJDmo.png" alt="紫升"></p><p>上面的示例太过理想化，大部分时候，负责更新状态的组件和负责展示状态的组件可能并不在一起，也就没办法抽离动态组件。比如我们将示例中的组件位置做下调整：</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ...</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">&quot;App&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Count</span> <span class="attr">count</span>=<span class="string">&#123;count&#125;/</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Random</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;onMinus&#125;</span>&gt;</span>➖<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;onAdd&#125;</span>&gt;</span>➕<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个时候，我们就需要法则二来帮助我们，最简单的是通过 props.children 属性来实现，原理上是一种依靠缓存的 bailout 优化方案。</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span>, &#123; useState &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">CountProps</span> &#123;</span><br><span class="line">  children?: <span class="variable constant_">JSX</span>.<span class="property">Element</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Random</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;Math.random()&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Count</span>(<span class="params">props: CountProps</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [count, setCount] = <span class="title function_">useState</span>(<span class="number">0</span>)</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">onAdd</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="title function_">setCount</span>(count + <span class="number">1</span>)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">onMinus</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="title function_">setCount</span>(count - <span class="number">1</span>)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h2</span>&gt;</span>&#123;count&#125;<span class="tag">&lt;/<span class="name">h2</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;props.children&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;onMinus&#125;</span>&gt;</span>➖<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;onAdd&#125;</span>&gt;</span>➕<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">&quot;App&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Count</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Random</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">Count</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure><h1 id="bailout"><a href="#bailout" class="headerlink" title="bailout"></a>bailout</h1><p>想要解释为什么案例一中使用 props.children 可以解决重复渲染，就要了解一下 React bailout 机制。</p><p>bailout（bail out of re-rendering）可以简单理解为是否重新渲染。</p><p>要触发 bailout 函数，需要同时满足以下条件：</p><ol><li>oldProps &#x3D;&#x3D;&#x3D; newProps 并且 Context 没有改变</li><li>!includesSomeLane(renderLanes, updateLanes)</li></ol><p>当前 fiber 上是否存在更新，如果存在那么更新的优先级是否和本次整棵 Fiber 树调度的优先级一致？如果一致代表该组件上存在更新，需要走 render 逻辑。</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// beginWork 函数</span></span><br><span class="line"><span class="keyword">if</span> (current !== <span class="literal">null</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> oldProps = current.<span class="property">memoizedProps</span>;</span><br><span class="line">  <span class="keyword">const</span> newProps = workInProgress.<span class="property">pendingProps</span>;</span><br><span class="line">  <span class="keyword">if</span> (</span><br><span class="line">    oldProps !== newProps ||</span><br><span class="line">    <span class="title function_">hasLegacyContextChanged</span>()</span><br><span class="line">  ) &#123;</span><br><span class="line">    didReceiveUpdate = <span class="literal">true</span>;</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (!<span class="title function_">includesSomeLane</span>(renderLanes, updateLanes)) &#123;</span><br><span class="line">    didReceiveUpdate = <span class="literal">false</span>;</span><br><span class="line">    <span class="comment">// 命中 bailoutOnAlreadyFinishedWork</span></span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">bailoutOnAlreadyFinishedWork</span>(current, workInProgress, renderLanes);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>bailout 函数逻辑大致是：尽量复用 fiber，不进行 render。fiber 复用，判断 fiber 的子树是否有 work。有，返回 child，继续遍历子树。无，返回 null，跳过子树。</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// bailoutOnAlreadyFinishedWork 函数</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">bailoutOnAlreadyFinishedWork</span>(<span class="params"></span></span><br><span class="line"><span class="params">  current: Fiber | <span class="literal">null</span>,</span></span><br><span class="line"><span class="params">  workInProgress: Fiber,</span></span><br><span class="line"><span class="params">  renderLanes: Lanes,</span></span><br><span class="line"><span class="params"></span>): <span class="title class_">Fiber</span> | <span class="literal">null</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (current !== <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="comment">// 重用以前的 context 依赖关系</span></span><br><span class="line">    workInProgress.<span class="property">dependencies</span> = current.<span class="property">dependencies</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 检测子树(childLanes)是否有 work</span></span><br><span class="line">  <span class="keyword">if</span> (!<span class="title function_">includesSomeLane</span>(renderLanes, workInProgress.<span class="property">childLanes</span>)) &#123;</span><br><span class="line">    <span class="comment">//无，跳过子树</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 虽然 fiber 没有 work，但是它的子树有，克隆子树的 fiber 然后继续检查</span></span><br><span class="line">  <span class="title function_">cloneChildFibers</span>(current, workInProgress);</span><br><span class="line">  <span class="keyword">return</span> workInProgress.<span class="property">child</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>案例一中，由于 children Random 组件的 props 没有发生改变，并且 lanes 也不在 renderLanes 上 ，Diff 组件命中 bailoutOnAlreadyFinishedWork。</p><p>lane 是 React 调度模型中的优先级模型。想象一下不同的赛车疾驰在不同的赛道。内圈的赛道总长度更短，外圈更长。某几个临近的赛道的长度可以看作差不多长。</p><p>lane 模型借鉴了同样的概念，使用 31 位的二进制表示 31 条赛道，位数越小的赛道优先级越高，某些相邻的赛道拥有相同优先级。</p><h2 id="案例二"><a href="#案例二" class="headerlink" title="案例二"></a>案例二</h2><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span>, &#123; useState, useContext &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> countContext = <span class="title class_">React</span>.<span class="property">createContext</span>&lt;<span class="built_in">number</span>&gt;(<span class="number">0</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">CountProvider</span> = countContext.<span class="property">Provider</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Random</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;Math.random()&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">CountProps</span> &#123;</span><br><span class="line">  children?: <span class="variable constant_">JSX</span>.<span class="property">Element</span></span><br><span class="line">  <span class="attr">onAdd</span>: <span class="function">() =&gt;</span> <span class="built_in">void</span></span><br><span class="line">  <span class="attr">onMinus</span>: <span class="function">() =&gt;</span> <span class="built_in">void</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Count</span>(<span class="params">props: CountProps</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> count = <span class="title function_">useContext</span>(countContext);</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h2</span>&gt;</span>&#123;count&#125;<span class="tag">&lt;/<span class="name">h2</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;props?.onMinus&#125;</span>&gt;</span>➖<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;props?.onAdd&#125;</span>&gt;</span>➕<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [count, setCount] = <span class="title function_">useState</span>(<span class="number">0</span>)</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">onAdd</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="title function_">setCount</span>(count + <span class="number">1</span>)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">onMinus</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="title function_">setCount</span>(count - <span class="number">1</span>)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">&quot;App&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">CountProvider</span> <span class="attr">value</span>=<span class="string">&#123;count&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Count</span> <span class="attr">onAdd</span>=<span class="string">&#123;onAdd&#125;</span> <span class="attr">onMinus</span>=<span class="string">&#123;onMinus&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Random</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">CountProvider</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure><p>该案例中我们引入了 Context，当 context 的 value 改变时，</p><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><ul><li><a href="https://zh-hans.reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html">React Profiler 介绍</a></li><li><a href="https://blog.wuct.me/react-internal-reconciliation-bailout-587695eb05a8?gi=c4bc84e7c1df">深入 React Reconciliation Bailout 機制</a></li><li><a href="https://jishuin.proginn.com/p/763bfbd32552">我在大厂写React学到了什么？性能优化篇</a></li><li><a href="https://juejin.cn/post/7090466770774917150">react 性能优化 - children如何避免不必要的渲染</a></li><li><a href="https://juejin.cn/post/7092593300233781285#heading-4">React 性能优化，你需要知道的一切</a></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/8132b809f088/</id>
    <link href="https://blog.zisheng.pro/posts/8132b809f088/"/>
    <published>2023-03-05T09:22:41.000Z</published>
    <summary>React 组件性能优化的核心是减少渲染真实 DOM 节点的频率，减少 VirtualDOM 比对的频率。</summary>
    <title>React 性能优化实践</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="组件化" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/%E7%BB%84%E4%BB%B6%E5%8C%96/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/tags/React-Native/"/>
    <category term="前端" scheme="https://blog.zisheng.pro/tags/%E5%89%8D%E7%AB%AF/"/>
    <category term="组件化" scheme="https://blog.zisheng.pro/tags/%E7%BB%84%E4%BB%B6%E5%8C%96/"/>
    <category term="组件库" scheme="https://blog.zisheng.pro/tags/%E7%BB%84%E4%BB%B6%E5%BA%93/"/>
    <content>
      <![CDATA[<blockquote><p>⚠️本文为掘金社区首发签约文章，未获授权禁止转载</p></blockquote><blockquote><p>大家好，我是<a href="https://blog.zisheng.pro/">紫升🎋</a>，一只住在杭城的木系前端🧚🏻‍♀️，如果你喜欢我的文章📚，可以通过点赞帮我聚集灵力⭐️。</p></blockquote><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在 <a href="https://juejin.cn/post/6983854006124675108">《每个前端都应该拥有自己的组件库,就像每个夏天都有西瓜🍉》</a> 一文中，紫升带领小黑从零搭建了一个组件库项目，完成了项目结构、构建、测试、文档等基础工程化工作并完成了第一个组件 Icon。本期延续上期的组件工程化的主题，夏日炎热，点上一杯杨枝甘露，和紫升赴一场 Button 开发之约吧。赴约后，你将会收获以下的内容：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/58285f3cce144972b2ccb88e11efd73f~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><blockquote><p>PS：配合<a href="https://github.com/youngjuning/vant-react-native">仓库</a>和<a href="https://vant-react-native.js.org/">组件库文档</a>阅读本文效果更佳喲！</p></blockquote><h2 id="Button-与设计心理学"><a href="#Button-与设计心理学" class="headerlink" title="Button 与设计心理学"></a>Button 与设计心理学</h2><p>作为前端工程师，入行至今接触最多的就是设计师了。耳濡目染下虽说没学会什么设计工具，但是对设计与人的心理有了一定认识。</p><p>紫升认为任何事物都不可能凭空出现，自有其传承。使用广泛的基础界面元素 Button 也不例外，我们生活中就有随处可见的按钮。举个栗子🌰，每天上班下班必然要按的电梯按钮、手机音量按钮、小米 9 鸡肋的小爱同学唤起按钮。要搞清楚为什么需要按钮，我们有必要探究下生活中这些按钮的作用。</p><h3 id="点一下按钮的快感"><a href="#点一下按钮的快感" class="headerlink" title="点一下按钮的快感"></a>点一下按钮的快感</h3><p>想象一下把键盘按键换成触摸屏，你最在乎的一定是完美还原物理键的敲击感，像紫升用手机虚拟键盘就喜欢设置按键震动和音效。通过打击（点击）获得快感是较为普遍的人性。按钮在按下、松开时有丰富的质感和交互感，完美满足了人们点一下的快感。</p><p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/542a6bbb880e4594ae384ee0bd1e511d~tplv-k3u1fbpfcp-watermark.image" alt="紫升"></p><h3 id="现实的实用性"><a href="#现实的实用性" class="headerlink" title="现实的实用性"></a>现实的实用性</h3><p>从 BB 机到诺基亚再到如今的智能机，实体按钮削减到只剩下音量键和开关机键。按键虽然光秃秃没有任何标识，但我们就是知道它的功能。试想一下没有这个来自远古时代的开关键，你手里的手机就是一块板砖。</p><h3 id="疯狂暗示用户，达到不可告人目的"><a href="#疯狂暗示用户，达到不可告人目的" class="headerlink" title="疯狂暗示用户，达到不可告人目的"></a>疯狂暗示用户，达到不可告人目的</h3><p>小米 9 单独唤起小爱同学的按键经常会被误按，之前我还不理解这么蠢的设计的目的。在简单研究了点设计心理学我明白了。小爱的设计者为了 产品日活和 AI 训练就是故意这个设计的。</p><p>小米 10 虽然移除了单独的唤起键，却把原来的电源键改成了一键多用。每次想要重启手机还得先唤起一下小爱同学。不得不说，小爱同学小米亲女儿。</p><p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/633381b8c2c342ed94bf024dbc19cf76~tplv-k3u1fbpfcp-watermark.image" alt="紫升"></p><p>吐槽归吐槽，小米这个按钮确实起到了培养用户习惯的任务。当用户知悉某个按钮能指向某个操作，或者获取某类信息后，长此以往用户就会形成使用习惯。如果某操作能够为用户和厂商持续带来价值，那就可以让按钮的位置更加醒目，持续培养用户点击习惯。</p><h3 id="指引用户操作"><a href="#指引用户操作" class="headerlink" title="指引用户操作"></a>指引用户操作</h3><p>这个在 Web 开发中是最常见的使用场景，每个可交互页面上都有这类按钮的出现，用来指引用户下一步该怎么做。比如表单的提交和重置。</p><p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d2c2a8abbe84471f96a67e18196c53fb~tplv-k3u1fbpfcp-watermark.image" alt="紫升"></p><p>虽然按钮也常作为表单元素，但是区别于其他表单元素，按钮因其天然地自说明性，不需要 Label 对其进行辅助说明，啰嗦这么多，掘友们应该在看到一个按钮时，应该也会有从设计上品鉴的意识了，欢迎将对下图的品鉴在评论区告诉紫升。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/88c6754b28984fc3866319c35790fc03~tplv-k3u1fbpfcp-watermark.image" alt="紫升"></p><h2 id="组件主题化"><a href="#组件主题化" class="headerlink" title="组件主题化"></a>组件主题化</h2><p>在开始开发具体组件之前，我们必须先约定好组件主题化的规范。之前 antd-mobile-rn 就因为设计问题，中途花费大力气重构。几乎所有的组件库都会将色彩、布局这些以 css 变量的形式提供给使用者和开发者为，React Native 不同的是样式基于 CSS in JS，不过道理相通，参照 vant 的<a href="https://github.com/youzan/vant/blob/dev/src/style/var.less">设计资源</a>，我们抽出了一套 JavaScript 常量：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// packages/themes</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> <span class="title class_">Theme</span> &#123;</span><br><span class="line">  <span class="string">&#x27;animation-duration-base&#x27;</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="string">&#x27;animation-duration-fast&#x27;</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="string">&#x27;animation-timing-function-enter&#x27;</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="string">&#x27;animation-timing-function-leave&#x27;</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="string">&#x27;font-size-xs&#x27;</span>: <span class="built_in">number</span>;</span><br><span class="line">  <span class="string">&#x27;font-size-sm&#x27;</span>: <span class="built_in">number</span>;</span><br><span class="line">  <span class="string">&#x27;font-size-md&#x27;</span>: <span class="built_in">number</span>;</span><br><span class="line">  <span class="string">&#x27;font-size-lg&#x27;</span>: <span class="built_in">number</span>;</span><br><span class="line">  <span class="string">&#x27;font-weight-bold&#x27;</span>: <span class="built_in">number</span>;</span><br><span class="line">  <span class="comment">// 变量过多，这里仅展示部分变量</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>有了这些 JS 常量，我们就可以设计主题系统。基于 CSS in JS 的主题化设计一般是基于 React Context 实现，需要提供 ThemeProvider 传入主题上下文，ThemeConsumer、WithTheme（高阶类组件）、withTheme（高阶函数组件） 或 useTheme（React Hooks）作为消费者获取上下文。自己实现也不难，不过更文任务比较紧急，我们先基于 <a href="https://github.com/cssinjs/theming">cssinjs&#x2F;theming</a> 实现功能，后期有需要再回来造轮子也不迟。下面👇就是我们基于 theming 的 <code>createTheming</code> 函数创建自定义主题上下文。</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; createTheming &#125; <span class="keyword">from</span> <span class="string">&#x27;theming&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> context = <span class="title class_">React</span>.<span class="title function_">createContext</span>(defaultTheme);</span><br><span class="line"><span class="keyword">const</span> theming = <span class="title function_">createTheming</span>(context);</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> &#123; <span class="title class_">ThemeProvider</span>, withTheme, useTheme &#125; = theming;</span><br></pre></td></tr></table></figure><blockquote><p>主题功能是通用的，因此我将主题相关的能力都放在 <code>@vant-react-native/theme</code> 包中发布。</p></blockquote><h2 id="Button-的实现"><a href="#Button-的实现" class="headerlink" title="Button 的实现"></a>Button 的实现</h2><p>React Native 内置的 Button 组件的样式是固定的，只能进行一些简单的设置。且内置的 Button 组件在 Android 和 ios 两个平台上的表现并不一致。所以我们需要根据更底层的组件进行封装。我们对比 ant-design-mobile-rn 和 react-native-elements 后采用了前者使用的 <code>TouchableHighlight</code> 组件。由于继承自 TouchableHighlight，所以我们组件的 Props 类型如下：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">TouchableHighlightProps</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;react-native&#x27;</span>;</span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">ButtonProps</span> <span class="keyword">extends</span> <span class="title class_">TouchableHighlightProps</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="按钮类型"><a href="#按钮类型" class="headerlink" title="按钮类型"></a>按钮类型</h3><p>vant 的 <a href="https://youzan.github.io/vant/#/zh-CN/button#an-niu-lei-xing">Button</a> 支持 <code>default</code>、<code>primary</code>、<code>info</code>、<code>warning</code>、<code>danger</code> 五种类型，默认为 <code>default</code>。现在，组件的基本定义如下：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ...</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">React</span>, &#123; <span class="title class_">FunctionComponent</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Text</span>, <span class="title class_">View</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;react-native&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">ButtonProps</span> &#123;</span><br><span class="line">  <span class="keyword">type</span>?: <span class="string">&#x27;default&#x27;</span> | <span class="string">&#x27;primary&#x27;</span> | <span class="string">&#x27;info&#x27;</span> | <span class="string">&#x27;warning&#x27;</span> | <span class="string">&#x27;danger&#x27;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Button</span>: <span class="title class_">FunctionComponent</span>&lt;<span class="title class_">ButtonProps</span>&gt; = <span class="function"><span class="params">props</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">// ...</span></span><br></pre></td></tr></table></figure><p>我们的组件为了适应主题化需求，样式不能是写死在组件里的，而是要通过上下文获取样式常量。我们思路是首先使用 <code>useTheme</code> 从上下文中获取主题，然后由于样式定义较多，我们为每个组件编写一个 <code>useStyle</code> hook 放在单独的 style.ts 文件中：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">StyleSheet</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;react-native&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Theme</span>, useTheme &#125; <span class="keyword">from</span> <span class="string">&#x27;@vant-react-native/theme&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">useStyle</span> = props =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> theme = useTheme&lt;<span class="title class_">Theme</span>&gt;();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">getBackgroundColor</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">switch</span> (props.<span class="property">type</span>) &#123;</span><br><span class="line">      <span class="keyword">case</span> <span class="string">&#x27;primary&#x27;</span>:</span><br><span class="line">        <span class="keyword">return</span> theme[<span class="string">&#x27;success-color&#x27;</span>];</span><br><span class="line">      <span class="keyword">case</span> <span class="string">&#x27;info&#x27;</span>:</span><br><span class="line">        <span class="keyword">return</span> theme[<span class="string">&#x27;primary-color&#x27;</span>];</span><br><span class="line">      <span class="keyword">case</span> <span class="string">&#x27;warning&#x27;</span>:</span><br><span class="line">        <span class="keyword">return</span> theme[<span class="string">&#x27;warning-color&#x27;</span>];</span><br><span class="line">      <span class="keyword">case</span> <span class="string">&#x27;danger&#x27;</span>:</span><br><span class="line">        <span class="keyword">return</span> theme[<span class="string">&#x27;danger-color&#x27;</span>];</span><br><span class="line">      <span class="attr">default</span>:</span><br><span class="line">        <span class="keyword">return</span> theme.<span class="property">white</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">getTextColor</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">if</span> (props.<span class="property">type</span> === <span class="string">&#x27;default&#x27;</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> theme.<span class="property">black</span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> theme.<span class="property">white</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">getBorderRadius</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">if</span> (props.<span class="property">round</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> theme[<span class="string">&#x27;border-radius-max&#x27;</span>];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (props.<span class="property">square</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> theme[<span class="string">&#x27;border-radius-sm&#x27;</span>];</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> styles = <span class="title class_">StyleSheet</span>.<span class="title function_">create</span>(&#123;</span><br><span class="line">    <span class="attr">container</span>: &#123;</span><br><span class="line">      <span class="attr">alignItems</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">      <span class="attr">backgroundColor</span>: <span class="title function_">getBackgroundColor</span>(),</span><br><span class="line">      <span class="attr">borderColor</span>: <span class="title function_">getBorderColor</span>(),</span><br><span class="line">      <span class="attr">borderRadius</span>: theme[<span class="string">&#x27;border-radius-sm&#x27;</span>],</span><br><span class="line">      <span class="attr">borderWidth</span>: theme[<span class="string">&#x27;border-width-base&#x27;</span>],</span><br><span class="line">      <span class="attr">flexDirection</span>: <span class="string">&#x27;row&#x27;</span>,</span><br><span class="line">      <span class="attr">flex</span>: <span class="number">1</span>,</span><br><span class="line">      <span class="attr">justifyContent</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">      <span class="attr">opacity</span>: <span class="number">1</span>,</span><br><span class="line">      <span class="attr">paddingHorizontal</span>: <span class="number">15</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">indicator</span>: &#123;</span><br><span class="line">      <span class="attr">marginRight</span>: theme[<span class="string">&#x27;padding-xs&#x27;</span>],</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">textStyle</span>: &#123;</span><br><span class="line">      <span class="attr">color</span>: <span class="title function_">getTextColor</span>(),</span><br><span class="line">      <span class="attr">fontSize</span>: <span class="number">14</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">wrapper</span>: &#123;</span><br><span class="line">      <span class="attr">borderRadius</span>: theme[<span class="string">&#x27;border-radius-sm&#x27;</span>],</span><br><span class="line">      <span class="attr">height</span>: <span class="number">44</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="keyword">return</span> styles;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>基于 <code>useStyle</code> 我们便可完成一个支持多类型的 Button 组件：</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">Button</span>: <span class="title class_">FunctionComponent</span>&lt;<span class="title class_">ButtonProps</span>&gt; = <span class="function"><span class="params">props</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> styles = <span class="title function_">useStyle</span>(props);</span><br><span class="line">  <span class="keyword">const</span> &#123; style, ...restProps &#125; = props;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">TouchableHighlight</span> <span class="attr">style</span>=<span class="string">&#123;[styles.wrapper,</span> <span class="attr">style</span>]&#125; &#123;<span class="attr">...restProps</span>&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">View</span> <span class="attr">style</span>=<span class="string">&#123;styles.container&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        &#123;typeof props.children === &#x27;string&#x27; ? (</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Text</span> <span class="attr">style</span>=<span class="string">&#123;styles.textStyle&#125;</span>&gt;</span>&#123;props.children&#125;<span class="tag">&lt;/<span class="name">Text</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        ) : (</span></span><br><span class="line"><span class="language-xml">          props.children</span></span><br><span class="line"><span class="language-xml">        )&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">View</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">TouchableHighlight</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><blockquote><p>注意：子组件可能是字符串，也可能是组件，所以需要判断类型。</p></blockquote><p>实现效果如下：</p><p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4a0b412c227e4dc994b818294165240d~tplv-k3u1fbpfcp-watermark.image" alt="紫升"></p><h3 id="朴素按钮"><a href="#朴素按钮" class="headerlink" title="朴素按钮"></a>朴素按钮</h3><p>朴素按钮的文字为按钮颜色，背景为白色，我们通过 <code>plain</code> 属性将按钮设置为朴素按钮。调研了 antd 和 react-native-elements 发现它们都是定义了很多样式，然后在组件内通过逻辑判断计算具体样式的值。个人很不喜欢这种方式，不是彻底的 CSS in JS，我的处理方式是将所有有关样式计算的都封装在每个组件的 <code>useStyle</code> 钩子中，比如当引入朴素按钮属性时，相对于普通按钮改变的有容器背景色、容器边框和字体颜色。所以我们将这三个属性的值都通过一个单独的函数计算。对比 antd 的源码，会发现不仅代码更易读，甚至代码量也少了。</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">getBackgroundColor</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">if</span> (props.<span class="property">plain</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> theme.<span class="property">white</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">getTextColor</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">if</span> (props.<span class="property">plain</span>) &#123;</span><br><span class="line">    <span class="keyword">switch</span> (props.<span class="property">type</span>) &#123;</span><br><span class="line">      <span class="keyword">case</span> <span class="string">&#x27;primary&#x27;</span>:</span><br><span class="line">        <span class="keyword">return</span> theme[<span class="string">&#x27;success-color&#x27;</span>];</span><br><span class="line">      <span class="keyword">case</span> <span class="string">&#x27;info&#x27;</span>:</span><br><span class="line">        <span class="keyword">return</span> theme[<span class="string">&#x27;primary-color&#x27;</span>];</span><br><span class="line">      <span class="keyword">case</span> <span class="string">&#x27;warning&#x27;</span>:</span><br><span class="line">        <span class="keyword">return</span> theme[<span class="string">&#x27;warning-color&#x27;</span>];</span><br><span class="line">      <span class="keyword">case</span> <span class="string">&#x27;danger&#x27;</span>:</span><br><span class="line">        <span class="keyword">return</span> theme[<span class="string">&#x27;danger-color&#x27;</span>];</span><br><span class="line">      <span class="attr">default</span>:</span><br><span class="line">        <span class="keyword">return</span> theme[<span class="string">&#x27;gray-3&#x27;</span>];</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (props.<span class="property">type</span> === <span class="string">&#x27;default&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> theme.<span class="property">black</span>;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> theme.<span class="property">white</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>实现效果如下：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0fc1bedf56fe4dd0a31e6d18b048665c~tplv-k3u1fbpfcp-watermark.image" alt="紫升"></p><h3 id="细边框"><a href="#细边框" class="headerlink" title="细边框"></a>细边框</h3><p>vant 实现细边框是通过设置 <code>hairline</code> 属性可以展示 0.5px 的细边框。但是手机上由于分辨率的影响，贸然设置 0.5 会导致边框不显示的兼容问题。好在 React Native 为我们提供了 <code>StyleSheet.hairlineWidth</code> 常量来兼容最细边框问题，下面是官方对它的定义：</p><blockquote><p>hairlineWidth 这一常量始终是一个整数的像素值（线看起来会像头发丝一样细），并会尽量符合当前平台最细的线的标准。可以用作边框或是两个元素间的分隔线。然而，你不能把它“视为一个常量”，因为不同的平台和不同的屏幕像素密度会导致不同的结果。</p><p>如果模拟器缩放过，可能会看不到这么细的线。</p></blockquote><p>由于 <code>hairline</code> 只影响了容器 <code>borderWidth</code> 属性，我们不需要编写单独的样式计算函数：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> styles = <span class="title class_">StyleSheet</span>.<span class="title function_">create</span>(&#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  <span class="attr">container</span>: &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    <span class="attr">borderWidth</span>: props.<span class="property">hairline</span> ? theme[<span class="string">&#x27;border-width-hairline&#x27;</span>] : theme[<span class="string">&#x27;border-width-base&#x27;</span>],</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>实现效果如下：</p><p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/43d80d537b07487d9043d718e6541f7e~tplv-k3u1fbpfcp-watermark.image" alt="紫升"></p><h3 id="禁用状态"><a href="#禁用状态" class="headerlink" title="禁用状态"></a>禁用状态</h3><p>表单元素或者说可触摸可点击的元素一般都有禁用状态，vant 中是通过 disabled 属性来禁用按钮，禁用状态下按钮不可点击。TouchableHighlight 继承地有 <code>disabled</code> 属性，我们只需要设置一些禁用状态下的按钮样式就可以，查看 vant 源码我们发现只需要修改透明度为 0.5 即可：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> styles = <span class="title class_">StyleSheet</span>.<span class="title function_">create</span>(&#123;</span><br><span class="line">  <span class="attr">container</span>: &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    <span class="attr">opacity</span>: props.<span class="property">disabled</span> ? <span class="number">0.5</span> : <span class="number">1</span>,</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>实现效果如下：</p><p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/892589086d07426a8346319d8466bccf~tplv-k3u1fbpfcp-watermark.image" alt="紫升"></p><h3 id="加载状态"><a href="#加载状态" class="headerlink" title="加载状态"></a>加载状态</h3><p>vant 是通过 <code>loading</code> 属性设置按钮为加载状态，加载状态下默认会隐藏按钮文字，可以通过 <code>loading-text</code> 设置加载状态下的文字。我们借助 React Native 的 ActivityIndicator 组件可以轻松实现这个特性：</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ...</span></span><br><span class="line">&lt;<span class="title class_">TouchableHighlight</span> &#123;...restProps&#125;&gt;</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">View</span> <span class="attr">style</span>=<span class="string">&#123;styles.contentWrapper&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    &#123;props.loading ? (</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">ActivityIndicator</span> <span class="attr">size</span>=<span class="string">&quot;small&quot;</span> <span class="attr">color</span>=<span class="string">&#123;indicatorColor&#125;</span> <span class="attr">style</span>=<span class="string">&#123;styles.indicator&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">        &#123;props.loadingText ? <span class="tag">&lt;<span class="name">Text</span> <span class="attr">style</span>=<span class="string">&#123;styles.textStyle&#125;</span>&gt;</span>&#123;props.loadingText&#125;<span class="tag">&lt;/<span class="name">Text</span>&gt;</span> : null&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    ) : <span class="literal">null</span>&#125;</span><br><span class="line">  &lt;/<span class="title class_">View</span>&gt;</span><br><span class="line">&lt;/<span class="title class_">TouchableHighlight</span>&gt;</span><br><span class="line"><span class="comment">// ...</span></span><br></pre></td></tr></table></figure><p>样式如下：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> useIndicatorColor = (<span class="attr">props</span>: <span class="title class_">ButtonProps</span>): <span class="function"><span class="params">string</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> theme = useTheme&lt;<span class="title class_">Theme</span>&gt;();</span><br><span class="line">  <span class="keyword">if</span> (props.<span class="property">plain</span>) &#123;</span><br><span class="line">    <span class="keyword">switch</span> (props.<span class="property">type</span>) &#123;</span><br><span class="line">      <span class="keyword">case</span> <span class="string">&#x27;primary&#x27;</span>:</span><br><span class="line">        <span class="keyword">return</span> theme[<span class="string">&#x27;success-color&#x27;</span>];</span><br><span class="line">      <span class="keyword">case</span> <span class="string">&#x27;info&#x27;</span>:</span><br><span class="line">        <span class="keyword">return</span> theme[<span class="string">&#x27;primary-color&#x27;</span>];</span><br><span class="line">      <span class="keyword">case</span> <span class="string">&#x27;warning&#x27;</span>:</span><br><span class="line">        <span class="keyword">return</span> theme[<span class="string">&#x27;warning-color&#x27;</span>];</span><br><span class="line">      <span class="keyword">case</span> <span class="string">&#x27;danger&#x27;</span>:</span><br><span class="line">        <span class="keyword">return</span> theme[<span class="string">&#x27;danger-color&#x27;</span>];</span><br><span class="line">      <span class="attr">default</span>:</span><br><span class="line">        <span class="keyword">return</span> theme.<span class="property">black</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (props.<span class="property">type</span> === <span class="string">&#x27;default&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> theme.<span class="property">black</span>;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> theme.<span class="property">white</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>实现效果如下：</p><p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/27c4340df2254245a3c830bf667a2e28~tplv-k3u1fbpfcp-watermark.image" alt="紫升"></p><h3 id="按钮形状"><a href="#按钮形状" class="headerlink" title="按钮形状"></a>按钮形状</h3><p>默认的按钮有值为 2 的圆角，vant 中通过 <code>square</code> 设置方形按钮，通过 <code>round</code> 设置圆形按钮。按例，我们通过判断设置样式：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">getBorderRadius</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">if</span> (props.<span class="property">round</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> theme[<span class="string">&#x27;border-radius-max&#x27;</span>];</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (props.<span class="property">square</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> theme[<span class="string">&#x27;border-radius-sm&#x27;</span>];</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">const</span> styles = <span class="title class_">StyleSheet</span>.<span class="title function_">create</span>(&#123;</span><br><span class="line">  <span class="attr">container</span>: &#123;</span><br><span class="line">    <span class="attr">borderColor</span>: <span class="title function_">getBorderColor</span>(),</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">wrapper</span>: &#123;</span><br><span class="line">    <span class="attr">borderRadius</span>: <span class="title function_">getBorderRadius</span>(),</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>实现效果如下：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d1402517566e49d1a1920fd9b3f7bb20~tplv-k3u1fbpfcp-watermark.image" alt="紫升"></p><h3 id="按钮尺寸"><a href="#按钮尺寸" class="headerlink" title="按钮尺寸"></a>按钮尺寸</h3><p>Antd RN 只提供了 large、small 两个尺寸，而在 vant 中支持 large、normal、small、mini 四种尺寸，默认为 normal。虽然写到这里已经很疲倦了，杨枝甘露也早喝完了，但是为了完整复原，还是续上一杯咖啡继续肝。根据 vant 设计稿我们新增三个样式获取函数并动态化指定样式：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">getSizeHeight</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">switch</span> (props.<span class="property">size</span>) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;large&#x27;</span>:</span><br><span class="line">      <span class="keyword">return</span> <span class="number">50</span>;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;small&#x27;</span>:</span><br><span class="line">      <span class="keyword">return</span> <span class="number">32</span>;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;mini&#x27;</span>:</span><br><span class="line">      <span class="keyword">return</span> <span class="number">24</span>;</span><br><span class="line">    <span class="attr">default</span>:</span><br><span class="line">      <span class="keyword">return</span> <span class="number">44</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">getSizePadding</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">switch</span> (props.<span class="property">size</span>) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;small&#x27;</span>:</span><br><span class="line">      <span class="keyword">return</span> <span class="number">8</span>;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;mini&#x27;</span>:</span><br><span class="line">      <span class="keyword">return</span> <span class="number">4</span>;</span><br><span class="line">    <span class="attr">default</span>:</span><br><span class="line">      <span class="keyword">return</span> <span class="number">15</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">getSizeFontSize</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">switch</span> (props.<span class="property">size</span>) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;large&#x27;</span>:</span><br><span class="line">      <span class="keyword">return</span> <span class="number">16</span>;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;small&#x27;</span>:</span><br><span class="line">      <span class="keyword">return</span> <span class="number">12</span>;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;mini&#x27;</span>:</span><br><span class="line">      <span class="keyword">return</span> <span class="number">10</span>;</span><br><span class="line">    <span class="attr">default</span>:</span><br><span class="line">      <span class="keyword">return</span> <span class="number">14</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> styles = <span class="title class_">StyleSheet</span>.<span class="title function_">create</span>(&#123;</span><br><span class="line">  <span class="attr">container</span>: &#123;</span><br><span class="line">    <span class="attr">paddingHorizontal</span>: <span class="title function_">getSizePadding</span>(),</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">textStyle</span>: &#123;</span><br><span class="line">    <span class="attr">fontSize</span>: <span class="title function_">getSizeFontSize</span>(),</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">wrapper</span>: &#123;</span><br><span class="line">    <span class="attr">height</span>: <span class="title function_">getSizeHeight</span>(),</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>实现效果如下：</p><p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/59c01002f25b477d85ed26fa1e21c86b~tplv-k3u1fbpfcp-watermark.image" alt="紫升"></p><h3 id="自定义颜色"><a href="#自定义颜色" class="headerlink" title="自定义颜色"></a>自定义颜色</h3><p>如果不是自己亲自复刻 Vant，是没想到一个 Button 能玩出这么多花，支持特性这么多耐心和代码管理都是一个挑战。当然了，紫升采取的样式管理方式比较偏激，大家有好的方式也可以在评论区讨论。</p><p>通过 <code>color</code> 属性自定义按钮的颜色。我们可以得出需求，不管 type 是什么，<code>color</code> 属性需始终覆盖原有样式，color 能影响的就是背景色、字体颜色和边框颜色，所以我们修改 <code>getBackgroundColor</code>、<code>getTextColor</code>、<code>getBorderColor</code> 样式函数在合适的地方加上以下代码即可：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (props.<span class="property">color</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> props.<span class="property">color</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>实现效果如下：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8efffdbfd6424618be6152895a53e8ee~tplv-k3u1fbpfcp-watermark.image" alt="紫升"></p><h2 id="双击事件的实现"><a href="#双击事件的实现" class="headerlink" title="双击事件的实现"></a>双击事件的实现</h2><p>我们从 React Native 内置的 TouchableHighlight 组件继承了很多事件，其中 onPress、onLongPress 分别代表单击和长按。但唯独“双击 666”的双击事件没有姓名。之前在实际业务曾经封装过双击事件，这次我们就直接就内置了。</p><p>实现思路是延时执行单击事件（默认 200 毫秒），然后记录点击次数和两次时间间隔，当识别为第二次点击且时间间隔小于单击延时时间。那么就取消单击事件延时，并立即执行双击事件。完整代码如下：</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> lastTime = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">let</span> clickCount = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">let</span> timeout = <span class="literal">null</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">_onPress</span> = (<span class="params">event: GestureResponderEvent</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> now = <span class="title class_">Date</span>.<span class="title function_">now</span>();</span><br><span class="line">  <span class="keyword">if</span> (timeout) &#123;</span><br><span class="line">    <span class="built_in">clearTimeout</span>(timeout);</span><br><span class="line">  &#125;</span><br><span class="line">  timeout = <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    props.<span class="title function_">onPress</span>(event);</span><br><span class="line">    clickCount = <span class="number">1</span>;</span><br><span class="line">    lastTime = <span class="number">0</span>;</span><br><span class="line">  &#125;, props.<span class="property">delayDoublePress</span>);</span><br><span class="line">  <span class="keyword">if</span> (clickCount === <span class="number">2</span> &amp;&amp; now - lastTime &lt;= props.<span class="property">delayDoublePress</span>) &#123;</span><br><span class="line">    <span class="built_in">clearTimeout</span>(timeout);</span><br><span class="line">    clickCount = <span class="number">1</span>;</span><br><span class="line">    lastTime = <span class="number">0</span>;</span><br><span class="line">    props.<span class="title function_">onDoublePress</span>(event);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    clickCount++;</span><br><span class="line">    lastTime = now;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>大家会发现这里的实现糅合了函数防抖、节流以及计数器的原理，有兴趣的小伙伴可以自行复习下原理，这里就不展开了。</p><h2 id="API-文档"><a href="#API-文档" class="headerlink" title="API 文档"></a>API 文档</h2><p>一个组件的文档，除了 Demo，还需要展示出来可用的 Props，Dumi 内置的 <code>&lt;API&gt;&lt;/API&gt;</code> 组件可以根据组件自动生成 API 文档。首先我们像下面一样编写 Props 注释：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">ButtonProps</span> <span class="keyword">extends</span> <span class="title class_">TouchableHighlightProps</span> &#123;</span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * <span class="doctag">@description</span>       Can be set to primary、info、warning、danger</span></span><br><span class="line"><span class="comment">   * <span class="doctag">@description</span>.zh-CN 类型，可选值为 primary、info、warning、danger</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="keyword">type</span>?: <span class="string">&#x27;default&#x27;</span> | <span class="string">&#x27;primary&#x27;</span> | <span class="string">&#x27;info&#x27;</span> | <span class="string">&#x27;warning&#x27;</span> | <span class="string">&#x27;danger&#x27;</span>;</span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * <span class="doctag">@description</span>       Can be set to large、small、mini</span></span><br><span class="line"><span class="comment">   * <span class="doctag">@description</span>.zh-CN 尺寸，可选值为</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  size?: <span class="string">&#x27;large&#x27;</span> | <span class="string">&#x27;normal&#x27;</span> | <span class="string">&#x27;small&#x27;</span> | <span class="string">&#x27;mini&#x27;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后在 Markdown 中引入 API 组件即可：</p><figure class="highlight md"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">API</span> <span class="attr">src</span>=<span class="string">&quot;./index.tsx&quot;</span>&gt;</span></span><span class="language-xml"><span class="tag">&lt;/<span class="name">API</span>&gt;</span></span></span><br></pre></td></tr></table></figure><p>内置组件 API 没有处理继承的情况，我们后续会自定义一个 API 组件，这里就不展开了，浏览 <a href="https://vant-react-native.js.org/components/button#api">Button 文档</a> 可以查看现在的效果：</p><p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7bedac7240334ff79c4142005a66b158~tplv-k3u1fbpfcp-watermark.image" alt="紫升"></p><h2 id="工程化串讲"><a href="#工程化串讲" class="headerlink" title="工程化串讲"></a>工程化串讲</h2><p>由于很难在一篇文章中将组件开发相关的工程化讲完，我们需要在每篇实战中串讲一下。</p><h3 id="组件创建脚手架"><a href="#组件创建脚手架" class="headerlink" title="组件创建脚手架"></a>组件创建脚手架</h3><blockquote><p>小黑：紫升，<code>lerna create</code> 命令创建出来的模块并不是我们想要的，以后要创建很多很多组件，我们可以写一个创建组件模块的脚手架吗？</p></blockquote><p>lerna 使用起来是有不少痛点的，<code>lerna create</code> 命令没办法指定模板，考虑到之后的几十上百个组件每次创建都要进行项目结构、Typescript 配置、单元测试配置、Babel 配置等等工作步骤，我们有必要写一个脚手架。</p><h4 id="模板解析"><a href="#模板解析" class="headerlink" title="模板解析"></a>模板解析</h4><p>说到模板解析，相信大家和我一样想到的是 vue-cli 的 template 解析。通过阅读 <a href="https://cdn.jsdelivr.net/npm/vue-cli@2.9.6/lib/generate.js">vue-cli@2.9.6 generate.js</a> 源码，我们可以分析出尤大是基于 metalsmith、handlebars、consolidate 这三个包来实现模板解析能力的。让人不安的是其中 metalsmith 库有长达 5 年没有维护了，紫升挑选开源项目一般对维护度很敏感，本着轮子要用自己造的原则，我翻看了 Metalsmith 的 Readme 发现这个插件无非是通过递归读文件的方式渲染模板，并且它的静态网站生成的能力对我们模板解析的需求也是多余的。</p><p>说干就干，在和 <a href="https://juejin.cn/user/3175045313873943">@林小帅</a> 同学简单沟通后，我动手造了 <a href="https://github.com/youngjuning/handlebars-template-compiler">handlebars-template-compiler</a> 这个轮子，其主要原理如下：</p><ol><li>使用 recursive-readdir 递归获取所有文件路径</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> files = <span class="keyword">await</span> <span class="title function_">recursive</span>(rootDir);</span><br></pre></td></tr></table></figure><ol start="2"><li>使用 <code>handlebars.compile</code> 方法使用元数据对模板进行渲染</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> content = fs.<span class="title function_">readFileSync</span>(file).<span class="title function_">toString</span>();</span><br><span class="line"><span class="keyword">const</span> result = handlebars.<span class="title function_">compile</span>(content)(meta);</span><br></pre></td></tr></table></figure><ol start="3"><li>使用 <code>fs.writeFileSync</code> API 重写文件</li></ol><p>另外，通过引入 <a href="https://www.npmjs.com/package/micromatch">glob</a> 模式匹配实现了 <code>exclude</code> 配置以及只处理指定后缀（默认 <code>**/*.tpl.*</code>）的文件来避免不必要的渲染。（PS：NPM 一周有了 300 多下载，有需要的掘友值得一试😄）</p><h4 id="Node-CLI（-vant-react-native-scripts）搭建"><a href="#Node-CLI（-vant-react-native-scripts）搭建" class="headerlink" title="Node CLI（@vant-react-native&#x2F;scripts）搭建"></a>Node CLI（@vant-react-native&#x2F;scripts）搭建</h4><p>这里紫升尝试用最简洁的语言为大家描述一个脚手架的诞生，源码在 <a href="https://github.com/youngjuning/vant-react-native/tree/main/packages/scripts">packages&#x2F;scripts</a> 目录下，没有接触过 CLI 的掘友请相信我，Node CLI 很容易上手的。接触过的同学也可以查漏补缺借鉴一二。</p><ol><li><code>package.json</code> 文件的 <code>bin</code> 字段是我们脚手架的入口</li></ol><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 指定可执行文件的位置以及别名</span></span><br><span class="line"><span class="attr">&quot;bin&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;vant&quot;</span><span class="punctuation">:</span> <span class="string">&quot;./bin/cli.js&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br></pre></td></tr></table></figure><ol start="2"><li>定义 <code>./bin/cli.js</code> 为可执行文件并调用 <code>init</code> 方法。</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 由于我们的脚本是 Node 编写的，所以需要指定 node 所在位置</span></span><br><span class="line">#!<span class="regexp">/usr/</span>bin/env node</span><br><span class="line"><span class="keyword">const</span> &#123; init &#125; = <span class="built_in">require</span>(<span class="string">&#x27;../lib&#x27;</span>);</span><br><span class="line"><span class="comment">// 这个地方参考了 create-react-native 的设计</span></span><br><span class="line"><span class="comment">// 本文点赞过 300，下一篇紫升带小黑为大家带来《基于 TypeScript 重构 create-react-native》</span></span><br><span class="line"><span class="title function_">init</span>();</span><br></pre></td></tr></table></figure><ol start="3"><li>然后在 <code>src/index.ts</code> 中初始化 commander 这个久负盛名的命令行框架</li></ol><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> init = (): <span class="function"><span class="params">void</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> packageJson = <span class="built_in">require</span>(<span class="string">&#x27;../package.json&#x27;</span>);</span><br><span class="line">  program.<span class="title function_">version</span>(packageJson.<span class="property">version</span>).<span class="title function_">description</span>(packageJson.<span class="property">description</span>);</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  program.<span class="title function_">parse</span>(process.<span class="property">argv</span>);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><ol start="4"><li>为了方便管理命令，我们将命令都放置在 <code>src/commands</code> 目录下并通过 <code>fs.readdirSync</code> API 动态扫描注册。</li></ol><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> init = (): <span class="function"><span class="params">void</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// 这段代码借鉴自 NeteaseCloudMusicApi 项目，作者的代码很有设计感，推荐阅读。</span></span><br><span class="line">  fs.<span class="title function_">readdirSync</span>(path.<span class="title function_">join</span>(__dirname, <span class="string">&#x27;commands&#x27;</span>)).<span class="title function_">forEach</span>(<span class="function">(<span class="params">file: <span class="built_in">string</span></span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!file.<span class="title function_">endsWith</span>(<span class="string">&#x27;.js&#x27;</span>)) <span class="keyword">return</span>;</span><br><span class="line">    <span class="built_in">require</span>(path.<span class="title function_">join</span>(__dirname, <span class="string">&#x27;commands&#x27;</span>, file));</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><ol start="5"><li>最后在 <code>commands</code> 目录下新建一个 <code>create.ts</code> 文件编写命令</li></ol><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; program &#125; <span class="keyword">from</span> <span class="string">&#x27;commander&#x27;</span>;</span><br><span class="line">program</span><br><span class="line">  .<span class="title function_">command</span>(<span class="string">&#x27;create &lt;name&gt; [loc]&#x27;</span>)</span><br><span class="line">  .<span class="title function_">description</span>(<span class="string">&#x27;Create a new vant-react-native package&#x27;</span>)</span><br><span class="line">  .<span class="title function_">action</span>(<span class="function">(<span class="params">name,loc</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Hello Luozhu&#x27;</span>);</span><br><span class="line">  &#125;)</span><br></pre></td></tr></table></figure><h4 id="脚手架实现"><a href="#脚手架实现" class="headerlink" title="脚手架实现"></a>脚手架实现</h4><p>上一小结，我们初始化了 CLI 并添加了 <code>create</code> 命令，这一小节我们就来实现一下脚手架功能。</p><p>我们首先在 <code>packages/scripts</code> 目录下创建组件模板</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">├── README.tpl.md <span class="comment"># tpl 后缀在生成组件模板的时候会被 handlebars-template-compiler 自动去掉。</span></span><br><span class="line">├── package.tpl.json</span><br><span class="line">├── src</span><br><span class="line">│   └── index.ts <span class="comment"># 没有 tpl 后缀则不会被编译，模板很大时可以节省时间。</span></span><br><span class="line">└── tsconfig.json</span><br></pre></td></tr></table></figure><p>然后我们明确我们的模板元数据的数据结构，我这里的数据结构是：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">IMeta</span> &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">version</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">description</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">author</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">email</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">url</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">directory</span>: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>有了数据结构，我们就可以使用 inquirer 模块引导用户输入信息。</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> inquirer <span class="keyword">from</span> <span class="string">&#x27;inquirer&#x27;</span>;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line"><span class="comment">// getQuestions 过长，感兴趣的同学可以查看：http://tny.im/UFbg</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">answer</span>: <span class="title class_">IMeta</span> = <span class="keyword">await</span> inquirer.<span class="title function_">prompt</span>(<span class="title function_">getQuestions</span>(name));</span><br><span class="line"><span class="comment">// ...</span></span><br></pre></td></tr></table></figure><p>下一步，我们使用 <code>tmp-promise</code> 模块创建一个系统临时文件夹，并将前文提到的 template 文件夹的内容拷贝进去：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> tmp <span class="keyword">from</span> <span class="string">&#x27;tmp-promise&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> fs <span class="keyword">from</span> <span class="string">&#x27;fs-extra&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> path <span class="keyword">from</span> <span class="string">&#x27;path&#x27;</span>;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line"><span class="keyword">const</span> tmpdir = <span class="keyword">await</span> tmp.<span class="title function_">dir</span>(&#123; <span class="attr">unsafeCleanup</span>: <span class="literal">true</span> &#125;);</span><br><span class="line">fs.<span class="title function_">copySync</span>(path.<span class="title function_">join</span>(__dirname, <span class="string">&#x27;../../template&#x27;</span>), tmpdir.<span class="property">path</span>);</span><br></pre></td></tr></table></figure><p>最后，我们对临时文件夹的内容进行编译再拷贝到指定位置即可：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> htc <span class="keyword">from</span> <span class="string">&#x27;handlebars-template-compiler&#x27;</span>;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line"><span class="keyword">await</span> htc&lt;<span class="title class_">IMeta</span>&gt;(answer, tmpdir.<span class="property">path</span>);</span><br><span class="line">fs.<span class="title function_">copySync</span>(tmpdir.<span class="property">path</span>, <span class="string">`<span class="subst">$&#123;process.cwd()&#125;</span>/packages/<span class="subst">$&#123;locPath&#125;</span>`</span>);</span><br><span class="line"><span class="comment">// ...</span></span><br></pre></td></tr></table></figure><p>折腾这一顿，让我们来看下成果吧：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4ba477bcbd8b4ef6b880c7e8e5a095bc~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><h3 id="Github-CODEOWENERS"><a href="#Github-CODEOWENERS" class="headerlink" title="Github CODEOWENERS"></a>Github CODEOWENERS</h3><p>大型的开源项目最难的不是技术问题，技术大咖永远不会缺。最难的其实是协作和后期维护。试想一下一个成百上千人参与的项目当有新的 pr 时，正常人根本无力去快速检索出需要谁去 review 代码。我们的 vant-react-native 由于是将每个组件单独发包维护，当参与的小伙伴多了也会产生这个困扰。</p><p>而 GitHub CODEOWNERS（代码所有者）就是为了解决这个问题的，在 5000+ 贡献者参与的 <a href="https://github.com/DefinitelyTyped/DefinitelyTyped">DefinitelyTyped</a> 项目中我们就可以看到它的身影。官方对代码所有者定义如下：</p><blockquote><p>你可以使用 CODEOWNERS 文件定义负责仓库代码的个人或团队。当有人修改代码并打开一个 pull request 时，将自动请求代码所有者进行审查。</p></blockquote><p>CODEOWNERS 文件使用遵循 gitignore 文件中所用大多数规则的模式，CODEOWNERS 文件位置一般位于 <code>.github/</code> 目录下。</p><p>在 vant-react-native，紫升是仓库的最终负责人，所以是期望每个 pr 都可以分配给自己审查一下的。那么我们这就来实验一下吧，新建一个 <code>.github/CODEOWNERS</code> 文件并写入以下内容：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"># This is a comment.</span><br><span class="line"># Each line is a file pattern followed by one or more owners.</span><br><span class="line"></span><br><span class="line"># These owners will be the default owners for everything in</span><br><span class="line"># the repo. Unless a later match takes precedence,</span><br><span class="line"># @youngjuning will be requested for review when someone opens a pull request.</span><br><span class="line">*       @youngjuning</span><br><span class="line"></span><br><span class="line"># In this example, @doctocat owns any files in the build/logs</span><br><span class="line"># directory at the root of the repository and any of its</span><br><span class="line"># subdirectories.</span><br><span class="line">/packages/ @luozhu1994</span><br></pre></td></tr></table></figure><p>一般如果文件具有代码所有者，则在打开拉取请求之前可以看到代码所有者是谁。在仓库中，你可以找到文件并悬停于一个锁图标上，悬浮之后会告诉你该文件所有者是谁：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/726bd5144a104902ae27ad31e1d46f93~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>然后我们提交一个 pr 看看效果：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bf4cb8b7ef5c41989660e735e621fc4e~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><h3 id="NPM-发包自动化"><a href="#NPM-发包自动化" class="headerlink" title="NPM 发包自动化"></a>NPM 发包自动化</h3><p>发包权限一般只有仓库所有者一个人拥有，但是 owner 同时维护好几个 NPM 账号，或者是 owner 忽然很忙将发布权限交给其他人管理员但是不便告知 NPM 账号该怎么办呢？答案是将 NPM 发包 CD（持续部署）化，公司一般会基于 Gitlab 或自建平台实现该功能。作为开源项目，我们当然是使用 GitHub Action。</p><p>正常的单包项目，使用 <a href="https://github.com/JS-DevTools/npm-publish">npm-publish</a> 或 <a href="https://github.com/pascalgn/npm-publish-action">npm-publish-action</a> 这两个 GitHub Action，这并没有好讲的。但是基于 lerna 的多包单体仓库并没有现成的插件可以用，照例，我们来看下自己实现的步骤：</p><ol><li>判断 commit message 是否以 <code>chore(release):</code> 开头<blockquote><p>通过 GitHub Action <code>startsWith(github.event.head_commit.message, &#39;chore(release):&#39;)</code> 实现</p></blockquote></li><li>通过 NPM publish token 认证登录<blockquote><p>通过 <code>npm config set //registry.npmjs.org/:_authToken=$&#123;&#123; secrets.NPM_TOKEN &#125;&#125;</code> 认证</p></blockquote></li><li>执行 <code>lerna publish from-package --yes</code> 发布<blockquote><p>需要本地先执行 <code>lerna version</code> 系列命令提升版本</p></blockquote></li></ol><p>完整 GitHub Action 实现如下：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">npm-publish</span></span><br><span class="line"></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line">  <span class="attr">push:</span></span><br><span class="line">    <span class="attr">branches:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">main</span></span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">npm-publish:</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line">    <span class="attr">if:</span> <span class="string">startsWith(github.event.head_commit.message,</span> <span class="string">&#x27;chore(release):&#x27;</span><span class="string">)</span></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">actions/checkout@v2</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">c-hive/gha-yarn-cache@v2</span> <span class="comment"># 缓存 node_modules 加快构建速度</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Install</span> <span class="string">Packages</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">yarn</span> <span class="string">install</span> <span class="string">--registry=https://registry.npmjs.org/</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Authenticate</span> <span class="string">with</span> <span class="string">Registry</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">          npm config set //registry.npmjs.org/:_authToken=$&#123;NPM_TOKEN&#125;</span></span><br><span class="line"><span class="string"></span>        <span class="attr">env:</span></span><br><span class="line">          <span class="attr">NPM_TOKEN:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.NPM_TOKEN</span> <span class="string">&#125;&#125;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Publish</span> <span class="string">package</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">lerna</span> <span class="string">publish</span> <span class="string">from-package</span> <span class="string">--yes</span></span><br></pre></td></tr></table></figure><p>为了在发布后及时获取通知，紫升使用了 <code>peter-evans/commit-comment</code> 插件在发布失败或成功后对相应 commit 进行评论，这样我们就可以收到邮件和站内通知。</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Create</span> <span class="string">commit</span> <span class="string">comment</span> <span class="string">after</span> <span class="string">publish</span> <span class="string">successfully</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">$&#123;&#123;</span> <span class="string">success()</span> <span class="string">&#125;&#125;</span></span><br><span class="line">  <span class="attr">uses:</span> <span class="string">peter-evans/commit-comment@v1</span></span><br><span class="line">  <span class="attr">with:</span></span><br><span class="line">    <span class="attr">body:</span> <span class="string">|</span></span><br><span class="line"><span class="string">      Hello Dear @youngjuning. This commit has been publish to NPM successfully.</span></span><br><span class="line"><span class="string">      &gt; Created by [commit-comment][1]</span></span><br><span class="line"><span class="string"></span></span><br><span class="line">      [<span class="number">1</span>]<span class="string">:</span> <span class="string">https://github.com/peter-evans/commit-comment</span></span><br><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Create</span> <span class="string">commit</span> <span class="string">comment</span> <span class="string">after</span> <span class="string">publish</span> <span class="string">unsuccessfully</span></span><br><span class="line">  <span class="attr">if:</span> <span class="string">$&#123;&#123;</span> <span class="string">failure()</span> <span class="string">&#125;&#125;</span></span><br><span class="line">  <span class="attr">uses:</span> <span class="string">peter-evans/commit-comment@v1</span></span><br><span class="line">  <span class="attr">with:</span></span><br><span class="line">    <span class="attr">body:</span> <span class="string">|</span></span><br><span class="line"><span class="string">      Hello Dear @youngjuning. This commit has been publish to NPM unsuccessfully.</span></span><br><span class="line"><span class="string">      &gt; Created by [commit-comment][1]</span></span><br><span class="line"><span class="string"></span></span><br><span class="line">      [<span class="number">1</span>]<span class="string">:</span> <span class="string">https://github.com/peter-evans/commit-comment</span></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/f4ea430d1d6d/</id>
    <link href="https://blog.zisheng.pro/posts/f4ea430d1d6d/"/>
    <published>2023-03-03T02:36:18.000Z</published>
    <summary>
      <![CDATA[<blockquote>
<p>⚠️本文为掘金社区首发签约文章，未获授权禁止转载</p>
</blockquote>
<blockquote>
<p>大家好，我是<a href="https://blog.zisheng.pro/">紫升🎋</a>，一只住在杭城的木系前端🧚�]]>
    </summary>
    <title>前端组件化实战之 Button</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="VS Code" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/VS-Code/"/>
    <category term="掘金" scheme="https://blog.zisheng.pro/tags/%E6%8E%98%E9%87%91/"/>
    <category term="VS Code" scheme="https://blog.zisheng.pro/tags/VS-Code/"/>
    <category term="Webview" scheme="https://blog.zisheng.pro/tags/Webview/"/>
    <category term="扩展" scheme="https://blog.zisheng.pro/tags/%E6%89%A9%E5%B1%95/"/>
    <content>
      <![CDATA[<blockquote><p>⚠️本文为掘金社区首发签约文章，未获授权禁止转载</p></blockquote><blockquote><p>温馨提示：结合本文配套<a href="https://github.com/youngjuning/juejin-me">源码</a>阅读体验更佳！</p></blockquote><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在团队降本提效的基建中，紫升开发了一款 vscode 插件，第一版我使用的是 vscode 内置 UI，虽说也能用，但是用户体验欠佳。由于 vscode 内置 UI 不够灵活，一番调研后我决定使用 webview 重构。</p><p>开发过 vscode 插件的同学可能对插件开发知识点多、文档阅读困难、参考资料少有所体会。基于 webview 开发插件更是如此，寻遍网络，虽然有优秀的项目，但却没有完整且优秀的教程。为了修炼 vscode 开发灵力，不妨和紫升一起挑战从零到一开发一款基于 webview 的 vscode 插件。</p><h2 id="Hello-vscode"><a href="#Hello-vscode" class="headerlink" title="Hello vscode"></a>Hello vscode</h2><p>英雄多起于市井，高楼皆起于平地。再伟大的软件也都是从 Hello World 开始的，本章尽量用最简洁的语言描述一个 vscode 插件 Hello World 的诞生。</p><h3 id="初始化项目"><a href="#初始化项目" class="headerlink" title="初始化项目"></a>初始化项目</h3><p>安装 <a href="http://yeoman.io/">Yeoman</a> 和 <a href="https://www.npmjs.com/package/generator-code">VS Code Extension Generator</a>：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ npm install -g yo generator-code</span><br></pre></td></tr></table></figure><p>这个脚手架会生成一个可以立马开发的项目。运行生成器，然后填好下列字段：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">$ yo code</span><br><span class="line"><span class="comment">#     _-----_     ╭──────────────────────────╮</span></span><br><span class="line"><span class="comment">#    |       |    │   Welcome to the Visual  │</span></span><br><span class="line"><span class="comment">#    |--(o)--|    │   Studio Code Extension  │</span></span><br><span class="line"><span class="comment">#   `---------´   │        generator!        │</span></span><br><span class="line"><span class="comment">#    ( _´U`_ )    ╰──────────────────────────╯</span></span><br><span class="line"><span class="comment">#    /___A___\   /</span></span><br><span class="line"><span class="comment">#     |  ~  |</span></span><br><span class="line"><span class="comment">#   __&#x27;.___.&#x27;__</span></span><br><span class="line"><span class="comment"># ´   `  |° ´ Y `</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># ? What type of extension do you want to create? New Extension (TypeScript)</span></span><br><span class="line"><span class="comment"># ? What&#x27;s the name of your extension? Juejin Posts</span></span><br><span class="line"><span class="comment"># ? What&#x27;s the identifier of your extension? juejin-posts</span></span><br><span class="line"><span class="comment"># ? What&#x27;s the description of your extension? 掘金文章管理</span></span><br><span class="line"><span class="comment"># ? Initialize a git repository? Yes</span></span><br><span class="line"><span class="comment"># ? Bundle the source code with webpack? No</span></span><br><span class="line"><span class="comment"># ? Which package manager to use? yarn</span></span><br><span class="line"></span><br><span class="line">$ code ./juejin-posts</span><br></pre></td></tr></table></figure><blockquote><p>提交记录：<a href="https://is.gd/IaJdlW">hello world</a></p></blockquote><h3 id="代码规范"><a href="#代码规范" class="headerlink" title="代码规范"></a>代码规范</h3><p>默认的脚手架生成的也有 ESLint 配置，但是 Editor、Prettier 的配置都没有，并且 ESLint 配置也不符合我的习惯。紫升关于前端工程化的包都在 <a href="https://github.com/youngjuning/luozhu">youngjuning&#x2F;luozhu</a>， ESlint 配置的包是 <code>@luozhu/eslint-config-*</code>。由于我们开发插件使用的是 Typescript，所以我们选择 <code>@luozhu/eslint-config-typescript</code>。</p><p><strong>安装依赖：</strong></p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add @luozhu/eslint-config-typescript @luozhu/prettier-config prettier -D</span><br></pre></td></tr></table></figure><p><strong>具体配置：</strong></p><p>配置涉及文件较多，请参考 <a href="https://github.com/youngjuning/luozhu#coding-style">coding-style</a>，不关心的同学也可以直接略过。</p><p><strong>提交检测：</strong></p><p>安装依赖：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add lint-staged yorkie -D</span><br></pre></td></tr></table></figure><p>修改配置：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// package.json</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;gitHooks&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;pre-commit&quot;</span><span class="punctuation">:</span> <span class="string">&quot;lint-staged&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;lint-staged&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;**/*.&#123;js,jsx,ts,tsx&#125;&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;eslint --fix&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;**/*.&#123;md,json&#125;&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;prettier --write&quot;</span><span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><strong>eslint –fix：</strong></p><p>修改完配置之后需要执行 fix 对所有文件格式化一次。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn lint --fix</span><br></pre></td></tr></table></figure><blockquote><p>提交记录：<a href="https://is.gd/xzFAVE">chore: code style config</a></p></blockquote><h3 id="约定式提交"><a href="#约定式提交" class="headerlink" title="约定式提交"></a>约定式提交</h3><p>约定式提交我使用的是渐进式脚手架 <code>@luozhu/create-commitlint</code>，在项目中执行 <code>npx @luozhu/create-commitlint</code> 即可使项目符合规范化提交的配置。对规范化提交不了解的同学，强烈建议读一下 <a href="https://juejin.cn/post/6877462747631026190/">一文搞定 Conventional Commits </a>。</p><blockquote><p>提交记录：<a href="https://is.gd/Ld142y">chore: npx @luozhu&#x2F;create-commitlint</a></p></blockquote><h3 id="调试"><a href="#调试" class="headerlink" title="调试"></a>调试</h3><p>按下 <code>F5</code> 开启调试会出现[扩展开发宿主]窗口，然后按 <code>Command+Shift+P</code> 组件键输入 <code>Hello World</code> 命令。如下图所示 vscode 弹出了 <code>Hello World from Juejin Posts!</code> 的提示。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bc26d7d429f84564b88b55e9df60fcd7~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>同时我们的开发窗口中，会出现一个 watch 任务的终端：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/75a15d0bd2cc4096af1a3f76173cbbe1~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>开发窗口的调试控制台会输出插件运行日志（忽略红色的警告）：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c31068ea0bca4d4793e35bdb16d70c0a~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>调试执行的任务是在 <code>.vscode/tasks.json</code> 中配置的：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// See https://go.microsoft.com/fwlink/?LinkId=733558</span></span><br><span class="line"><span class="comment">// for the documentation about the tasks.json format</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2.0.0&quot;</span><span class="punctuation">,</span> <span class="comment">// 配置的版本号。</span></span><br><span class="line"><span class="attr">&quot;tasks&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span> <span class="comment">// 任务配置。通常是外部任务运行程序中已定义任务的扩充。</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"><span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;npm&quot;</span><span class="punctuation">,</span> <span class="comment">// 要自定义的任务类型。</span></span><br><span class="line"><span class="attr">&quot;script&quot;</span><span class="punctuation">:</span> <span class="string">&quot;watch&quot;</span><span class="punctuation">,</span> <span class="comment">// 要自定义的 npm 脚本。</span></span><br><span class="line"><span class="attr">&quot;problemMatcher&quot;</span><span class="punctuation">:</span> <span class="string">&quot;$tsc-watch&quot;</span><span class="punctuation">,</span> <span class="comment">// 要使用的问题匹配程序。可以是一个字符串或一个问题匹配程序定义，也可以是一个字符串数组和多个问题匹配程序。</span></span><br><span class="line"><span class="attr">&quot;isBackground&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span> <span class="comment">// 执行的任务是否保持活动状态并在后台运行。</span></span><br><span class="line"><span class="attr">&quot;presentation&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="comment">// 配置用于显示任务输出并读取其输入的面板。</span></span><br><span class="line"><span class="attr">&quot;reveal&quot;</span><span class="punctuation">:</span> <span class="string">&quot;never&quot;</span> <span class="comment">// 控制运行任务的终端是否显示。可按选项 &quot;revealProblems&quot; 进行替代。默认设置为“始终”。</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;group&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="comment">// 定义此任务属于的执行组。它支持 &quot;build&quot; 以将其添加到生成组，也支持 &quot;test&quot; 以将其添加到测试组。</span></span><br><span class="line"><span class="attr">&quot;kind&quot;</span><span class="punctuation">:</span> <span class="string">&quot;build&quot;</span><span class="punctuation">,</span> <span class="comment">// 任务的执行组。</span></span><br><span class="line"><span class="attr">&quot;isDefault&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span> <span class="comment">// 定义此任务是否为组中的默认任务。</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="打包"><a href="#打包" class="headerlink" title="打包"></a>打包</h2><p>我们的插件开发完成前，想要分享给小伙伴体验可以吗？答案是肯定的，vscode 为我们提供了 <a href="https://github.com/microsoft/vscode-vsce">vsce</a> 实现这个需求，我们将 vsce 模块安装到全局，然后使用 <code>vsce package</code> 命令尝试打包：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ vsce package</span><br><span class="line"> ERROR  Missing publisher name. Learn more: https://code.visualstudio.com/api/working-with-extensions/publishing-extension<span class="comment">#publishing-extensions</span></span><br></pre></td></tr></table></figure><p>啊，咋还报错了？<code>publisher</code> 是啥？？一脸懵逼。不慌，按<a href="https://code.visualstudio.com/api/working-with-extensions/publishing-extension#create-a-publisher">链接</a> 我知道了 publisher 是一个可以将扩展发布到Visual Studio Code Marketplace 的身份。每个扩展都需要在其 <code>package.json</code> 文件中包含一个发布者名称。如果注册发布者我们后面详说，这里我们把 <code>publisher</code> 设置为 <code>luozhu</code>。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ vsce package</span><br><span class="line"> INFO  Detected presence of yarn.lock. Using <span class="string">&#x27;yarn&#x27;</span> instead of <span class="string">&#x27;npm&#x27;</span> (to override this pass <span class="string">&#x27;--no-yarn&#x27;</span> on the <span class="built_in">command</span> line).</span><br><span class="line"> ERROR  Make sure to edit the README.md file before you package or publish your extension.</span><br></pre></td></tr></table></figure><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cf561381232b4690a5d7cd0378c267d7~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>额，裂开，这咋还报错，假装淡定，读一下提示原来是要我们编辑一下 README.md，没错，vscode 模板里有初始的 README，我们需要编辑一下才可以打包。修改后再次尝试 <code>vsce package</code>：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/acfe2e957c9545578c7b6990371a3f0b~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>终于，打包成功！为了追求完美，最后我们再来做一些优化工作：</p><ol><li>执行 <code>vsce package</code> 的时候加上 <code>--no-yarn</code></li><li>在 <em>package.json</em> 中加上 <code>repository</code> 字段即可看不到任何警告。</li><li>为了便捷，我们将 vsce 安装到项目中，然后把 <code>vsce package --no-yarn</code> 添加到 npm scripts 中。</li><li><em>package.json</em> 加上 <code>license</code> 字段。</li></ol><p>然后再次尝试 <code>yarn package</code> 就完美了：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4a9a251834b945c8a882039020f56e4f~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><blockquote><p>提示：vsce package 会先执行 <code>vscode:prepublish</code> 这个预发布脚本去编译项目。</p></blockquote><blockquote><p>提交记录：<a href="https://is.gd/ZCp4qU">chore: config vsce package</a></p></blockquote><h3 id="打包原理"><a href="#打包原理" class="headerlink" title="打包原理"></a>打包原理</h3><p>如过你也跟着一路敲到了这里，此时你会在项目根目录发现 <code>vsix</code> 结尾的文件：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f2c0846bd66747ceab0c09524a209b28~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>这就是 vscode 插件的安装包，我们先不急着安装，先一起来看一下这个文件是个什么东西。尝试用归档工具解压后得到如下目录文件夹：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/182c365a0690467cb0bd8b0d1490c175~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>我们可以看到编译后的文件夹 <code>out</code> 和其他一些文件是被直接压缩进安装包的，聪明的你肯定发现了 <code>.cz-config.js</code>、<code>.prettierrc.js</code> 和 <code>commitlint.config.js</code> 这种开发时文件也被压缩了，运行插件完全用不到，这明显不合理。其实和其他插件体系一样，vscode 也提供了 <code>.vscodeignore</code> 来实现打包忽略配置，我们将以上无关文件忽略重新打包即可。</p><p>原理就这？不存在的，我们打开 <code>extension.js</code> 会发现引用了 <code>vscode</code> 这个包：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bb42881e49a94456aeac417f6b5cba71~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>但是我们的安装包中并没有 <em>node_modules</em>，那么 vscode 这个包存在在哪里呢？我猜的是挂在 node 环境上了，读了<a href="https://is.gd/33GTcH">源码</a>后我发现我竟然是对的：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5e796fcf81b64fd7a18d9c3e36fbefdf~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>vscode 实现了拦截器在加载 Node 环境的时候将 vscode 给添加到了内置包中，这样的好处是减小插件的体积。</p><p>那么我们如果使用三方插件呢？以常用的 lodash 为例，安装 lodash 之后重新打包：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">$ yarn package</span><br><span class="line">yarn run v1.22.10</span><br><span class="line">$ vsce package --no-yarn</span><br><span class="line">Executing prepublish script <span class="string">&#x27;npm run vscode:prepublish&#x27;</span>...</span><br><span class="line"></span><br><span class="line">&gt; juejin-posts@0.0.1 vscode:prepublish</span><br><span class="line">&gt; yarn run compile</span><br><span class="line"></span><br><span class="line">$ tsc -p ./</span><br><span class="line">This extension consists of 1060 files, out of <span class="built_in">which</span> 1049 are JavaScript files. For performance reasons, you should bundle your extension: https://aka.ms/vscode-bundle-extension . You should also exclude unnecessary files by adding them to your .vscodeignore: https://aka.ms/vscode-vscodeignore</span><br><span class="line"> DONE  Packaged: /Users/luozhu/Desktop/playground/juejin-posts/juejin-posts-0.0.1.vsix (1060 files, 644.72KB)</span><br><span class="line">✨  Done <span class="keyword">in</span> 5.54s.</span><br></pre></td></tr></table></figure><p>这个时候提示我们有 1000 多个文件，大概率 <em>node_modules</em> 文件夹被打包了，我们来解压下见证一下：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/917b7742991640c9a0b03314cef24ce6~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>不出所料，vscode 默认的打包方式就是简单的编译拷贝，通过忽略文件减小体积也是杯水车薪。而且 vscode 扩展的规模往往增长很快。它们是在多个源文件中编写的，并依赖于 npm 的模块。分解和重用是开发的最佳实践，但在安装和运行扩展时，它们是有代价的。加载 100 个小文件要比加载一个大文件慢得多。这就是我们推荐捆绑的原因。捆绑是将多个小的源文件合并成一个文件的过程。</p><p>在 JavaScript 中，有不同的打包工具可以用，流行的有 rollup.js、Parcel、esbuild 和 webpack，官方脚手架默认只能选 webpack，我们这里推荐直接使用更快更强的 esbuild。</p><blockquote><p>提交记录：<a href="https://is.gd/ZCp4qU">chore: ignore config file when package</a>、<a href="https://is.gd/ggpQmv">chore: add esModuleInterop to tsconfig</a></p></blockquote><h3 id="使用-esbuild-优化打包"><a href="#使用-esbuild-优化打包" class="headerlink" title="使用 esbuild 优化打包"></a>使用 esbuild 优化打包</h3><p><strong>安装依赖：</strong></p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add -D esbuild</span><br></pre></td></tr></table></figure><p><strong>npm scripts：</strong></p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">&quot;scripts&quot;: &#123;</span><br><span class="line"><span class="deletion">-    &quot;vscode:prepublish&quot;: &quot;yarn run compile&quot;,</span></span><br><span class="line"><span class="deletion">-    &quot;compile&quot;: &quot;tsc -p ./&quot;,</span></span><br><span class="line"><span class="deletion">-    &quot;watch&quot;: &quot;tsc -watch -p ./&quot;,</span></span><br><span class="line"><span class="deletion">-    &quot;pretest&quot;: &quot;yarn run compile &amp;&amp; yarn run lint&quot;,</span></span><br><span class="line"><span class="addition">+    &quot;vscode:prepublish&quot;: &quot;yarn esbuild-base --minify&quot;,</span></span><br><span class="line"><span class="addition">+    &quot;esbuild-base&quot;: &quot;esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node&quot;,</span></span><br><span class="line"><span class="addition">+    &quot;esbuild&quot;: &quot;yarn esbuild-base --sourcemap&quot;,</span></span><br><span class="line"><span class="addition">+    &quot;esbuild-watch&quot;: &quot;yarn esbuild-base --sourcemap --watch&quot;,</span></span><br><span class="line"><span class="addition">+    &quot;test-compile&quot;: &quot;tsc -p ./&quot;,</span></span><br><span class="line"><span class="addition">+    &quot;pretest&quot;: &quot;yarn test-compile &amp;&amp; yarn lint&quot;,</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>注意：由于 watch 改成了 esbuild-watch，所以 <em>.vscode&#x2F;tasks.json</em> 中的 scripts 子段也需要做相应修改。</p></blockquote><p><strong>vscode tasks：</strong></p><p>理论上我们把打包命令改成 esbuild 之后，应该将 vscode 任务中的问题匹配程序设置为 <code>$esbuild-watch</code>，但是 vscode 会提示我们无法识别的问题匹配程序：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4b51c79ee1344b6f92a3f2efaf9ef7aa~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>尝试搜索扩展，果然有一个 esbuild Problem Matchers 插件，我们将其安装并添加 <code>&quot;connor4312.esbuild-problem-matchers&quot;</code> 到 <em>.vscode&#x2F;extensions.json</em> 文件的 <code>recommendations</code> 中。</p><p><strong>忽略文件：</strong></p><p>我们使用 esbuild 打包后会将使用到的代码都打包进 <code>out/extension.js</code>，但是 vsce 的打包机制是不管你有没有用到都会把 <code>dependencies</code> 中的包打进安装包中，所以我们需要将 <em><em>node_modules</em></em> 忽略掉。</p><p><strong>成果展示：</strong></p><p>从图中我们可以看到，安装包的体积大大减小了。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ba86286be7fc41f4b8325a3e0be0d2d2~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><blockquote><p>提交记录：<a href="https://is.gd/F12xNk">chore: config esbuild</a></p></blockquote><h2 id="集成-umijs"><a href="#集成-umijs" class="headerlink" title="集成 umijs"></a>集成 umijs</h2><h3 id="初始化-umi-项目"><a href="#初始化-umi-项目" class="headerlink" title="初始化 umi 项目"></a>初始化 umi 项目</h3><p>使用 umi 脚手架在根目录新建一个 <em>web</em> 目录。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">mkdir</span> web &amp;&amp; <span class="built_in">cd</span> web</span><br></pre></td></tr></table></figure><p>通过官方工具创建项目：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn create @umijs/umi-app</span><br></pre></td></tr></table></figure><p>修改 <em>.umirc.ts</em> 配置：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; defineConfig, <span class="title class_">IConfig</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;umi&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title function_">defineConfig</span>(&#123;</span><br><span class="line">  <span class="attr">nodeModulesTransform</span>: &#123;</span><br><span class="line">    <span class="attr">type</span>: <span class="string">&#x27;none&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">routes</span>: [&#123; <span class="attr">path</span>: <span class="string">&#x27;/&#x27;</span>, <span class="attr">component</span>: <span class="string">&#x27;@/pages/index&#x27;</span> &#125;],</span><br><span class="line">  <span class="attr">fastRefresh</span>: &#123;&#125;, <span class="comment">// 开发时可以保持组件状态，同时编辑提供即时反馈。</span></span><br><span class="line">  <span class="attr">history</span>: &#123;</span><br><span class="line">    <span class="attr">type</span>: <span class="string">&#x27;memory&#x27;</span>, <span class="comment">// 默认的类型是 `browser`，但是由于 vscode webview 环境不存在浏览器路由，改成 `memory` 和 `hash` 都可以</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">devServer</span>: &#123;</span><br><span class="line">    <span class="comment">// 需要在 dev 时写文件到输出目录，这样保证开发阶段有 js/css 文件</span></span><br><span class="line">    <span class="attr">writeToDisk</span>: <span class="function"><span class="params">filePath</span> =&gt;</span></span><br><span class="line">      [<span class="string">&#x27;umi.js&#x27;</span>, <span class="string">&#x27;umi.css&#x27;</span>].<span class="title function_">some</span>(<span class="function"><span class="params">name</span> =&gt;</span> filePath.<span class="title function_">endsWith</span>(name)),</span><br><span class="line">  &#125;,</span><br><span class="line">&#125; <span class="keyword">as</span> <span class="title class_">IConfig</span>);</span><br></pre></td></tr></table></figure><p>修改 <code>package.json</code> 加入 <code>name</code>、<code>version</code>、<code>description</code>：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;web&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;0.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;web for juejin-posts&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="忽略文件"><a href="#忽略文件" class="headerlink" title="忽略文件"></a>忽略文件</h3><p><strong>.gitignore：</strong></p><p>将 vscode 扩展和 umijs 脚手架生成的 gitignore 合并为一下内容：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"># See https://help.github.com/articles/ignoring-files/ for more about ignoring files.</span><br><span class="line"></span><br><span class="line"># vscode</span><br><span class="line">.vscode-test/</span><br><span class="line">*.vsix</span><br><span class="line"></span><br><span class="line"># dependencies</span><br><span class="line">node_modules</span><br><span class="line">npm-debug.log</span><br><span class="line">yarn-error.log</span><br><span class="line">package-lock.json</span><br><span class="line"></span><br><span class="line"># production</span><br><span class="line">out</span><br><span class="line">dist</span><br><span class="line"></span><br><span class="line"># misc</span><br><span class="line">.DS_Store</span><br><span class="line"></span><br><span class="line"># umi</span><br><span class="line">**/src/.umi</span><br><span class="line">**/src/.umi-production</span><br><span class="line">**/src/.umi-test</span><br><span class="line">**/.env.local</span><br><span class="line">web/yarn.lock</span><br></pre></td></tr></table></figure><p><strong>.vscodeignore：</strong></p><p>由于 vscode 打包的时候只需要获取 umijs 打包后的产物，所有加入 <code>web/**</code> 和 <code>!web/dist/**</code> 将无用的文件忽略掉。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">.vscode/**</span><br><span class="line">.vscode-test/**</span><br><span class="line">out/test/**</span><br><span class="line"></span><br><span class="line">src/**</span><br><span class="line">.gitignore</span><br><span class="line">.yarnrc</span><br><span class="line">vsc-extension-quickstart.md</span><br><span class="line">**/tsconfig.json</span><br><span class="line">**/*.map</span><br><span class="line">**/*.ts</span><br><span class="line"></span><br><span class="line">.cz-config.js</span><br><span class="line">.prettierrc.js</span><br><span class="line">.commitlintrc.js</span><br><span class="line">**/node_modules/**</span><br><span class="line">yarn-error.log</span><br><span class="line">web/**</span><br><span class="line">!web/dist/**</span><br></pre></td></tr></table></figure><h3 id="yarn-workspace"><a href="#yarn-workspace" class="headerlink" title="yarn workspace"></a>yarn workspace</h3><p>由于我们的项目是 vscode 扩展和 web 项目混合的项目。为了方便管理脚本和依赖，我们引入了 <code>yarn workspace</code> 来管理项目。在根目录的 <em>package.json</em> 中加入以下配置即可：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;private&quot;</span><span class="punctuation">:</span> <span class="string">&quot;true&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;workspaces&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;web&quot;</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="调试-1"><a href="#调试-1" class="headerlink" title="调试"></a>调试</h3><p>由于我们的 web 项目也需要编译，所以我们需要修改一下 vscode <code>launch.json</code> 加入 web 项目的编译任务。配置参考了 <a href="http://tny.im/bOqQT">appworks</a>。</p><p>首先在根目录的 <code>package.json</code> 的 scripts 中添加:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;web-build&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn workspace web run build&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;web-watch&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn workspace web run start&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>然后修改 <em>.vscode&#x2F;launch.json</em> 配置为：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// A launch configuration that compiles the extension and then opens it inside a new window</span></span><br><span class="line"><span class="comment">// Use IntelliSense to learn about possible attributes.</span></span><br><span class="line"><span class="comment">// Hover to view descriptions of existing attributes.</span></span><br><span class="line"><span class="comment">// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;0.2.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;compounds&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="comment">// 复合列表。每个复合可引用多个配置，这些配置将一起启动。</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Debug Extension&quot;</span><span class="punctuation">,</span> <span class="comment">// 复合的名称。在启动配置下拉菜单中显示。</span></span><br><span class="line">      <span class="attr">&quot;configurations&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="comment">// 将作为此复合的一部分启动的配置名称。</span></span><br><span class="line">        <span class="string">&quot;Run Extension&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;Watch Webview&quot;</span></span><br><span class="line">      <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;presentation&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;order&quot;</span><span class="punctuation">:</span> <span class="number">0</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;configurations&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Watch Webview&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;request&quot;</span><span class="punctuation">:</span> <span class="string">&quot;attach&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;node&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;preLaunchTask&quot;</span><span class="punctuation">:</span> <span class="string">&quot;npm: web-watch&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Run Extension&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;extensionHost&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;request&quot;</span><span class="punctuation">:</span> <span class="string">&quot;launch&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;args&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;--extensionDevelopmentPath=$&#123;workspaceFolder&#125;&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;outFiles&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;$&#123;workspaceFolder&#125;/out/**/*.js&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;preLaunchTask&quot;</span><span class="punctuation">:</span> <span class="string">&quot;$&#123;defaultBuildTask&#125;&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>完成后进入 VS Code，按下<code>F5</code>，你会立即看到一个<strong>插件发开主机</strong>窗口，其中就运行着插件。这时候运行你会发现控制台报一下错误 ❌：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">error TS6059: File &#x27;/Users/luozhu/Desktop/github/juejin-posts/web/src/pages/index.tsx&#x27; is not under &#x27;rootDir&#x27; &#x27;/Users/luozhu/Desktop/github/juejin-posts/src&#x27;. &#x27;rootDir&#x27; is expected to contain all source files.</span><br><span class="line">  The file is in the program because:</span><br><span class="line">    Matched by include pattern &#x27;**/*&#x27; in &#x27;/Users/luozhu/Desktop/github/juejin-posts/tsconfig.json&#x27;</span><br></pre></td></tr></table></figure><p>原因是因为 umi 的约定的项目结构和 vscode extension 都包含 <em>src</em> 目录。由于 vscode 插件和 umi 的编译是分开的，我们在根目录的 <em>tsconfig.json</em> 中将 <em>web</em> 目录忽略即可：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;exclude&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;web&quot;</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>现在，你可以按下 <code>F5</code> 看到<strong>插件发开主机</strong>窗口的同时还会看到两个调试任务：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d4b3b6f2522643fb9bc574e2f9982775~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><blockquote><p>注意📢：请选择 Debug Extension 调试任务而不是 Run Extension</p></blockquote><h3 id="其他优化工作"><a href="#其他优化工作" class="headerlink" title="其他优化工作"></a>其他优化工作</h3><ol><li>由于基于 yarn workspace，我们把公用的依赖合并</li><li>合并 Eslint 配置并使用 <code>@luozhu/eslint-config-react-typescrip</code></li><li>合并 Editorconfig 和 Prettier 配置</li><li>添加 <code>prestart</code> 和 <code>prebuild</code> script</li><li>设置 <code>HTML=none umi build</code></li></ol><blockquote><p>提交记录：<a href="https://is.gd/bt3WHr">chore: config umijs</a></p></blockquote><h2 id="vscode-插件开发核心概念"><a href="#vscode-插件开发核心概念" class="headerlink" title="vscode 插件开发核心概念"></a>vscode 插件开发核心概念</h2><p>在开始 webview 能力开发之前，我们有必要了解一下 vscode 插件开发的核心概念。为了有个全局的理解，我们先来看下我们现在项目的主要目录结构：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">├── CHANGELOG.md <span class="comment"># 基于 standard-version 生成的更新日志文件</span></span><br><span class="line">├── README.md</span><br><span class="line">├── package.json <span class="comment"># vscode 包配置文件，诸如插件 LOGO、名字、描述、注册激活事件</span></span><br><span class="line">├── src</span><br><span class="line">│   └── extension.ts <span class="comment"># 插件入口文件，暴露 activate 方法用于注册命令和初始化一些配置，暴露 deactivate 方法用于插件关闭前执行清理工作</span></span><br><span class="line">├── tsconfig.json <span class="comment"># vscode 的编译配置</span></span><br><span class="line">├── web <span class="comment"># 基于 umi 的 web，也是我们后边 webview 要承载的内容</span></span><br><span class="line">└── yarn.lock</span><br></pre></td></tr></table></figure><p>从目录结构可以看出，关键的文件是 <code>package.json</code> 和 <code>extension.ts</code>，我们以 helloWorld 命令为例介绍下 vscode 插件的三个核心概念。</p><h3 id="1-激活事件"><a href="#1-激活事件" class="headerlink" title="1. 激活事件"></a>1. 激活事件</h3><p><strong>激活事件</strong>是在 <code>package.json</code> 中的 <code>activationEvents</code> 字段声明的一个 JSON 数组对象。为了注册 helloWorld 这个命令，第一步就是注册激活事件，激活事件类型有很多，注册命令的激活事件是 <code>onCommand</code>:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;activationEvents&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;onCommand:juejin-posts.helloWorld&quot;</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="2-发布内容配置"><a href="#2-发布内容配置" class="headerlink" title="2. 发布内容配置"></a>2. 发布内容配置</h3><p>发布内容配置（ 即 VS Code 为插件扩展提供的配置项）是 <code>package.json</code> 的 <code>contributes</code> 字段，你可以在其中注册各种配置项扩展 VS Code 的能力。上一步我们注册的 helloWorld 激活事件只是告诉了 vscode 可以通过 <code>juejin-posts.helloWorld</code> 命令触发。我们还需要再 <code>contributes.commands</code> 中注册我们的 <code>juejin-posts.helloWorld</code> 命令：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;contributes&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;commands&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;command&quot;</span><span class="punctuation">:</span> <span class="string">&quot;juejin-posts.helloWorld&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;title&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Hello World&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="3-VS-Code-API"><a href="#3-VS-Code-API" class="headerlink" title="3. VS Code API"></a>3. VS Code API</h3><p><strong>VS Code API</strong> 是 VS Code 提供给插件使用的一系列 Javascript API。通过前两个核心概念的能力，我们已经注册好了命令和事件，那么下一步必然就是注册事件回调。事件回调在 vscode 中是通过 <code>vscode.commands.registerCommand</code> 函数来注册的，下面 👇🏻 是我们在入口文件 <code>src/extension.ts</code> 中注册 <code>juejin-posts.helloWorld</code> 命令。</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// vscode 这个模块包含了 VS Code 扩展的 API</span></span><br><span class="line"><span class="keyword">import</span> vscode <span class="keyword">from</span> <span class="string">&#x27;vscode&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 这个方法当你的扩展激活时调用，扩展会在命令首次执行时激活</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">activate</span>(<span class="params">context: vscode.ExtensionContext</span>) &#123;</span><br><span class="line">  <span class="comment">// 当你的扩展被激活时，这行代码将只被执行一次</span></span><br><span class="line">  <span class="comment">//</span></span><br><span class="line">  <span class="comment">// 使用 console.log 输出日志信息或使用 console.error 输出错误信息。</span></span><br><span class="line">  <span class="comment">//</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Congratulations, your extension &quot;juejin-posts&quot; is now active!&#x27;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 入口命令已经在 package.json 文件中定义好了，现在调用 registerCommand 方法</span></span><br><span class="line">  <span class="comment">// registerCommand 中的参数必须与 package.json 中的 command 保持一致</span></span><br><span class="line">  <span class="keyword">const</span> disposable = vscode.<span class="property">commands</span>.<span class="title function_">registerCommand</span>(<span class="string">&#x27;juejin-posts.helloWorld&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 把你的代码写在这里，每次命令执行时都会调用这里的代码</span></span><br><span class="line">    <span class="comment">// 给用户显示一个消息提示</span></span><br><span class="line">    vscode.<span class="property">window</span>.<span class="title function_">showInformationMessage</span>(<span class="string">&#x27;Hello World from Juejin Posts!&#x27;</span>);</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  context.<span class="property">subscriptions</span>.<span class="title function_">push</span>(disposable);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 当你的扩展被停用时，这个方法被调用。</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">deactivate</span>(<span class="params"></span>) &#123;&#125;</span><br></pre></td></tr></table></figure><h2 id="集成-webview"><a href="#集成-webview" class="headerlink" title="集成 webview"></a>集成 webview</h2><h3 id="注册命令"><a href="#注册命令" class="headerlink" title="注册命令"></a>注册命令</h3><p>1、<em>package.json</em> 激活事件（<code>activationEvents</code>）中添加 <code>&quot;onCommand:juejin-posts.start&quot;</code></p><p>2、<em>package.json</em> 命令（<code>commands</code>）中添加：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;command&quot;</span><span class="punctuation">:</span> <span class="string">&quot;juejin-posts.start&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;title&quot;</span><span class="punctuation">:</span> <span class="string">&quot;start&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;category&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Juejin Posts&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>3、<em>src&#x2F;extension.ts</em> 中注册命令</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">context.<span class="property">subscriptions</span>.<span class="title function_">push</span>(</span><br><span class="line">  vscode.<span class="property">commands</span>.<span class="title function_">registerCommand</span>(<span class="string">&#x27;juejin-posts.start&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// Truth is endless. Keep coding...</span></span><br><span class="line">  &#125;)</span><br><span class="line">)</span><br></pre></td></tr></table></figure><h3 id="创建-webview-面板"><a href="#创建-webview-面板" class="headerlink" title="创建 webview 面板"></a>创建 webview 面板</h3><h4 id="创建一个空白的面板"><a href="#创建一个空白的面板" class="headerlink" title="创建一个空白的面板"></a>创建一个空白的面板</h4><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> vscode <span class="keyword">from</span> <span class="string">&#x27;vscode&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建并显示新的webview</span></span><br><span class="line"><span class="keyword">const</span> panel = vscode.<span class="property">window</span>.<span class="title function_">createWebviewPanel</span>(</span><br><span class="line">  <span class="string">&#x27;juejin-posts&#x27;</span>, <span class="comment">// 只供内部使用，这个 webview 的标识</span></span><br><span class="line">  <span class="string">&#x27;Juejin Posts&#x27;</span>, <span class="comment">// 给用户显示的面板标题</span></span><br><span class="line">  vscode.<span class="property">vscode</span>.<span class="property">ViewColumn</span>.<span class="property">One</span>, <span class="comment">// 给新的 webview 面板一个编辑器视图</span></span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">enableScripts</span>: <span class="literal">true</span>, <span class="comment">// 启用 javascript 脚本</span></span><br><span class="line">    <span class="attr">retainContextWhenHidden</span>: <span class="literal">true</span>, <span class="comment">// 隐藏时保留上下文</span></span><br><span class="line">  &#125; <span class="comment">// webview 面板的内容配置</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>我们使用了 <a href="https://vscode-api.js.org/modules/window.html#createWebviewPanel">window.createWebviewPanel</a> API 创建了一个 webview 面板，现在我们尝试运行 <code>juejin-posts.start</code> 就可以打开一个 webview 面板：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d85ad985232542f587376e5073feb1a1~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><h4 id="给面板设置内容"><a href="#给面板设置内容" class="headerlink" title="给面板设置内容"></a>给面板设置内容</h4><p>上面我们创建了一个空白的面板，那么我们如何给面板添加内容呢？我们可以使用 <code>panel.webview.html</code> 来设置 HTML 内容：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">getWebviewContent</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">`</span></span><br><span class="line"><span class="string">    &lt;!DOCTYPE html&gt;</span></span><br><span class="line"><span class="string">    &lt;html lang=&quot;en&quot;&gt;</span></span><br><span class="line"><span class="string">    &lt;head&gt;</span></span><br><span class="line"><span class="string">        &lt;meta charset=&quot;UTF-8&quot;&gt;</span></span><br><span class="line"><span class="string">        &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;</span></span><br><span class="line"><span class="string">        &lt;title&gt;Juejin Posts&lt;/title&gt;</span></span><br><span class="line"><span class="string">        &lt;style&gt;</span></span><br><span class="line"><span class="string">          html, body &#123;</span></span><br><span class="line"><span class="string">            padding: 0px;</span></span><br><span class="line"><span class="string">            height: 100vh;</span></span><br><span class="line"><span class="string">            position: relative;</span></span><br><span class="line"><span class="string">            margin: 0;</span></span><br><span class="line"><span class="string">            padding: 0;</span></span><br><span class="line"><span class="string">            overflow: hidden;</span></span><br><span class="line"><span class="string">          &#125;</span></span><br><span class="line"><span class="string">          #yoyo &#123;</span></span><br><span class="line"><span class="string">            position: absolute;</span></span><br><span class="line"><span class="string">            bottom: 50px;</span></span><br><span class="line"><span class="string">            right: -90px;</span></span><br><span class="line"><span class="string">            opacity: 0;</span></span><br><span class="line"><span class="string">            transition: .25s ease-in-out</span></span><br><span class="line"><span class="string">          &#125;</span></span><br><span class="line"><span class="string">          #yoyo:hover &#123;</span></span><br><span class="line"><span class="string">            opacity: 1;</span></span><br><span class="line"><span class="string">            right: 0;</span></span><br><span class="line"><span class="string">          &#125;</span></span><br><span class="line"><span class="string">        &lt;/style&gt;</span></span><br><span class="line"><span class="string">    &lt;/head&gt;</span></span><br><span class="line"><span class="string">    &lt;body&gt;</span></span><br><span class="line"><span class="string">      &lt;a href=&quot;https://juejin.cn&quot;&gt;&lt;img id=&quot;yoyo&quot; src=&quot;https://cdn.jsdelivr.net/gh/youngjuning/images/20210817163229.png&quot; width=&quot;100&quot; /&gt;&lt;/a&gt;</span></span><br><span class="line"><span class="string">    &lt;/body&gt;</span></span><br><span class="line"><span class="string">    &lt;/html&gt;</span></span><br><span class="line"><span class="string">  `</span>;</span><br><span class="line">&#125;</span><br><span class="line">...</span><br><span class="line"><span class="comment">// 给 webview panel 设置 HTML 内容</span></span><br><span class="line">panel.<span class="property">webview</span>.<span class="property">html</span> = <span class="title function_">getWebviewContent</span>();</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>重新使用 <code>juejin-posts.start</code> 命令就可以调戏悠悠船长了：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5160d0c76536441b8d1ac3cfcedc0d0e~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><h4 id="限制-webview-视图为一个"><a href="#限制-webview-视图为一个" class="headerlink" title="限制 webview 视图为一个"></a>限制 webview 视图为一个</h4><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">activate</span>(<span class="params">context: vscode.ExtensionContext</span>) &#123;</span><br><span class="line">  <span class="comment">// 追踪当前 webview 面板</span></span><br><span class="line">  <span class="keyword">let</span> <span class="attr">currentPanel</span>: vscode.<span class="property">WebviewPanel</span> | <span class="literal">undefined</span> = <span class="literal">undefined</span>;</span><br><span class="line"></span><br><span class="line">  context.<span class="property">subscriptions</span>.<span class="title function_">push</span>(</span><br><span class="line">    vscode.<span class="property">commands</span>.<span class="title function_">registerCommand</span>(<span class="string">&#x27;juejin-posts.start&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="comment">// 获取当前活动的编辑器</span></span><br><span class="line">      <span class="keyword">const</span> columnToShowIn = vscode.<span class="property">window</span>.<span class="property">activeTextEditor</span></span><br><span class="line">        ? vscode.<span class="property">window</span>.<span class="property">activeTextEditor</span>.<span class="property">viewColumn</span></span><br><span class="line">        : <span class="literal">undefined</span>;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (currentPanel) &#123;</span><br><span class="line">        <span class="comment">// 如果我们已经有了一个面板，那就把它显示到目标列布局中</span></span><br><span class="line">        currentPanel.<span class="title function_">reveal</span>(columnToShowIn);</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 不然，创建一个新面板</span></span><br><span class="line">        currentPanel = vscode.<span class="property">window</span>.<span class="title function_">createWebviewPanel</span>();</span><br><span class="line">        <span class="comment">// 当前面板被关闭后重置</span></span><br><span class="line">        currentPanel.<span class="title function_">onDidDispose</span>(</span><br><span class="line">          <span class="function">() =&gt;</span> &#123;</span><br><span class="line">            currentPanel = <span class="literal">undefined</span>;</span><br><span class="line">          &#125;,</span><br><span class="line">          <span class="literal">null</span>,</span><br><span class="line">          context.<span class="property">subscriptions</span></span><br><span class="line">        );</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><a href="https://vscode-api-cn.js.org/modules/window.html#activeTextEditor">vscode.window.activeTextEditor</a>：获取当前活动的文本编辑器</li><li><a href="https://vscode-api-cn.js.org/interfaces/WebviewPanel.html#reveal">currentPanel.reveal()</a>：调用 <code>reveal()</code> 或者拖动 webview 面板到新的编辑布局中去。</li></ul><h4 id="设置-Icon"><a href="#设置-Icon" class="headerlink" title="设置 Icon"></a>设置 Icon</h4><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 设置 Logo</span></span><br><span class="line">panel.<span class="property">iconPath</span> = vscode.<span class="property">Uri</span>.<span class="title function_">file</span>(</span><br><span class="line">  path.<span class="title function_">join</span>(context.<span class="property">extensionPath</span>, <span class="string">&#x27;assets&#x27;</span>, <span class="string">&#x27;icon-juejin.png&#x27;</span>)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>在 vscode 扩展中我们需要通过 <code>vscode.Uri.file</code> 方法获取磁盘上的资源路径。</p><h4 id="webview-获取内容的-Uri"><a href="#webview-获取内容的-Uri" class="headerlink" title="webview 获取内容的 Uri"></a>webview 获取内容的 Uri</h4><p>你应该使用 <code>asWebviewUri</code> 管理插件资源。不要硬编码 <code>vscode-resource://</code>，而是使用 <code>asWebviewUri</code> 确保你的插件在云端环境也能正常运行。</p><p>在 <a href="http://tny.im/jb4go">@luozhu&#x2F;vscode-utils</a> 中我们对获取本地资源路径做了封装：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取内容的 Uri</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">getDiskPath</span> = (<span class="params">fileName: <span class="built_in">string</span></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> webviewPanel.<span class="property">webview</span>.<span class="title function_">asWebviewUri</span>(</span><br><span class="line">    vscode.<span class="property">Uri</span>.<span class="title function_">file</span>(path.<span class="title function_">join</span>(context.<span class="property">extensionPath</span>, rootPath, <span class="string">&#x27;dist&#x27;</span>, fileName))</span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="使用-umi-开发-webview"><a href="#使用-umi-开发-webview" class="headerlink" title="使用 umi 开发 webview"></a>使用 umi 开发 webview</h3><p>上一节我们通过调戏悠悠船长熟悉了 webview 面板的创建，这一节我们来看下如何使用 umijs 来代替 HTML 的内容。</p><p><code>panel.webview.html</code> 中的内容其实就是正常的 HTML+JavaScript+CSS 代码。你可以使用任何前端技术去编写它的内容，比如 jquery、bootstrap、Vue 以及 React。虽然本文的例子是基于 umijs 开发 webview 的内容，但是其他技术原理是一样的，紫升在后续也会提供多个技术的 vscode webview 开发脚手架。</p><h4 id="封装获取-umijs-打包产物的方法"><a href="#封装获取-umijs-打包产物的方法" class="headerlink" title="封装获取 umijs 打包产物的方法"></a>封装获取 umijs 打包产物的方法</h4><p>我们知道 <code>umi build</code> 命令会在 <em>web&#x2F;dist</em> 产生 index.html、umi.js、umi.css 三个文件，我们根据 index.html 改造前面的 getWebviewContent 方法如下：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> vscode <span class="keyword">from</span> <span class="string">&#x27;vscode&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> path <span class="keyword">from</span> <span class="string">&#x27;path&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 获取基于 umijs 的 webview 内容</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> context 扩展上下文</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> webviewPanel webview 面板对象</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> rootPath webview 所在路径，默认 web</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> umiVersion umi 版本</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@returns</span> <span class="variable">string</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">getUmiContent</span> = (<span class="params"></span></span><br><span class="line"><span class="params">  context: vscode.ExtensionContext,</span></span><br><span class="line"><span class="params">  webviewPanel: vscode.WebviewPanel,</span></span><br><span class="line"><span class="params">  umiVersion?: <span class="built_in">string</span>,</span></span><br><span class="line"><span class="params">  rootPath = <span class="string">&#x27;web&#x27;</span></span></span><br><span class="line"><span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="comment">// 获取磁盘上的资源路径</span></span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">getDiskPath</span> = (<span class="params">fileName: <span class="built_in">string</span></span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">return</span> webviewPanel.<span class="property">webview</span>.<span class="title function_">asWebviewUri</span>(</span><br><span class="line">      vscode.<span class="property">Uri</span>.<span class="title function_">file</span>(path.<span class="title function_">join</span>(context.<span class="property">extensionPath</span>, rootPath, <span class="string">&#x27;dist&#x27;</span>, fileName))</span><br><span class="line">    );</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">`</span></span><br><span class="line"><span class="string">    &lt;html&gt;</span></span><br><span class="line"><span class="string">      &lt;head&gt;</span></span><br><span class="line"><span class="string">        &lt;meta charset=&quot;utf-8&quot; /&gt;</span></span><br><span class="line"><span class="string">        &lt;meta</span></span><br><span class="line"><span class="string">          name=&quot;viewport&quot;</span></span><br><span class="line"><span class="string">          content=&quot;width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no&quot;</span></span><br><span class="line"><span class="string">        /&gt;</span></span><br><span class="line"><span class="string">        &lt;link rel=&quot;stylesheet&quot; href=&quot;<span class="subst">$&#123;getDiskPath(<span class="string">&#x27;umi.css&#x27;</span>)&#125;</span>&quot; /&gt;</span></span><br><span class="line"><span class="string">        &lt;style&gt;</span></span><br><span class="line"><span class="string">          html, body, #root &#123;</span></span><br><span class="line"><span class="string">            width: 100%;</span></span><br><span class="line"><span class="string">            height: 100%;</span></span><br><span class="line"><span class="string">            margin: 0;</span></span><br><span class="line"><span class="string">            padding: 0;</span></span><br><span class="line"><span class="string">            overflow: hidden;</span></span><br><span class="line"><span class="string">          &#125;</span></span><br><span class="line"><span class="string">        &lt;/style&gt;</span></span><br><span class="line"><span class="string">        &lt;script&gt;</span></span><br><span class="line"><span class="string">          //! umi version: <span class="subst">$&#123;umiVersion&#125;</span></span></span><br><span class="line"><span class="string">        &lt;/script&gt;</span></span><br><span class="line"><span class="string">      &lt;/head&gt;</span></span><br><span class="line"><span class="string">      &lt;body&gt;</span></span><br><span class="line"><span class="string">        &lt;div id=&quot;root&quot;&gt;&lt;/div&gt;</span></span><br><span class="line"><span class="string">        &lt;script src=&quot;<span class="subst">$&#123;getDiskPath(<span class="string">&#x27;umi.js&#x27;</span>)&#125;</span>&quot;&gt;&lt;/script&gt;</span></span><br><span class="line"><span class="string">      &lt;/body&gt;</span></span><br><span class="line"><span class="string">    &lt;/html&gt;</span></span><br><span class="line"><span class="string">  `</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><blockquote><p>提示：上面的方法我已经封装在 <a href="https://github.com/youngjuning/luozhu/tree/main/packages/vscode-utils">@luozhu&#x2F;vscode-utils</a> 的中。</p></blockquote><p>我们使用 getUmiContent 重新前面的代码：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; getUmiContent &#125; <span class="keyword">from</span> <span class="string">&#x27;@luozhu/vscode-utils&#x27;</span>;</span><br><span class="line">...</span><br><span class="line">panel.<span class="property">webview</span>.<span class="property">html</span> = <span class="title function_">getUmiContent</span>(context, panel, <span class="string">&#x27;3.5.17&#x27;</span>);</span><br></pre></td></tr></table></figure><h4 id="优化打包"><a href="#优化打包" class="headerlink" title="优化打包"></a>优化打包</h4><p>由于我们封装了 <code>getUmiContent</code> 方法，<code>umi build</code> 生成的 index.html 就没有用了，我们可以使用 <code>HTML=none umi build</code> 命令在打包的时候不生成 index.html 文件。</p><p>另外目前 <a href="https://github.com/umijs/umi/issues/7132">umijs 的 mfsu 不支持 writeToDisk 方法</a>，如果后续支持了可以使用 mfsu 优化调试速度。</p><blockquote><p>创建 webview 面板的任务大部分都比较重复，为了沉淀最佳实践，我在 <a href="https://github.com/youngjuning/luozhu/tree/main/packages/vscode-utils">@luozhu&#x2F;vscode-utils</a> 封装了 <a href="http://tny.im/bHLQx">createUmiWebviewPanel</a> 方法。</p></blockquote><h3 id="给-webview-内容加上主题"><a href="#给-webview-内容加上主题" class="headerlink" title="给 webview 内容加上主题"></a>给 webview 内容加上主题</h3><p>webview 可以基于当前的 VS Code 主题和 CSS 改变自身的样式。VS Code 将主题分成 3 种类别，而且在 body 元素上加上了特殊类名以表明当前主题，我们在 umi 中全局加入下面的样式：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">body.<span class="property">vscode</span>-light &#123;</span><br><span class="line">  h1, h2, h3, h4, h5, h6 &#123;</span><br><span class="line">    <span class="attr">color</span>: black;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="attr">color</span>: black;</span><br><span class="line">  background-<span class="attr">color</span>: <span class="title function_">var</span>(--vscode-editor-background);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">body.<span class="property">vscode</span>-dark &#123;</span><br><span class="line">  h1, h2, h3, h4, h5, h6 &#123;</span><br><span class="line">    <span class="attr">color</span>: white;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="attr">color</span>: white;</span><br><span class="line">  background-<span class="attr">color</span>: <span class="title function_">var</span>(--vscode-editor-background);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">body.<span class="property">vscode</span>-high-contrast &#123;</span><br><span class="line">  h1, h2, h3, h4, h5, h6 &#123;</span><br><span class="line">    <span class="attr">color</span>: red;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="attr">color</span>: red;</span><br><span class="line">  background-<span class="attr">color</span>: <span class="title function_">var</span>(--vscode-editor-background);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>由于这部分适配大部分是通用的，所以我也将它封装进了 <code>@luozhu/vscode-utils</code> 的 <code>getUmiContent</code> 中了。</p><h2 id="webview-与-vscode-交互"><a href="#webview-与-vscode-交互" class="headerlink" title="webview 与 vscode 交互"></a>webview 与 vscode 交互</h2><h3 id="webview-中执行脚本"><a href="#webview-中执行脚本" class="headerlink" title="webview 中执行脚本"></a>webview 中执行脚本</h3><p>vscode 中的 webview 本质就是一个 iframe，因此我们是可以再 webview 中执行脚本的，只不过在 vscode 中 webview 默认禁用了 JavaScript，我们在调用 <code>createWebviewPanel</code> API 时传入 <code>enableScripts: true</code> 即可。</p><h3 id="插件传递信息给-webview"><a href="#插件传递信息给-webview" class="headerlink" title="插件传递信息给 webview"></a>插件传递信息给 webview</h3><p>webview 的脚本能做到任何普通网页脚本能做到的事情，但是 webview 运行在自己的上下文中，脚本是不能访问 VS Code API 的。我们需要借助 postMessage 这种事件的方式传递信息。在 vscode 中，我们在 vscode 侧可以使用 <a href="https://vscode-api-cn.js.org/interfaces/Webview.html#postMessage">Webview.postMessage</a> 发布事件并发送任何序列化的 JSON 数据，在 webview 侧则使用 <code>window.addEventListener(&#39;message&#39; event =&gt; { ... })</code> 来处理这些信息：</p><p><strong>vscode 侧</strong>：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 注册一个新的命令</span></span><br><span class="line">context.<span class="property">subscriptions</span>.<span class="title function_">push</span>(</span><br><span class="line">  vscode.<span class="property">commands</span>.<span class="title function_">registerCommand</span>(<span class="string">&#x27;juejin-me.author&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!currentPanel) &#123;</span><br><span class="line">      <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 把信息发送到 webview</span></span><br><span class="line">    <span class="comment">// 你可以发送任何序列化的 JSON 数据</span></span><br><span class="line">    currentPanel.<span class="property">webview</span>.<span class="title function_">postMessage</span>(&#123; <span class="attr">method</span>: <span class="string">&#x27;showAuthor&#x27;</span> &#125;);</span><br><span class="line">  &#125;)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p><strong>webview 侧</strong>：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Modal</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;antd&#x27;</span>;</span><br><span class="line">...</span><br><span class="line"><span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;message&#x27;</span>, <span class="function"><span class="params">event</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> message = event.<span class="property">data</span>;</span><br><span class="line">  <span class="keyword">switch</span> (message.<span class="property">method</span>) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;showAuthor&#x27;</span>: &#123;</span><br><span class="line">      <span class="title class_">Modal</span>.<span class="title function_">info</span>(&#123;</span><br><span class="line">        <span class="attr">title</span>: <span class="string">&#x27;紫升&#x27;</span>,</span><br><span class="line">        <span class="attr">content</span>: (</span><br><span class="line">          <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            大家好，我是紫升🎋一只住在杭城的木系前端🧚🏻‍♀️，如果你喜欢我的文章📚，可以通过</span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;https://juejin.cn/user/325111174662855/posts&quot;</span>&gt;</span>点赞<span class="tag">&lt;/<span class="name">a</span>&gt;</span>帮我聚集灵力⭐️。</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">        ),</span><br><span class="line">        <span class="attr">okText</span>: <span class="language-xml"><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;https://juejin.cn/user/325111174662855/posts&quot;</span>&gt;</span>点赞 o(￣▽￣)ｄ<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span>,</span><br><span class="line">      &#125;);</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="attr">default</span>:</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p><strong>效果</strong>：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0dd7dd37db954d84925ac1c0f60cb965~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><h3 id="webview-传递信息给插件"><a href="#webview-传递信息给插件" class="headerlink" title="webview 传递信息给插件"></a>webview 传递信息给插件</h3><p>webview 反向传递信息给插件的原理也是一样的，只不过由于 webview 的上下文限制，我们只能通过 <code>acquireVsCodeApi</code> 函数获取阉割版的 VS Code API 对象，这个阉割的对象上有一个 <code>postMessage</code> 函数可以供我们发送事件用。注意 <code>acquireVsCodeApi</code> 个会话中只能调用一次，重复调用会报错。而在插件侧则可以通过 <a href="https://vscode-api-cn.js.org/interfaces/Webview.html#onDidReceiveMessage">Webview.onDidReceiveMessage</a> 处理 webview 传递的信息。我们来写一个在 webview 中调用 <code>vscode.window.showInformationMessage</code> 的例子：</p><p><strong>webview 侧</strong>：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> vscode = <span class="title function_">acquireVsCodeApi</span>();</span><br><span class="line">vscode.<span class="title function_">postMessage</span>(&#123;</span><br><span class="line">  <span class="attr">method</span>: <span class="string">&#x27;showMessage&#x27;</span>,</span><br><span class="line">  <span class="attr">params</span>: &#123;</span><br><span class="line">    <span class="attr">text</span>: <span class="string">`为人民服务`</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p><strong>插件侧</strong>：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 处理 webview 中的信息</span></span><br><span class="line">currentPanel.<span class="property">webview</span>.<span class="title function_">onDidReceiveMessage</span>(</span><br><span class="line">  <span class="function"><span class="params">message</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (message.<span class="property">method</span> === <span class="string">&#x27;showMessage&#x27;</span>) &#123;</span><br><span class="line">      vscode.<span class="property">window</span>.<span class="title function_">showInformationMessage</span>(message.<span class="property">params</span>.<span class="property">content</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="literal">undefined</span>,</span><br><span class="line">  context.<span class="property">subscriptions</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p><strong>效果</strong>：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c32c8b374b534d6fac1cb552287de6ca~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><h3 id="在-webview-中请求接口"><a href="#在-webview-中请求接口" class="headerlink" title="在 webview 中请求接口"></a>在 webview 中请求接口</h3><p>一开始，我以为这是个轻松的工作，直到遇到跨域半天解决不了后我绝望了，在 <a href="https://juejin.cn/post/6844903966799577101#heading-3">VSCode WebView插件（扩展）开发实战</a> 一文中我终于知道了 vscode webview 内部是不允许发送 ajax 请求，所有 ajax 请求都是跨域的，因为 webview 本身是没有 host 的。</p><p>人裂开了，这什么鬼呀，我们核心的需求就是请求掘金的接口获取我们的文章列表呀，那我们还有办法吗？答案是肯定的，其实还是借助上面我们提到的通信机制把请求接口的任务交给 vscode 去处理，完事再让 vscode 把数据通过 <code>postMessage</code> 返回给我们，多说无益，我们来看代码：</p><p><strong>webview 侧</strong>：</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">React</span>.<span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// @ts-ignore</span></span><br><span class="line">  <span class="keyword">const</span> vscode = <span class="keyword">typeof</span> acquireVsCodeApi === <span class="string">&#x27;function&#x27;</span> ? <span class="title function_">acquireVsCodeApi</span>() : <span class="literal">null</span>;</span><br><span class="line">  vscode.<span class="title function_">postMessage</span>(&#123;</span><br><span class="line">    <span class="attr">method</span>: <span class="string">&#x27;queryPosts&#x27;</span>,</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;message&#x27;</span>, <span class="function"><span class="params">event</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (method === <span class="string">&#x27;queryPosts&#x27;</span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> message = event.<span class="property">data</span>;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(message);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;, []);</span><br></pre></td></tr></table></figure><p><strong>vscode 侧</strong>：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 处理 webview 中的信息，并返回接口请求的数据</span></span><br><span class="line">currentPanel.<span class="property">webview</span>.<span class="title function_">onDidReceiveMessage</span>(</span><br><span class="line">  <span class="keyword">async</span> message =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> data = <span class="keyword">await</span> <span class="title function_">events</span>(message);</span><br><span class="line">    currentPanel?.<span class="property">webview</span>.<span class="title function_">postMessage</span>(&#123; data &#125;);</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="literal">undefined</span>,</span><br><span class="line">  context.<span class="property">subscriptions</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><h3 id="luozhu-vscode-channel"><a href="#luozhu-vscode-channel" class="headerlink" title="@luozhu&#x2F;vscode-channel"></a>@luozhu&#x2F;vscode-channel</h3><p>前面我们知道了使用 <a href="https://vscode-api-cn.js.org/interfaces/Webview.html#postMessage">Webview.postMessage</a>、<a href="https://vscode-api-cn.js.org/interfaces/Webview.html#onDidReceiveMessage">Webview.onDidReceiveMessage</a>、<code>acquireVsCodeApi().postMessage</code> 和 <code>window.addEventListener</code> 就可以满足各种通信需求了，那 <code>@luozhu/vscode-channel</code> 又是什么呢？</p><p>受 <a href="https://www.npmjs.com/package/js-channel">js-channel</a> 启发，<code>@luozhu/vscode-channel</code> 主要是封装了 webview 与 vscode 交互流程，核心原理是通过暴露 <code>call</code>、<code>bind</code> 方法抹平 API 的差异，减少重复代码量。其中参考 appworks 和 cs-channel 使用 uuid 保证交互的可靠性。Talk is cheap, show you the code：</p><p><strong>webview 侧</strong>：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建 channel 对象</span></span><br><span class="line"><span class="keyword">const</span> channel = <span class="keyword">new</span> <span class="title class_">Channel</span>();</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">getData</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="comment">// 发起一个请求，并等待其返回数据</span></span><br><span class="line">  <span class="keyword">const</span> &#123; payload &#125; = <span class="keyword">await</span> channel.<span class="title function_">call</span>(&#123; <span class="attr">method</span>: <span class="string">&#x27;queryPosts&#x27;</span> &#125;);</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(payload);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><blockquote><p>webview 中由于 acquireVsCodeApi 只能调用一次，之后又需要在多个地方使用，所以我们在 <code>wev/src/layouts/index.ts</code> 中创建一次并挂载到 <code>window</code> 对象上比较合适。</p></blockquote><p><strong>vscode 侧</strong>：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// vscode 侧的 channel 需要依赖上下文和 WebviewPanel 实例</span></span><br><span class="line"><span class="keyword">const</span> channel = <span class="keyword">new</span> <span class="title class_">Channel</span>(context, currentPanel);</span><br><span class="line"><span class="comment">// 绑定一个回调函数，一般只需要创建一个，然后根据约定做分发即可</span></span><br><span class="line">channel.<span class="title function_">bind</span>(<span class="keyword">async</span> message =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; eventType, method, params &#125; = message;</span><br><span class="line">  <span class="comment">// 实际发起请求获取数据的地方</span></span><br><span class="line">  <span class="keyword">const</span> data = <span class="keyword">await</span> events[eventType][method](params);</span><br><span class="line">  <span class="comment">// 这里将获取的数据直接返回即可，channel 内部会进行消息合并和回传。</span></span><br><span class="line">  <span class="comment">// 如果只是执行一个功能，不写 return 语句即可，内部会进行判断降级成单工通信。</span></span><br><span class="line">  <span class="keyword">return</span> data;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="vscode-国际化"><a href="#vscode-国际化" class="headerlink" title="vscode 国际化"></a>vscode 国际化</h2><p>我们都知道 vscode 中是可以切换语言环境的，一款优秀的 vscode 扩展至少要支持中英两种语言。而且支持国际化可以让你的插件受众直接突破国界限制。vscode 国际化分为三部分，一部分是配置的国际化，一部分是代码中的国际化，另一部分则是 webview 中 umijs 的国际化。本章我们就来具体看一下如何在 vscode 中实现国际化。</p><h3 id="配置国际化"><a href="#配置国际化" class="headerlink" title="配置国际化"></a>配置国际化</h3><p>我们已经知道 vscode 中的配置都是在 <em>package.json</em> 中，而配置的国际化是约定在 <code>package.nls.json</code> 和 <code>package.nls.zh-cn.json</code> 这种文件中编写。比如我们要在中英文环境下命令配置中英文版本，我们可以在 <code>package.nls.json</code> 中写：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;contributes.category.juejin-me&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Juejin Me&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>在 <code>package.nls.zh-cn.json</code> 写：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;contributes.category.juejin-me&quot;</span><span class="punctuation">:</span> <span class="string">&quot;掘金一下&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>然后 <code>package.json</code> 中写：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;contributes&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;commands&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        ...</span><br><span class="line">        <span class="attr">&quot;category&quot;</span><span class="punctuation">:</span> <span class="string">&quot;%contributes.category.juejin-me%&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="代码中国际化"><a href="#代码中国际化" class="headerlink" title="代码中国际化"></a>代码中国际化</h3><p>推荐使用紫升贡献过代码的 <a href="https://github.com/axetroy/vscode-nls-i18n">vscode-nls-i18n</a>，使用方法也很简单，配置的话和上一节一样，在 <code>src/extension.ts</code> 中使用 <code>init</code> 方法初始化，然后使用 <code>localize</code> 方法实现国际化：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; init, localize &#125; <span class="keyword">from</span> <span class="string">&#x27;vscode-nls-i18n&#x27;</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">activate</span>(<span class="params">context: vscode.ExtensionContext</span>) &#123;</span><br><span class="line">  <span class="title function_">init</span>(context.<span class="property">extensionPath</span>); <span class="comment">// 初始化国际化配置。只用在扩展激活时初始化一次</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">localize</span>(<span class="string">&#x27;extension.activeLog&#x27;</span>)); <span class="comment">// 之后就可以在各个文件中使用。</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="umijs-国际化"><a href="#umijs-国际化" class="headerlink" title="umijs 国际化"></a>umijs 国际化</h3><p>umijs 的国际化需要使用 <code>@umijs/plugin-locale</code> 插件支持，这个插件封装了 <code>react-intl</code>，配置方式如下：</p><p>1、.umirc.ts 中配置 <code>local</code></p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">locale</span>: &#123;&#125;</span><br></pre></td></tr></table></figure><p>2、在 <em>src</em> 目录下创建 <code>locales</code> 并创建 <code>en.ts</code> 或 <code>zh-CN.ts</code></p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/locales/en.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  <span class="attr">WELCOME_TO_UMI_WORLD</span>: <span class="string">&quot;welcome to umi&#x27;s world&quot;</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/locales/zh-CN.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  <span class="attr">WELCOME_TO_UMI_WORLD</span>: <span class="string">&#x27;欢迎光临  umi  的世界&#x27;</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>3、使用国际化</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; useIntl &#125; <span class="keyword">from</span> <span class="string">&#x27;umi&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="attr">default</span>: <span class="title class_">React</span>.<span class="property">FunctionComponent</span> = <span class="function">(<span class="params">props</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> intl = <span class="title function_">useIntl</span>()</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">     &lt;div&gt;</span><br><span class="line">     &#123;intl.<span class="title function_">formatMessage</span>(</span><br><span class="line">        &#123;</span><br><span class="line">          <span class="attr">id</span>: <span class="string">&#x27;WELCOME_TO_UMI_WORLD&#x27;</span>,</span><br><span class="line">        &#125;</span><br><span class="line">      )&#125;&lt;div&gt;</span><br><span class="line">   )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>4、切换语言</p><p>切换语言，我们需要使用 <code>setLocale</code> 方法，需要注意的是我们给这个方法第二个参数传入 <code>false</code> 来实现无刷新动态切换。</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; setLocale &#125; <span class="keyword">from</span> <span class="string">&#x27;umi&#x27;</span>;</span><br><span class="line"><span class="comment">// 不刷新页面</span></span><br><span class="line"><span class="title function_">setLocale</span>(<span class="string">&#x27;zh-CN&#x27;</span>, <span class="literal">false</span>);</span><br></pre></td></tr></table></figure><p>不过，切换语言的时机在什么时候呢？切换时机就是我们语言环境改变的时机。在 vscode webview 环境中，其实当使用 <code>Config display language</code> 方法切换语言环境后，会要求 vscode 重启。也就说我们只需要在 webview 创建时设置一次语言环境即可。由于 vscode 和 webview 传值太困难，我们选择在 <code>getUmiHTMLContent</code> 时传如 <code>vscode.env</code>：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="variable language_">window</span>.<span class="property">vscodeEnv</span> = $&#123;<span class="title class_">JSON</span>.<span class="title function_">stringify</span>(vscode.<span class="property">env</span>)&#125;</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>然后，我们在 <code>web/src/layouts/index.ts</code> 中设置一下即可：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">setLocale</span>(<span class="variable language_">window</span>.<span class="property">vscodeEnv</span>.<span class="property">language</span>, <span class="literal">false</span>);</span><br></pre></td></tr></table></figure><h2 id="“掘金一下”-扩展核心实现"><a href="#“掘金一下”-扩展核心实现" class="headerlink" title="“掘金一下” 扩展核心实现"></a>“掘金一下” 扩展核心实现</h2><p>灵感来源于现实，作为掘金的重度使用者，几乎每篇文章和笔记都同步在这里。当有些知识忘记需要查阅或拷贝代码时，我就有在掘金搜索我的文章的需求。但是掘金的搜索是全站的，就算加上自己的名字搜索也会出现大量无关记录。“掘金一下” 这个名字就像插件功能一样，在你想搜索自己掘金文章的时候就可以打开插件“掘金一下” 进行搜索。</p><p>其实为了只搜索到自己的文章，我想到的还有开发 chrome 插件来实现。但是考虑到市场和便捷性，我最终还是决定开发 vscode 插件来落地这个灵感。本章就是综合前面的经验实现 “掘金一下” 的核心逻辑。</p><h3 id="juejin-me-start-命令"><a href="#juejin-me-start-命令" class="headerlink" title="juejin-me.start 命令"></a><code>juejin-me.start</code> 命令</h3><h4 id="vscode-侧开启-channel-通信"><a href="#vscode-侧开启-channel-通信" class="headerlink" title="vscode 侧开启 channel 通信"></a>vscode 侧开启 channel 通信</h4><p>vscode 侧通过 <code>channel.bind</code> 绑定一个事件处理函数。</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> events <span class="keyword">from</span> <span class="string">&#x27;./events&#x27;</span>;</span><br><span class="line">...</span><br><span class="line">context.<span class="property">subscriptions</span>.<span class="title function_">push</span>(</span><br><span class="line">  vscode.<span class="property">commands</span>.<span class="title function_">registerCommand</span>(<span class="string">&#x27;juejin-me.start&#x27;</span>, <span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">    currentPanel = <span class="title function_">createUmiWebviewPanel</span>(</span><br><span class="line">      context,</span><br><span class="line">      <span class="string">&#x27;juejin-me&#x27;</span>,</span><br><span class="line">      <span class="title function_">localize</span>(<span class="string">&#x27;extension.webview-panel.title&#x27;</span>),</span><br><span class="line">      <span class="string">&#x27;assets/icon-luozhu.png&#x27;</span>,</span><br><span class="line">      <span class="string">&#x27;3.5.17&#x27;</span></span><br><span class="line">    );</span><br><span class="line">    <span class="comment">// 处理 webview 中的信息</span></span><br><span class="line">    channel = <span class="keyword">new</span> <span class="title class_">Channel</span>(context, currentPanel);</span><br><span class="line">    channel.<span class="title function_">bind</span>(<span class="keyword">async</span> message =&gt; &#123;</span><br><span class="line">      <span class="keyword">const</span> &#123; eventType, method, params &#125; = message;</span><br><span class="line">      <span class="comment">// 根据事件类型、方法、参数来完成一次 api 调用，内置的 eventType 有 request、command 和 variable。</span></span><br><span class="line">      <span class="keyword">const</span> data = <span class="keyword">await</span> events[eventType][method](params);</span><br><span class="line">      <span class="keyword">return</span> data;</span><br><span class="line">    &#125;, vscode);</span><br><span class="line">  &#125;)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><blockquote><p>注意：我们不需要给定监听事件名，内部会根据 eventId 保证可靠性和全局唯一性</p></blockquote><h4 id="注册-events"><a href="#注册-events" class="headerlink" title="注册 events"></a>注册 events</h4><p><strong>events&#x2F;index.ts</strong>：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> requests <span class="keyword">from</span> <span class="string">&#x27;./requests&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  <span class="attr">request</span>: requests,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>events&#x2F;requests</strong>：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> vscode <span class="keyword">from</span> <span class="string">&#x27;vscode&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> request <span class="keyword">from</span> <span class="string">&#x27;../utils/request&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> queryPosts = <span class="keyword">async</span> (<span class="attr">params</span>: &#123; <span class="attr">cursor</span>: <span class="built_in">string</span> &#125;): <span class="title class_">Promise</span>&lt;<span class="built_in">any</span>&gt; =&gt; &#123;</span><br><span class="line">  <span class="comment">// 这里我们根据 vscode 配置动态取的用户 id</span></span><br><span class="line">  <span class="keyword">const</span> &#123; userId &#125; = vscode.<span class="property">workspace</span>.<span class="title function_">getConfiguration</span>(<span class="string">&#x27;juejin-me&#x27;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> &#123; cursor &#125; = params;</span><br><span class="line">  <span class="keyword">const</span> data = <span class="keyword">await</span> request.<span class="title function_">post</span>(<span class="string">&#x27;/article/query_list&#x27;</span>, &#123;</span><br><span class="line">    <span class="attr">cursor</span>: <span class="string">`<span class="subst">$&#123;cursor&#125;</span>`</span>,</span><br><span class="line">    <span class="attr">sort_type</span>: <span class="number">2</span>,</span><br><span class="line">    <span class="attr">user_id</span>: userId,</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="keyword">return</span> data;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  queryPosts,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>utils&#x2F;request</strong>：</p><p>这里简单封装了基于 axios 的请求对象。</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* eslint-disable no-param-reassign */</span></span><br><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&#x27;axios&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> vscode <span class="keyword">from</span> <span class="string">&#x27;vscode&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> qs <span class="keyword">from</span> <span class="string">&#x27;qs&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 中文文档: http://t.cn/ROfXFuj</span></span><br><span class="line"><span class="comment">// 创建实例</span></span><br><span class="line"><span class="keyword">const</span> request = axios.<span class="title function_">create</span>(&#123;</span><br><span class="line">  <span class="attr">baseURL</span>: <span class="string">&#x27;https://api.juejin.cn/content_api/v1/&#x27;</span>,</span><br><span class="line">  <span class="attr">timeout</span>: <span class="number">10000</span>,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 添加请求拦截器</span></span><br><span class="line">request.<span class="property">interceptors</span>.<span class="property">request</span>.<span class="title function_">use</span>(</span><br><span class="line">  <span class="function"><span class="params">config</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (config.<span class="property">method</span> === <span class="string">&#x27;get&#x27;</span>) &#123;</span><br><span class="line">      config.<span class="property">paramsSerializer</span> = <span class="function"><span class="params">params</span> =&gt;</span> qs.<span class="title function_">stringify</span>(params, &#123; <span class="attr">arrayFormat</span>: <span class="string">&#x27;repeat&#x27;</span> &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> config;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="function"><span class="params">error</span> =&gt;</span> &#123;</span><br><span class="line">    vscode.<span class="property">window</span>.<span class="title function_">showErrorMessage</span>(error.<span class="property">message</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(error);</span><br><span class="line">  &#125;</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 添加响应拦截器</span></span><br><span class="line">request.<span class="property">interceptors</span>.<span class="property">response</span>.<span class="title function_">use</span>(</span><br><span class="line">  <span class="function"><span class="params">response</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; data &#125; = response;</span><br><span class="line">    <span class="keyword">return</span> data;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="function"><span class="params">error</span> =&gt;</span> &#123;</span><br><span class="line">    vscode.<span class="property">window</span>.<span class="title function_">showErrorMessage</span>(error.<span class="property">message</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(error);</span><br><span class="line">  &#125;</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> request;</span><br></pre></td></tr></table></figure><h3 id="webview-中调用接口"><a href="#webview-中调用接口" class="headerlink" title="webview 中调用接口"></a>webview 中调用接口</h3><p>channel 是在 <code>web/src/layouts/index.tsx</code> 中初始化并挂载到 window 上的，我们在 <code>web/src/pages/index.tsx</code> 中调用 <code>window.channel.call</code> 即可调用指定接口。由于我们需要模糊搜索所有的文章，所以我们需要在初始化页面时一次请求完所有数据。</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">Homepage</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">getData</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; payload &#125; = (<span class="keyword">await</span> <span class="variable language_">window</span>.<span class="property">channel</span>.<span class="title function_">call</span>(&#123;</span><br><span class="line">      <span class="attr">eventType</span>: <span class="string">&#x27;request&#x27;</span>,</span><br><span class="line">      <span class="attr">method</span>: <span class="string">&#x27;queryPosts&#x27;</span>,</span><br><span class="line">      <span class="attr">params</span>: &#123; cursor &#125;,</span><br><span class="line">    &#125;)) <span class="keyword">as</span> <span class="built_in">any</span>;</span><br><span class="line">    tempData = tempData.<span class="title function_">concat</span>(payload.<span class="property">data</span>);</span><br><span class="line">    <span class="title function_">setData</span>(tempData);</span><br><span class="line">    <span class="keyword">if</span> (!payload.<span class="property">has_more</span>) &#123;</span><br><span class="line">      <span class="title function_">setInitLoading</span>(<span class="literal">false</span>);</span><br><span class="line">      <span class="title function_">setCategories</span>(<span class="title function_">_union</span>([<span class="string">&#x27;全部&#x27;</span>, ...tempData.<span class="title function_">map</span>(<span class="function"><span class="params">item</span> =&gt;</span> item.<span class="property">category</span>.<span class="property">category_name</span>)]));</span><br><span class="line">      tempData = [];</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      cursor += <span class="number">10</span>;</span><br><span class="line">      <span class="title function_">getData</span>();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>更多具体实现细节就是一些页面编写逻辑，不是本文的重点，感兴趣的同学可以直接进查看<a href="https://github.com/youngjuning/juejin-me/tree/main/web">源码</a>。</p><h3 id="配置掘金-ID"><a href="#配置掘金-ID" class="headerlink" title="配置掘金 ID"></a>配置掘金 ID</h3><p><strong>声明配置</strong>：</p><p>vscode 的配置我们需要借助 package.json 的 <code>contributes.configuration</code> 属性，我们的掘金 ID 是 string，所以声明如下：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;contributes&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;configuration&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;title&quot;</span><span class="punctuation">:</span> <span class="string">&quot;%configuration.title%&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;object&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;properties&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;juejin-me.userId&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;string&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;default&quot;</span><span class="punctuation">:</span> <span class="string">&quot;325111174662855&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;%configuration.properties.juejin-me.userId%&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><strong>修改配置的命令</strong>：</p><p>让用户打开设置去修改配置也可以，但是为了用户体验，我们提供了 <code>juejin-me.configUserId</code> 命令，我们来看下命令的实现：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">context.<span class="property">subscriptions</span>.<span class="title function_">push</span>(</span><br><span class="line">  vscode.<span class="property">commands</span>.<span class="title function_">registerCommand</span>(<span class="string">&#x27;juejin-me.configUserId&#x27;</span>, <span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> userId = <span class="keyword">await</span> vscode.<span class="property">window</span>.<span class="title function_">showInputBox</span>(&#123;</span><br><span class="line">      <span class="attr">placeHolder</span>: <span class="title function_">localize</span>(<span class="string">&#x27;extension.juejin-me.configUserId.placeHolder&#x27;</span>),</span><br><span class="line">      <span class="attr">validateInput</span>: <span class="function"><span class="params">value</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (value) &#123;</span><br><span class="line">          <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="title function_">localize</span>(<span class="string">&#x27;extension.juejin-me.configUserId.validateInput&#x27;</span>);</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">const</span> config = vscode.<span class="property">workspace</span>.<span class="title function_">getConfiguration</span>(<span class="string">&#x27;juejin-me&#x27;</span>);</span><br><span class="line"></span><br><span class="line">    config.<span class="title function_">update</span>(<span class="string">&#x27;userId&#x27;</span>, userId, <span class="literal">true</span>);</span><br><span class="line">  &#125;)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><ul><li><a href="https://vscode-api-cn.js.org/modules/window.html#showInputBox">vscode.window.showInputBox</a>：打开一个输入框，提示用户输入掘金用户 ID</li><li><a href="https://vscode-api-cn.js.org/modules/workspace.html#getConfiguration">vscode.workspace.getConfiguration</a>：获取工作空间的配置对象</li><li><a href="https://vscode-api-cn.js.org/interfaces/WorkspaceConfiguration.html#update">WorkspaceConfiguration.update</a>：更新一个配置值。</li><li><a href="https://vscode-api-cn.js.org/interfaces/InputBoxOptions.html#validateInput">InputBoxOptions.validateInput</a>：一个可选的函数，被调用来验证输入信息并提示用户</li></ul><h3 id="插件效果展示"><a href="#插件效果展示" class="headerlink" title="插件效果展示"></a>插件效果展示</h3><p>感兴趣的话你也可以直接在扩展中搜索“掘金一下”自行体验。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ac7d2ab498b2469486e8f237fc3b7997~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/91ae0fdbff7043db9513e539640fcc2e~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><h2 id="彩蛋"><a href="#彩蛋" class="headerlink" title="彩蛋"></a>彩蛋</h2><h3 id="luozhu-create-vscode-webview"><a href="#luozhu-create-vscode-webview" class="headerlink" title="@luozhu&#x2F;create-vscode-webview"></a>@luozhu&#x2F;create-vscode-webview</h3><p>本文中有很多最佳实践，为了方便之后创建新的项目时减少重复工作，紫升抽离出了一个简单的模板。掘友直接使用 <code>yarn create @luozhu/vscode-webview  myvscode</code> 即可创建出一个属于自己的 vscode 扩展。参考本文的一些实践再加一些你的创意即可完成一个出色的基于 webview 的 vscode 扩展。</p><h3 id="Word-Count-Juejin"><a href="#Word-Count-Juejin" class="headerlink" title="Word Count Juejin"></a>Word Count Juejin</h3><p>为了答谢掘金平台和掘友一直以来的支持，我编写了一款专为掘金适配的 Markdown 文件字数统计 VS Code 扩展，字数统计会实时显示在状态栏。比起来 vscode 官方的 Word Count，我们支持中文字数统计，比起来 Word Count CJK，我们支持中英文混排。如果你也喜欢使用 VS Code 的 Markdown 编辑能力，那么一定不要错过紫升的这款插件，下载请认准：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/08c060326c954849b3eb2d34f2e6b0e5~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>如果你还在犹豫要不要下载，那不妨看下三个插件的统计对比，我们拿 <code>i love juejin. 我爱掘金</code> 这个字符串测试一下三款插件的功能：</p><table><thead><tr><th>Word Count</th><th>Word Count CJK</th><th>Word Count Juejin</th></tr></thead><tbody><tr><td>4 个字</td><td>4 个字</td><td>7 个字</td></tr><tr><td>中文算成了一个字</td><td>直接忽略了英文</td><td>中文4 个字加英文三个字，格局正好</td></tr></tbody></table><h3 id="vscode-api-cn"><a href="#vscode-api-cn" class="headerlink" title="vscode api cn"></a>vscode api cn</h3><p>在学习和开发 vscode 插件的过程中，最大的痛点无过于 API 文档翻译的缺失。哪怕是硬着头皮看英文原版 API 文档，阅读体验也很差。为了方便自己、回馈社区，我和 <a href="https://juejin.cn/user/703340610597064">寒草</a> 等小伙伴决定翻译 vscode api 类型声明并使用 Typedoc 承载，另外在完工后我们也会输出 <code>@types/vscode-cn</code> 类型包代替 <code>@types/vscode</code> 进一步方便 vscode 插件开发者。团队成员现状：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/aa80c1603b2e4482883fe39e1b44f62e~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>翻译是一件带有侠义精神的事业，欢迎更多的小伙伴加入我们。你可以浏览<a href="https://github.com/vscode-cn/vscode-api-cn">仓库</a>和<a href="https://vscode-api-cn.js.org/">官网</a>了解具体情况。</p><h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><p>这是第一次尝试写这么长的文章，断断续续经历了有半个月，本着对读者负责任的态度，文中的实践都是经过反复测试以及和同事朋友的讨论。当然 vscode 插件开发的概念和 API 比较多，一篇文章也很难讲全，讲透彻。如果大家感兴趣，可以在评论区告诉紫升，我可以继续更新这方面的教程。</p><blockquote><p>本文首发于「掘金专栏」，同步于公众号「程序人生」。</p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/609531a3ab52/</id>
    <link href="https://blog.zisheng.pro/posts/609531a3ab52/"/>
    <published>2023-02-28T16:55:47.000Z</published>
    <summary>在团队降本提效的基建中，紫升开发了一款 vscode 插件，第一版我使用的是 vscode 内置 UI，虽说也能用，但是用户体验欠佳。由于 vscode 内置 UI 不够灵活，一番调研后我决定使用 webview 重构。</summary>
    <title>掘金一下 | 从零开发一款基于 webview 的 vscode 扩展</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="VS Code" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/VS-Code/"/>
    <category term="vscode 插件开发" scheme="https://blog.zisheng.pro/tags/vscode-%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91/"/>
    <category term="VS Code" scheme="https://blog.zisheng.pro/tags/VS-Code/"/>
    <category term="yarn.lock" scheme="https://blog.zisheng.pro/tags/yarn-lock/"/>
    <category term="vscode 中文文档" scheme="https://blog.zisheng.pro/tags/vscode-%E4%B8%AD%E6%96%87%E6%96%87%E6%A1%A3/"/>
    <category term="vscode webview" scheme="https://blog.zisheng.pro/tags/vscode-webview/"/>
    <content>
      <![CDATA[<blockquote><p><a href="https://marketplace.visualstudio.com/items?itemName=youngjuning.yarn-lock-preview">Yarn Lock Preview</a> 插件已发布，亲测已比较稳定，源码在 <a href="https://github.com/youngjuning/vscode-yarn-lock-preview">vscode-yarn-lock-preview</a> ，如果对你些许帮助和启发，不妨赏一个 Star。</p></blockquote><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在 <a href="https://juejin.cn/post/7000589186898231333">《从零开发一款基于 webview 的 vscode 扩展》</a> 一文中，紫升通过实战阐述了如何基于 webview 实现在 vscode 中展示自定义 UI 的功能，另外文章配套产物 <a href="vscode:extension/youngjuning.juejin-me">掘金一下</a> 插件也已经发布，欢迎试用。</p><p>人们👨🏻‍💻总是喜欢炫耀自己的新技能，紫升也不例外，毕竟新技能需要大量的实战练习。在入门了 VS Code 扩展开发后，遇到开发痛点我就习惯地用 VS Code 插件的思路去解决。不过大多扩展 idea 都已经名花有主，直到我想到可以将 <code>yarn.lock</code> 文件可视化并搜索相关插件无果后，我知道机会来了。</p><h2 id="什么是自定义编辑器❓"><a href="#什么是自定义编辑器❓" class="headerlink" title="什么是自定义编辑器❓"></a>什么是自定义编辑器❓</h2><p>在开始开发之前，我们有必要先了解一下什么编辑器？什么又是自定义编辑器？</p><p>VS Code 中编辑器（Editor）其实就是我们使用频率最高的编码的地方。如下图，VS Code 的用户界面分为 5 个部分，分别是 Activity Bar（活动栏）、Side Bar（侧边栏）、Editor Groups（编辑器组）、Panel（面板） 和 Status Bar（状态栏）。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/301eab5bbab04c65aec4a927b7d306a9~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>自定义编辑器指的是 VS Code 中相对于默认文本编辑器的编辑器类型。根据用途不同，自定义编辑器又分为 <a href="https://vscode-api-cn.js.org/interfaces/vscode.CustomTextEditorProvider.html">自定义文本编辑器</a>、<a href="https://vscode-api-cn.js.org/interfaces/vscode.CustomEditorProvider.html">自定义编辑器</a> 和 <a href="https://vscode-api-cn.js.org/interfaces/vscode.CustomReadonlyEditorProvider.html">自定义只读编辑器</a>。</p><p>自定义文本编辑器常被用来为 JSON、XML、CSV、JSON 或者任意 <a href="https://vscode-api-cn.js.org/interfaces/vscode.TextDocument.html">文本文档</a> 提供自定义的视觉渲染。比如 <a href="https://marketplace.visualstudio.com/items?itemName=SimonSiefke.svg-preview">Svg Preview</a>、<a href="https://marketplace.visualstudio.com/items?itemName=shd101wyy.markdown-preview-enhanced">Markdown Preview Enhanced</a>、<a href="https://marketplace.visualstudio.com/items?itemName=vstirbu.vscode-mermaid-preview">Mermaid Preview</a> 和 <a href="https://marketplace.visualstudio.com/items?itemName=saber2pr.todolist">Todo List</a>。</p><p>自定义编辑器常被用来预览资产(assets)文件，比如 <a href="https://marketplace.visualstudio.com/items?itemName=slevesque.vscode-3dviewer">3D Viewer for VSCode</a>、<a href="https://marketplace.visualstudio.com/items?itemName=hediet.vscode-drawio">Draw.io Integration</a> 和 <a href="https://marketplace.visualstudio.com/items?itemName=elypia.magick-image-reader">Magick Image Reader</a></p><p>但他们的共同点都是使用了 VS Code 自定义编辑器这个功能。本文的目标其实就是实现一个和上述插件一样的 Preview 类型的扩展。</p><h2 id="为什么开发-Yarn-Lock-Preview？"><a href="#为什么开发-Yarn-Lock-Preview？" class="headerlink" title="为什么开发 Yarn Lock Preview？"></a>为什么开发 Yarn Lock Preview？</h2><p>本来文章是没有这一章的。在插件刚发布的时候，没等文章写完，我就迫不及待地向同事和朋友以及常混迹的几个群推荐了我的插件，然后就被一个群友灵魂拷问了“你这有什么意义？”：</p><p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c054b9bca3b44b61b7b9ecc7206240a4~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/643f9dd16c734e8487866f6a3306f0f3~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p>开发一款自定义编辑器插件要比单纯的开发插件和 webview 插件都要麻烦，没有人会闲的无聊牺牲业余时间，做一个没有意思的事情。从学习角度来讲，可以掌握了大量 VSCode API，从作用来讲，更直观地查看 <code>yarn.lock</code> 并支持搜索某个包以及依赖该包的包。这在你想确定应用的间接引用了哪些包时很有帮助：</p><p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c2faa24d3cee4c9088e83c4d0ee06c63~tplv-k3u1fbpfcp-watermark.image" alt="screenshot.gif"></p><p>这其实是一个来自于实际工作中的一个痛点，React Native 中如果同时依赖两个不同版本的原生包，运行项目的时候就会因为重复注册某个 <code>View</code> 造成应用崩溃。入职涂鸦一年来，帮助业务同学排查了 N 次这个问题。由于某些版本的依赖并不是直接在 <code>dependencies</code> 中写的，而是某个包间接依赖的，我的办法是就是在 <code>yarn.lock</code> 中 <code>cmd+f</code> 搜索造成崩溃的包被哪些包依赖了，然后再人肉分析这个有结构的纯文本文件：</p><p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4ee531d6d54d4d269984174b5bae97b8~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p>虽然我已经是处理这个问题的老司机了，但总归是会耽误不少我和同事的时间。上面的这个问题可能比较极端，还有一个我们经常遇到的开发的反馈是引入了某个原生包之后导致应用崩溃。这其实是因为我们的 React Native 应用是以嵌套的形式集成到已有原生涂鸦 App 内的，所以原生库的支持也是依赖 App 提供的版本。我们在打包的时候也有相应的检验工具，但是用户有可能没有直接依赖这个库，而是这个库间接或者间接地间接库引用了这个库。这个时候我们就得在上万行纯文本 yarn.lock 中开启人肉分析。</p><p>比如，App 目前支持的 react-native-svg 版本是 5.5.1。用户使用的 <code>react-native-svg-charts</code> 最新版依赖的是 react-native-svg <code>^6.2.1</code> 或者 <code>^7.0.3</code>。用户按照指引直接安装使用运行崩溃，然后找到我们质疑。那如果用了这款插件，我们就可以直接在项目中搜索：</p><p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/95d979f9b7c549e68f8321dac0aaa481~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p>最后一个就是你一定可能会遇到的场景，比如开发声明的依赖是 <code>tuya-panel-kit@^4.6.0</code>，然后遇到问题，直接截图告诉你我没有升级过版本呀，为啥报错了或者为啥表现不一样了的问题。然后我们就得让用户看一下实际依赖的版本是什么。有的开发会去 <code>node_modules</code> 下面找，这其实不靠谱，层级那么复杂，想快速定位是很难的；有的开发会在 <code>yarn.lock</code> 中人肉搜索；或者有的开发直接让我们帮忙排查。那么有了这款插件，我们就可以直接让开发自己搜，然后截图提 issues 给我们：</p><p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7bc01f9c7df543db90d6f6b31444eeca~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><blockquote><p>温馨提示：<code>yarn list --pattern tuya-panel-kit</code> 命令也可以列出简要的依赖信息。</p></blockquote><p>叨逼叨这么多，相信读者已经和我达成共识了，如果没有，欢迎评论区 Battle。废话不多说，下面就让我们来开始展示真正的技术吧。</p><p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7c9f81662bba4518b8d075d132d870e1~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><h2 id="初始化项目"><a href="#初始化项目" class="headerlink" title="初始化项目"></a>初始化项目</h2><h3 id="使用官方脚手架"><a href="#使用官方脚手架" class="headerlink" title="使用官方脚手架"></a>使用官方脚手架</h3><ol><li>安装 <a href="http://yeoman.io/">Yeoman</a> 和 <a href="https://www.npmjs.com/package/generator-code">VS Code Extension Generator</a>：<code>npm install -g yo generator-code</code></li><li>生成项目：<code>yo code</code></li></ol><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">$ yo code</span><br><span class="line"><span class="comment">#     _-----_     ╭──────────────────────────╮</span></span><br><span class="line"><span class="comment">#    |       |    │   Welcome to the Visual  │</span></span><br><span class="line"><span class="comment">#    |--(o)--|    │   Studio Code Extension  │</span></span><br><span class="line"><span class="comment">#   `---------´   │        generator!        │</span></span><br><span class="line"><span class="comment">#    ( _´U`_ )    ╰──────────────────────────╯</span></span><br><span class="line"><span class="comment">#    /___A___\   /</span></span><br><span class="line"><span class="comment">#     |  ~  |</span></span><br><span class="line"><span class="comment">#   __&#x27;.___.&#x27;__</span></span><br><span class="line"><span class="comment"># ´   `  |° ´ Y `</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># ? What type of extension do you want to create? New Extension (TypeScript)</span></span><br><span class="line"><span class="comment"># ? What&#x27;s the name of your extension? Yarn Lock Preview</span></span><br><span class="line"><span class="comment"># ? What&#x27;s the identifier of your extension? yarn-lock-preview</span></span><br><span class="line"><span class="comment"># ? What&#x27;s the description of your extension? Previews yarn.lock file</span></span><br><span class="line"><span class="comment"># ? Initialize a git repository? Yes</span></span><br><span class="line"><span class="comment"># ? Bundle the source code with webpack? No</span></span><br><span class="line"><span class="comment"># ? Which package manager to use? yarn</span></span><br><span class="line"></span><br><span class="line">$ code ./vscode-yarn-lock-preview</span><br></pre></td></tr></table></figure><h3 id="规范化项目"><a href="#规范化项目" class="headerlink" title="规范化项目"></a>规范化项目</h3><ol><li>代码规范配置 - <code>npx @luozhu/create-coding-style</code></li><li>按照新的代码规范格式化代码 - <code>yarn lint --fix</code></li><li>Git Commit 规范配置 - <code>npx @luozhu/create-commitlint</code></li></ol><h3 id="扩展信息配置"><a href="#扩展信息配置" class="headerlink" title="扩展信息配置"></a>扩展信息配置</h3><ol><li>扩展信息配置（package.json）<ol><li>配置 <a href="https://code.visualstudio.com/api/working-with-extensions/publishing-extension#create-a-publisher">publisher</a></li><li>添加上 <code>license</code> 字段。</li><li>添加 <code>repository</code> 字段。</li><li>配置 <code>icon</code>：128 x 128 像素图标的路径。</li></ol></li><li>修改 readme：因为初始的没法通过 <code>vsce package</code> 校验</li><li>使用 <code>npx vsce package</code> 尝试打包，确保没有错误和警告</li></ol><h3 id="使用-esbuild-打包"><a href="#使用-esbuild-打包" class="headerlink" title="使用 esbuild 打包"></a>使用 esbuild 打包</h3><p>在 <a href="https://juejin.cn/post/7000589186898231333/#heading-8">从零开发一款基于 webview 的 vscode 扩展</a> 中我们讲述了使用 esbuild 打包可以减小打包产物的体积以及加快调试速度。参考上一篇文章即可完成配置，这里就不赘述了。</p><h3 id="Don’t-repeat-yourself"><a href="#Don’t-repeat-yourself" class="headerlink" title="Don’t repeat yourself"></a>Don’t repeat yourself</h3><p>本着重复的工作都可以用脚本代替的原则，我 vscode 插件开发的最佳实践沉淀为了一个脚手架，执行 <code>yarn create @luozhu/vscode-extension</code> 即可快速开始开发 vscode 扩展。</p><h2 id="自定义编辑器原理"><a href="#自定义编辑器原理" class="headerlink" title="自定义编辑器原理"></a>自定义编辑器原理</h2><h3 id="工作机制"><a href="#工作机制" class="headerlink" title="工作机制"></a>工作机制</h3><p>前面我们已经知道了在 VS Code 中自定义编辑器有三种：自定义文本编辑器、自定义编辑器和自定义只读编辑器。三种类型的编辑器都会替换 VS Code 中的标准文本编辑器展示的位置。不同的是自定义文本编辑器由于是基于 VS Code 的标准文本文档模型，不需要开发者提供，比如 <a href="https://marketplace.visualstudio.com/items?itemName=SimonSiefke.svg-preview">Svg Preview</a> 插件。而自定义编辑器用于二进制文件的预览，因此需要开发者自己提供文档模型并自行实现诸如保存和备份这些功能比如 <a href="https://marketplace.visualstudio.com/items?itemName=hediet.vscode-drawio">Draw.io Integration</a> 插件。自定义只读编辑器则用于预览二进制文件，比如 <a href="https://marketplace.visualstudio.com/items?itemName=elypia.magick-image-reader">Magick Image Reader</a>。</p><p>了解了三种自定义编辑器的区别，我们来看下我们的插件属于哪种自定义编辑器。其实答案显而易见，<code>yarn.lock</code> 文件属于 VS Code 标准文档模型，我们选用最简单的自定义文本编辑器即可。</p><p>编写一个自定义编辑器会涉及视图、插件、文档模型、底层资源文件以及它们之间的交互，其大概原理如下图：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/116948b627f4406ba51faa0353f147a7~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>由于预览 <code>yarn.lock</code> 不需要我们提供文档模型，因此我们只需要基于 <a href="https://vscode-api-cn.js.org/interfaces/vscode.CustomTextEditorProvider.html">CustomTextEditorProvider</a> 编写一个 CustomEditorProvider 给插件注册使用，然后编写我们的视图（用户界面），最后实现视图和插件的交互即可。</p><p>其中我们需要关注的点有如何开发视图、视图和插件如何通信、如何实现 CustomEditorProvider 以及如何注册 CustomEditorProvider。</p><h3 id="如何开发视图"><a href="#如何开发视图" class="headerlink" title="如何开发视图"></a>如何开发视图</h3><p>视图是通过 webview 实现的，所以你可以通过标准的 HTML、CSS 和 JavaScript 构建用户体验，也可以使用你熟悉的前端框架实现。</p><h3 id="视图如何与插件通信"><a href="#视图如何与插件通信" class="headerlink" title="视图如何与插件通信"></a>视图如何与插件通信</h3><p>由于 VS Code 的限制，Webview 是不能直接访问 VS Code API 以及发送网络请求的，但是它可以通过 postMessage 与插件进行双向通信。但是手写双向通信比较麻烦，尤其是进行网络请求时，webview 需要先发送发起网络请求的消息，插件侧注册的监听事件发起网络请求，插件等待网络请求返回后再发送消息将数据发给 webview，最后 webview 通过注册的监听事件获取信息。简要的流程如下：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/20f34a967c234945afbaf9aaf2901163~tplv-k3u1fbpfcp-watermark.image" alt="webview 通信原理.png"></p><p>抛开具体实现来看这个交互就已经很反人类了，而且具体使用时还会面对 webview 和插件用到的 API 也不一样导致的转换成本和心智负担。为了解决这个痛点，我封装了 <a href="https://www.npmjs.com/package/@luozhu/vscode-channel">@luozhu&#x2F;vscode-channel</a> 来实现好用的双向通信。你可以简单地在 webview 侧通过 <code>call</code> 方法调用方法并等待处理结果，插件侧通过 <code>bind</code> 方法绑定事件处理。</p><h3 id="如何实现-CustomEditorProvider"><a href="#如何实现-CustomEditorProvider" class="headerlink" title="如何实现 CustomEditorProvider"></a>如何实现 CustomEditorProvider</h3><p>参考官方 Demo <a href="https://github.com/microsoft/vscode-extension-samples/blob/main/custom-editor-sample/src/catScratchEditor.ts#L16">catScratchEditor</a> 我们可以看到 <code>CatScratchEditorProvider</code> 是通过实现了 <code>vscode.CustomTextEditorProvider</code> 接口创建的自定义编辑器 Provider。而 <code>vscode.CustomTextEditorProvider</code> 这个接口只有一个方法  <code>resolveCustomTexEditor</code>。所以原理上我们只需要实现 <code>resolveCustomTexEditor</code> 这个方法即可。</p><h3 id="如何注册-CustomEditorProvider"><a href="#如何注册-CustomEditorProvider" class="headerlink" title="如何注册 CustomEditorProvider"></a>如何注册 CustomEditorProvider</h3><p>实现了自定义编辑器 Provider 之后，我们需要将其注册到插件中，VS Code 提供了 <code>vscode.window.registerCustomEditorProvider</code> 方法用来完成这项任务。</p><h2 id="自定义编辑器实现"><a href="#自定义编辑器实现" class="headerlink" title="自定义编辑器实现"></a>自定义编辑器实现</h2><p>纸上得来终觉浅，绝知此事要躬行。如果对前面的概念感到困惑，那接下来的实战可能会让你豁然开朗。</p><h3 id="声明自定义编辑器"><a href="#声明自定义编辑器" class="headerlink" title="声明自定义编辑器"></a>声明自定义编辑器</h3><p>自定义编辑器是通过 <code>package.json</code> 的贡献内容（<code>contributes</code>）的 <code>customEditors</code> 属性声明的，<code>customEditors</code> 提供自定义编辑器。它是一个数组，也就是说我们可以在一个扩展中提供多个自定义编辑器。我们声明的自定义编辑器如下：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;contributes&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;activationEvents&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;onCustomEditor:yarn-lock-preview.yarnLock&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;customEditors&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;viewType&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn-lock-preview.yarnLock&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;displayName&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Preview yarn.lock&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;selector&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;filenamePattern&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn.lock&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;priority&quot;</span><span class="punctuation">:</span> <span class="string">&quot;option&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br></pre></td></tr></table></figure><ul><li><code>activationEvents</code> - 注册激活事件<ul><li><code>onCustomEditor:*</code> - 激活自定义编辑器的事件</li></ul></li><li><code>customEditors</code> - 提供的自定义编辑器。<ul><li><code>viewType</code> - 自定义编辑器的标识符。它在所有自定义编辑器中都必须是唯一的，因此建议将扩展 ID 作为 <code>viewType</code> 的一部分包括在内。在使用 <code>vscode.registerCustomEditorProvider</code> 和在 <code>onCustomEditor:${id}</code> 激活事件中注册自定义编辑器时，使用 <code>viewType</code>。</li><li><code>displayName</code> - 自定义编辑器的用户可读名称。当选择要使用的编辑器时，向用户显示此名称。</li><li><code>selector</code> - 为其启用了自定义编辑器的一组 glob。</li><li><code>priority</code> - （可选）确定自定义编辑器的使用时机。这个字段控制合适使用特定的自定义编辑器。<ul><li><code>option</code> - 在用户打开资源时不会自动使用此编辑器，但用户可使用 <code>Reopen With</code> 命令切换到此编辑器。</li><li><code>default</code> - 在用户打开资源时自动使用此编辑器，前提是没有为该资源注册其他默认的自定义编辑器。</li></ul></li></ul></li></ul><p>我们现在可以打开一个 <code>yarn.lock</code> 文件，并在命令面板中输入 <code>Reopen with</code> 选择我前面注册的 <code>Preview yarn.lock</code>：</p><p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/376a6b66b7c24de589fb48a54d29efb6~tplv-k3u1fbpfcp-watermark.image" alt="Kapture 2021-09-14 at 11.28.37.gif"></p><p>这时我们可以看到一个空白的编辑器以及顶部的不会停止的加载进度条。</p><p>打开命令面板输出命令的方式对用户来说不是很友好，我们可以在编辑器菜单中添加一个切换按钮实现快速切换编辑器模式。首先我们在 <code>package.json</code> 中配置命令和菜单：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;contributes&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;commands&quot;</span><span class="punctuation">:</span><span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;command&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn-lock-preview.switchEditorMode&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;title&quot;</span><span class="punctuation">:</span> <span class="string">&quot;switch editor mode&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;icon&quot;</span><span class="punctuation">:</span> <span class="string">&quot;$(rocket)&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;menus&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;editor/title&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;command&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn-lock-preview.switchEditorMode&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;group&quot;</span><span class="punctuation">:</span> <span class="string">&quot;navigation&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><ul><li><code>commands</code> - 对命令面板提供命令。</li><li><code>menus</code> - 向编辑器提供菜单项。</li></ul><p>然后在 <code>src/extension</code> 中的 active 函数中注册命令及实现命令回调：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; commands, <span class="title class_">ExtensionContext</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;vscode&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">activate</span>(<span class="params">context: ExtensionContext</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Congratulations, your extension &quot;yarn-lock-preview&quot; is now active!&#x27;</span>);</span><br><span class="line"></span><br><span class="line">  context.<span class="property">subscriptions</span>.<span class="title function_">push</span>(</span><br><span class="line">    commands.<span class="title function_">registerCommand</span>(<span class="string">&#x27;yarn-lock-preview.switchEditorMode&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      commands.<span class="title function_">executeCommand</span>(<span class="string">&#x27;workbench.action.reopenWithEditor&#x27;</span>);</span><br><span class="line">    &#125;)</span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我只找到了 <code>workbench.action.reopenWithEditor</code> 这个可以触发的命令，我本意是实现类似 git 的<strong>打开文件</strong>和<strong>打开更改</strong>的功能。由于学艺不精，<a href="https://github.dev/microsoft/vscode/blob/f66a3e06bcb9f000e5dc0ad0040ff9b32fc75c78/extensions/git/src/commands.ts#L655-L656">git.openFile</a> 的实现我还需要再研究一下，如果有大佬看到这来可以指点一下。目前效果如下：</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cb03abea51634bd3a0a8c1a27b155ec0~tplv-k3u1fbpfcp-watermark.image" alt="Kapture 2021-09-14 at 12.46.00.gif"></p><h3 id="注册自定义编辑器-Provider"><a href="#注册自定义编辑器-Provider" class="headerlink" title="注册自定义编辑器 Provider"></a>注册自定义编辑器 Provider</h3><p>现在我们已经注册了自定义文档类型 <code>yarn-lock-preview.yarnLock</code> 和 <code>onCustomEditor:yarn-lock-preview.yarnLock</code> 激活事件。现在我们需要借助 <a href="https://vscode-api-cn.js.org/modules/window.html#registerCustomEditorProvider">window.registerCustomEditorProvider</a> 方法注册对应的自定义编辑器 Provider。</p><p>如果这时候尝试调用 <code>registerCustomEditorProvider</code> 方法，你会发现我们并没有一个自定义编辑器 provider 可以使用，下一节我们会实现自定义编辑器 Provider。</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> vscode <span class="keyword">from</span> <span class="string">&#x27;vscode&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">activate</span>(<span class="params">context: vscode.ExtensionContext</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Congratulations, your extension &quot;yarn-lock-preview&quot; is now active!&#x27;</span>);</span><br><span class="line"></span><br><span class="line">  context.<span class="property">subscriptions</span>.<span class="title function_">push</span>(</span><br><span class="line">    vscode.<span class="property">window</span>.<span class="title function_">registerCustomEditorProvider</span>(</span><br><span class="line">      <span class="string">&#x27;yarn-lock-preview.yarnLock&#x27;</span>,</span><br><span class="line">      provider <span class="comment">// 自定义编辑器 provider 实例</span></span><br><span class="line">    );</span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="实现自定义编辑器-Provider"><a href="#实现自定义编辑器-Provider" class="headerlink" title="实现自定义编辑器 Provider"></a>实现自定义编辑器 Provider</h3><p>由于我们要预览的文件是 VS Code 的标准文档模型，所以我们需要基于 <code>CustomTextEditorProvider</code> 这个接口封装一个类来实现它。为此我们新建一个 <code>YarnLockEditorProvider.ts</code> 文件，该文件的最小实现如下：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> vscode <span class="keyword">from</span> <span class="string">&#x27;vscode&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">YarnLockEditorProvider</span> <span class="keyword">implements</span> vscode.<span class="property">CustomTextEditorProvider</span> &#123;</span><br><span class="line">  <span class="comment">// 将 context 注入 this 对象</span></span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"><span class="keyword">private</span> <span class="keyword">readonly</span> context: vscode.ExtensionContext</span>) &#123;&#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 当自定义编辑器打开时调用。</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="keyword">async</span> <span class="title function_">resolveCustomTextEditor</span>(</span><br><span class="line">    <span class="attr">_document</span>: vscode.<span class="property">TextDocument</span>,</span><br><span class="line">    <span class="attr">webviewPanel</span>: vscode.<span class="property">WebviewPanel</span>,</span><br><span class="line">    <span class="attr">_token</span>: vscode.<span class="property">CancellationToken</span></span><br><span class="line">  ): <span class="title class_">Promise</span>&lt;<span class="built_in">void</span>&gt; &#123;</span><br><span class="line">    <span class="comment">// 给 webview 设置初始内容</span></span><br><span class="line">    webviewPanel.<span class="property">webview</span>.<span class="property">options</span> = &#123;</span><br><span class="line">      <span class="attr">enableScripts</span>: <span class="literal">true</span>, <span class="comment">// 允许在 webview 中运行脚本</span></span><br><span class="line">    &#125;;</span><br><span class="line">    webviewPanel.<span class="property">webview</span>.<span class="property">html</span> = <span class="variable language_">this</span>.<span class="title function_">getHtmlForWebview</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> <span class="title function_">getHtmlForWebview</span>(): <span class="built_in">string</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">`</span></span><br><span class="line"><span class="string">        &lt;!DOCTYPE html&gt;</span></span><br><span class="line"><span class="string">        &lt;html lang=&quot;en&quot;&gt;</span></span><br><span class="line"><span class="string">        &lt;head&gt;</span></span><br><span class="line"><span class="string">            &lt;meta charset=&quot;UTF-8&quot;&gt;</span></span><br><span class="line"><span class="string">            &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;</span></span><br><span class="line"><span class="string">            &lt;title&gt;Cat Coding&lt;/title&gt;</span></span><br><span class="line"><span class="string">        &lt;/head&gt;</span></span><br><span class="line"><span class="string">        &lt;body&gt;</span></span><br><span class="line"><span class="string">            &lt;img src=&quot;https://media.giphy.com/media/JIX9t2j0ZTN9S/giphy.gif&quot; width=&quot;300&quot; /&gt;</span></span><br><span class="line"><span class="string">        &lt;/body&gt;</span></span><br><span class="line"><span class="string">        &lt;/html&gt;</span></span><br><span class="line"><span class="string">    `</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">YarnLockEditorProvider</span>;</span><br></pre></td></tr></table></figure><ul><li><code>class YarnLockEditorProvider implements vscode.CustomTextEditorProvider</code> - 声明一个类实现自定义文本编辑器 provider。</li><li><a href="https://vscode-api-cn.js.org/interfaces/CustomTextEditorProvider.html#resolveCustomTextEditor">resolveCustomTextEditor</a>：根据给定的文本资源解析自定义编辑器。当用户第一次打开一个 <code>CustomTextEditorProvider</code> 的资源时，或者当他们使用这个 <code>CustomTextEditorProvider</code> 重新打开一个现有的编辑器时，该方法将被调用。</li><li><code>webviewPanel.webview.options</code> - 配置 webview 选项，这里我们配置了允许使用脚本。</li><li><code>webviewPanel.webview.html = this.getHtmlForWebview()</code> - 为 HTML 设置初始内容。</li></ul><p>为了简化初始化操作，我们为 <code>YarnLockEditorProvider</code> 类封装一个 <code>register</code> 静态方法：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="title function_">register</span>(<span class="attr">context</span>: vscode.<span class="property">ExtensionContext</span>): vscode.<span class="property">Disposable</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> provider = <span class="keyword">new</span> <span class="title class_">YarnLockEditorProvider</span>(context);</span><br><span class="line">  <span class="keyword">const</span> providerRegistration = vscode.<span class="property">window</span>.<span class="title function_">registerCustomEditorProvider</span>(</span><br><span class="line">    <span class="title class_">YarnLockEditorProvider</span>.<span class="property">viewType</span>,</span><br><span class="line">    provider,</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">webviewOptions</span>: &#123;</span><br><span class="line">        <span class="attr">retainContextWhenHidden</span>: <span class="literal">true</span>, <span class="comment">// 隐藏时保留上下文</span></span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;</span><br><span class="line">  );</span><br><span class="line">  <span class="keyword">return</span> providerRegistration;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">readonly</span> viewType = <span class="string">&#x27;yarn-lock-preview.yarnLock&#x27;</span>;</span><br></pre></td></tr></table></figure><p>现在我们就可以注册我们的自定义编辑器 provider 了，在 <code>src/extension.ts</code> 的 <code>activate</code> 方法中调用 <code>YarnLockEditorProvider.register(context)</code> 得到注册的自定义编辑器，然后 <code>push</code> 到代理监听数组中即可：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> vscode <span class="keyword">from</span> <span class="string">&#x27;vscode&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">YarnLockEditorProvider</span> <span class="keyword">from</span> <span class="string">&#x27;./YarnLockEditorProvider&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">activate</span>(<span class="params">context: vscode.ExtensionContext</span>) &#123;</span><br><span class="line">  context.<span class="property">subscriptions</span>.<span class="title function_">push</span>(<span class="title class_">YarnLockEditorProvider</span>.<span class="title function_">register</span>(context));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样一个简单但完整的自定义编辑器就完成了：</p><p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e6addd1cb739448db52db1767154fe65~tplv-k3u1fbpfcp-watermark.image" alt="Kapture 2021-09-14 at 17.00.55.gif"></p><h2 id="yarn-lock-可视化预览实现"><a href="#yarn-lock-可视化预览实现" class="headerlink" title="yarn.lock 可视化预览实现"></a>yarn.lock 可视化预览实现</h2><p>前面我们已经顺利带大家实现了在自定义编辑器中看猫的功能。当然了，我们的最终目标可不是看黑猫敲代码。这一章我们将挑战最后的 Boss：实现一个可以搜索的 yarn.lock 依赖 Json 树。</p><h3 id="解析-yarn-lock-文件"><a href="#解析-yarn-lock-文件" class="headerlink" title="解析 yarn.lock 文件"></a>解析 yarn.lock 文件</h3><p>解决了技术问题，现在我们来看下业务问题。我们的痛点是 yarn.lock 文件是纯文本的，阅读起来比较困难，需要一个更好的展示形式。那我们可以第一步肯定是将文本文件转成更易处理的资源，作为前端，当然首选 JSON。借助 Yarn 官方的 <a href="https://www.npmjs.com/package/@yarnpkg/lockfile">@yarnpkg&#x2F;lockfile</a> 工具我们就可以实现这个功能。我们来编写 demo 试一下。</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> lockfile <span class="keyword">from</span> <span class="string">&#x27;@yarnpkg/lockfile&#x27;</span>;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">YarnLockEditorProvider</span> <span class="keyword">implements</span> vscode.<span class="property">CustomTextEditorProvider</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 当自定义编辑器打开时调用。</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="keyword">async</span> <span class="title function_">resolveCustomTextEditor</span>(</span><br><span class="line">    <span class="attr">document</span>: vscode.<span class="property">TextDocument</span>,</span><br><span class="line">    <span class="attr">webviewPanel</span>: vscode.<span class="property">WebviewPanel</span>,</span><br><span class="line">    <span class="attr">_token</span>: vscode.<span class="property">CancellationToken</span></span><br><span class="line">  ): <span class="title class_">Promise</span>&lt;<span class="built_in">void</span>&gt; &#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="comment">// 获取纯文本并解析成 json 数据</span></span><br><span class="line">    <span class="keyword">const</span> json = lockfile.<span class="title function_">parse</span>(<span class="variable language_">document</span>.<span class="title function_">getText</span>()).<span class="property">object</span>;</span><br><span class="line">    <span class="comment">// 将 JSON 字符串传递给 HTML 拼装方法展示</span></span><br><span class="line">    webviewPanel.<span class="property">webview</span>.<span class="property">html</span> = <span class="variable language_">this</span>.<span class="title function_">getHtmlForWebview</span>(<span class="title class_">JSON</span>.<span class="title function_">stringify</span>(json));</span><br><span class="line">    ...</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">private</span> <span class="title function_">getHtmlForWebview</span>(<span class="attr">json</span>: <span class="built_in">string</span>): <span class="built_in">string</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">`</span></span><br><span class="line"><span class="string">        &lt;!DOCTYPE html&gt;</span></span><br><span class="line"><span class="string">        &lt;html lang=&quot;en&quot;&gt;</span></span><br><span class="line"><span class="string">        &lt;head&gt;</span></span><br><span class="line"><span class="string">            &lt;meta charset=&quot;UTF-8&quot;&gt;</span></span><br><span class="line"><span class="string">            &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;</span></span><br><span class="line"><span class="string">            &lt;title&gt;Cat Coding&lt;/title&gt;</span></span><br><span class="line"><span class="string">        &lt;/head&gt;</span></span><br><span class="line"><span class="string">        &lt;body&gt;</span></span><br><span class="line"><span class="string">          &lt;h1&gt;JSON 数据&lt;/h1&gt;</span></span><br><span class="line"><span class="string">          &lt;p&gt;<span class="subst">$&#123;json&#125;</span>&lt;/p&gt;</span></span><br><span class="line"><span class="string">        &lt;/body&gt;</span></span><br><span class="line"><span class="string">        &lt;/html&gt;</span></span><br><span class="line"><span class="string">    `</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>效果如下：</p><p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/979363c7be8c42d59e2aa564cf687c26~tplv-k3u1fbpfcp-watermark.image" alt="Kapture 2021-09-14 at 18.59.12.gif"></p><h3 id="webview-集成-umijs"><a href="#webview-集成-umijs" class="headerlink" title="webview 集成 umijs"></a>webview 集成 umijs</h3><p>参考 <a href="https://juejin.cn/post/7000589186898231333/#heading-9">《# 从零开发一款基于 webview 的 vscode 扩展》</a> 和 <a href="https://github.com/youngjuning/vscode-juejin-me">vscode-juejin-me</a> 初始化 umijs 项目并做一些修剪适配工作。然后使用 <code>@luozhu/vscode-utils</code> 的 <code>getUmiHTMLContent</code> 方法获取 HTML 内容：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">webviewPanel.<span class="property">webview</span>.<span class="property">html</span> = <span class="title function_">getUmiHTMLContent</span>(<span class="variable language_">this</span>.<span class="property">context</span>, webviewPanel, &#123;</span><br><span class="line">  <span class="attr">title</span>: <span class="string">&#x27;Yarn Lock Preview&#x27;</span>,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="webview-内容更新"><a href="#webview-内容更新" class="headerlink" title="webview 内容更新"></a>webview 内容更新</h3><p>集成 umijs 后我们通过 <code>webviewPanel.webview.html</code> 设置的初始内容是空的。如果我们想要将文本传递从插件传递到 webview，我们需要进行一次通信。VS Code 中由于种种限制造成了编写通信的代码很繁琐。基本依赖 webview 的插件都会将通信机制给封装了。我这里的封装思路是借鉴了 <a href="https://www.npmjs.com/package/js-channel">js-channel</a> 实现了一个使用起来心智负担最小的 <a href="https://github.com/youngjuning/luozhu/tree/main/packages/vscode-channel#readme">@luozhu&#x2F;vscode-channel</a>。借助这个工具我们可以很方便地实现更新 webview 的操作：</p><p><strong>插件侧发送更新消息：</strong></p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">Channel</span> <span class="keyword">from</span> <span class="string">&#x27;@luozhu/vscode-channel&#x27;</span>;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">YarnLockEditorProvider</span> <span class="keyword">implements</span> vscode.<span class="property">CustomTextEditorProvider</span> &#123;</span><br><span class="line">  <span class="keyword">async</span> <span class="title function_">resolveCustomTextEditor</span>(</span><br><span class="line">    <span class="attr">document</span>: vscode.<span class="property">TextDocument</span>,</span><br><span class="line">    <span class="attr">webviewPanel</span>: vscode.<span class="property">WebviewPanel</span>,</span><br><span class="line">    <span class="attr">_token</span>: vscode.<span class="property">CancellationToken</span></span><br><span class="line">  ): <span class="title class_">Promise</span>&lt;<span class="built_in">void</span>&gt; &#123;</span><br><span class="line">    <span class="comment">// 给 webview 设置初始内容</span></span><br><span class="line">    webviewPanel.<span class="property">webview</span>.<span class="property">options</span> = &#123;</span><br><span class="line">      <span class="attr">enableScripts</span>: <span class="literal">true</span>,</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    webviewPanel.<span class="property">webview</span>.<span class="property">html</span> = <span class="title function_">getUmiHTMLContent</span>(<span class="variable language_">this</span>.<span class="property">context</span>, webviewPanel, &#123;</span><br><span class="line">      <span class="attr">title</span>: <span class="string">&#x27;Yarn Lock Preview&#x27;</span>,</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 初始化一个 channel 对象</span></span><br><span class="line">    <span class="keyword">const</span> channel = <span class="keyword">new</span> <span class="title class_">Channel</span>(<span class="variable language_">this</span>.<span class="property">context</span>, webviewPanel);</span><br><span class="line">    <span class="keyword">const</span> json = lockfile.<span class="title function_">parse</span>(<span class="variable language_">document</span>.<span class="title function_">getText</span>()).<span class="property">object</span>;</span><br><span class="line">    <span class="comment">// 触发 updateWebview 事件，并将文本作为参数传入</span></span><br><span class="line">    channel.<span class="title function_">call</span>(<span class="string">&#x27;updateWebview&#x27;</span>, json);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>webview 侧监听消息：</strong></p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">Channel</span> <span class="keyword">from</span> <span class="string">&#x27;@luozhu/vscode-channel&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">HomePage</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [data, setData] = <span class="title class_">React</span>.<span class="title function_">useState</span>(&#123;&#125;);</span><br><span class="line">  <span class="title class_">React</span>.<span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    channel.<span class="title function_">bind</span>(<span class="string">&#x27;updateWebview&#x27;</span>, <span class="function"><span class="params">message</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="title function_">setData</span>(message.<span class="property">params</span>);</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;,[])</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="文本内容同步"><a href="#文本内容同步" class="headerlink" title="文本内容同步"></a>文本内容同步</h3><p>前面一章我们实现了初始内容的同步，但是内容不是一成不变的，<code>yarn.lock</code> 很可能会变化，这时候我们也需要相应地更新我们内容，这个需求我们需要借助 <code>vscode.workspace.onDidChangeTextDocument</code> 事件监听来实现：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 仍然是在 resolveCustomTextEditor 方法中实现</span></span><br><span class="line"><span class="comment">// 由于需要对 json 数据处理和重复调用更新方法，封装了此方法</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">updateWebview</span>(<span class="params">textDocument: vscode.TextDocument</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> json = lockfile.<span class="title function_">parse</span>(textDocument.<span class="title function_">getText</span>());</span><br><span class="line">  <span class="keyword">switch</span> (json.<span class="property">type</span>) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;merge&#x27;</span>:</span><br><span class="line">      <span class="comment">// <span class="doctag">TODO:</span> 处理 merge type</span></span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;conflict&#x27;</span>:</span><br><span class="line">      <span class="comment">// <span class="doctag">TODO:</span> 处理 conflict type</span></span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="attr">default</span>:</span><br><span class="line">      json = json.<span class="property">object</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  channel.<span class="title function_">call</span>(<span class="string">&#x27;updateWebview&#x27;</span>, json);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 注册钩子事件处理程序，这样我们就可以使 webview 与文本文档同步。</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// 文本文件作为我们的模型，所以我们必须将文件中的变化同步到我们的编辑器。</span></span><br><span class="line"><span class="comment">// 请记住，一个文本文件也可以在多个自定义编辑器之间共享（例如，当你分割一个自定义编辑器时就会发生这种情况）。</span></span><br><span class="line"><span class="keyword">const</span> changeDocumentSubscription = vscode.<span class="property">workspace</span>.<span class="title function_">onDidChangeTextDocument</span>(<span class="function"><span class="params">e</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (e.<span class="property">document</span>.<span class="property">uri</span>.<span class="title function_">toString</span>() === <span class="variable language_">document</span>.<span class="property">uri</span>.<span class="title function_">toString</span>()) &#123;</span><br><span class="line">    <span class="title function_">updateWebview</span>(e.<span class="property">document</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">// 确保当我们的编辑器关闭时，移除了监听器。</span></span><br><span class="line">webviewPanel.<span class="title function_">onDidDispose</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  changeDocumentSubscription.<span class="title function_">dispose</span>();</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="实现可搜索的-JSON-View"><a href="#实现可搜索的-JSON-View" class="headerlink" title="实现可搜索的 JSON View"></a>实现可搜索的 JSON View</h3><p>搜索实现不复杂，大家感兴趣可以直接阅读源码，UI 用的是 umi 自带的 antd。JSON View 我选用了 <code>react-json-view</code>，这个有几点比较有意思可以讲一下。</p><ol><li>数据量过大时渲染性能差需要判断是否折叠</li></ol><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">ReactJson</span> <span class="keyword">from</span> <span class="string">&#x27;react-json-view&#x27;</span>;</span><br><span class="line">...</span><br><span class="line">&lt;<span class="title class_">ReactJson</span></span><br><span class="line">  shouldCollapse=&#123;<span class="function"><span class="params">filed</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 除了根目录都折叠</span></span><br><span class="line">    <span class="keyword">if</span> (filed.<span class="property">name</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">  &#125;&#125;</span><br><span class="line">/&gt;</span><br></pre></td></tr></table></figure><ol start="2"><li>样式符合当前主题</li></ol><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">ReactJson</span> <span class="keyword">from</span> <span class="string">&#x27;react-json-view&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">getCssVar</span> = (<span class="params">cssVar: <span class="built_in">string</span></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> htmlStyle = <span class="variable language_">document</span>.<span class="property">documentElement</span>.<span class="property">style</span>;</span><br><span class="line">  <span class="keyword">return</span> htmlStyle.<span class="title function_">getPropertyValue</span>(cssVar).<span class="title function_">trim</span>();</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line">&lt;<span class="title class_">ReactJson</span></span><br><span class="line">  style=&#123;&#123;</span><br><span class="line">    <span class="attr">backgroundColor</span>: <span class="title function_">getCssVar</span>(<span class="string">&#x27;--vscode-editor-background&#x27;</span>),</span><br><span class="line">    <span class="attr">fontSize</span>: <span class="title function_">getCssVar</span>(<span class="string">&#x27;--vscode-editor-font-size&#x27;</span>),</span><br><span class="line">  &#125;&#125;</span><br><span class="line">/&gt;</span><br></pre></td></tr></table></figure><ol start="3"><li>亮暗主题适配</li></ol><p>完美的适配肯定是要随着主题的更改切换 ReactJson 的主题，我们需要在插件侧发起通知：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 初始主题</span></span><br><span class="line">channel.<span class="title function_">call</span>(<span class="string">&#x27;updateColorTheme&#x27;</span>, vscode.<span class="property">window</span>.<span class="property">activeColorTheme</span>);</span><br><span class="line"><span class="comment">// 监听主题改变事件</span></span><br><span class="line">vscode.<span class="property">window</span>.<span class="title function_">onDidChangeActiveColorTheme</span>(<span class="function"><span class="params">colorTheme</span> =&gt;</span> &#123;</span><br><span class="line">  channel.<span class="title function_">call</span>(<span class="string">&#x27;updateColorTheme&#x27;</span>, colorTheme);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>然后在 webview 侧监听 <code>updateColorTheme</code> 事件：</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> [theme, setTheme] = <span class="title class_">React</span>.<span class="property">useState</span>&lt;<span class="title class_">ThemeKeys</span>&gt;();</span><br><span class="line">...</span><br><span class="line"><span class="title class_">React</span>.<span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">channel.<span class="title function_">bind</span>(<span class="string">&#x27;updateColorTheme&#x27;</span>, <span class="function"><span class="params">message</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; kind &#125; = message.<span class="property">params</span>;</span><br><span class="line">  <span class="title function_">setTheme</span>(kind === <span class="number">1</span> ? <span class="string">&#x27;rjv-default&#x27;</span> : <span class="string">&#x27;monokai&#x27;</span>);</span><br><span class="line">&#125;);</span><br><span class="line">&#125;, []);</span><br><span class="line">...</span><br><span class="line">&lt;<span class="title class_">ReactJson</span></span><br><span class="line">  theme=&#123;theme&#125;</span><br><span class="line">/&gt;</span><br></pre></td></tr></table></figure><p>文章开头已经演示了用户界面，这里放一张黑色主题效果吧：</p><p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8449b0b270aa423da2ede94f4ad10805~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><h2 id="涉及-API-附录"><a href="#涉及-API-附录" class="headerlink" title="涉及 API 附录"></a>涉及 API 附录</h2><p>VS Code 插件开发涉及知识点比较多，每种插件类型都有自己的套路和 API。基于关注分离原则，这里列出了和自定义编辑器相关的一些 API，方便大家以及未来的我查阅。</p><h3 id="vscode-commands-registerCommand"><a href="#vscode-commands-registerCommand" class="headerlink" title="vscode.commands.registerCommand"></a>vscode.commands.registerCommand</h3><p>注册一个可以被键映射、菜单项、动作或直接调用的命令。用一个现有的命令标识符注册两次一个命令将导致错误。</p><h3 id="vscode-TextDocument"><a href="#vscode-TextDocument" class="headerlink" title="vscode.TextDocument"></a>vscode.TextDocument</h3><h3 id="vscode-window-registerCustomEditorProvider"><a href="#vscode-window-registerCustomEditorProvider" class="headerlink" title="vscode.window.registerCustomEditorProvider"></a>vscode.window.registerCustomEditorProvider</h3><p>为 <code>customEditors</code> 扩展功能点贡献的 <code>viewType</code> 注册一个自定义编辑器的 Provider。</p><p>当打开一个自定义编辑器被时，会触发一个 <code>onCustomEditor:viewType</code> 活动事件。你的扩展必须为 <code>viewType</code> 注册一个 <code>CustomTextEditorProvider</code>、<code>CustomReadonlyEditor</code>  或 <code>CustomEditorProvider</code> 作为激活的一部分。</p><h3 id="vscode-CustomTextEditorProvider"><a href="#vscode-CustomTextEditorProvider" class="headerlink" title="vscode.CustomTextEditorProvider"></a>vscode.CustomTextEditorProvider</h3><p>基于文本的自定义编辑器 provider。</p><p>基于文本的自定义编辑器使用 TextDocument 作为其数据模型。因为它允许编辑器处理许多常见的操作，如撤销和备份。provider 负责在 webview 和 TextDocument 之间同步文本变化。</p><h4 id="CustomTextEditorProvider-resolveCustomTextEditor"><a href="#CustomTextEditorProvider-resolveCustomTextEditor" class="headerlink" title="CustomTextEditorProvider.resolveCustomTextEditor"></a>CustomTextEditorProvider.resolveCustomTextEditor</h4><p>根据给定文本资源解析一个自定义编辑器。该方法将在用户第一次为 <code>CustomTextEditorProvider</code> 打开一个资源时，或者他们使用这个 <code>CustomTextEditorProvider</code> 重新打开已经存在的编辑器时被调用。</p><h3 id="vscode-CustomReadonlyEditorProvider"><a href="#vscode-CustomReadonlyEditorProvider" class="headerlink" title="vscode.CustomReadonlyEditorProvider"></a>vscode.CustomReadonlyEditorProvider</h3><p>使用自定义文档模型的只读自定义编辑器 Provider。</p><p>自定义只读编辑器使用 <code>CustomDocument</code>，而不是 <code>TextDocument</code>。</p><p>当处理二进制文件或者更复杂的场景时，你应该使用这个类型的自定义编辑器。简单的基于文本的文档请使用 <code>CustomTextEditorProvider</code>。</p><h3 id="vscode-CustomEditorProvider"><a href="#vscode-CustomEditorProvider" class="headerlink" title="vscode.CustomEditorProvider"></a>vscode.CustomEditorProvider</h3><p>使用自定义文档模型的可编辑自定义编辑器的 provider。</p><p>自定义编辑器使用 <code>CustomDocument</code>，而不是 <code>TextDocument</code>。这使得扩展程序可以完全控制编辑、保存和备份等操作。</p><p>当处理二进制文件或者更复杂的场景时，你应该使用这个类型的自定义编辑器。简单的基于文本的文档请使用 <code>CustomTextEditorProvider</code>。</p><h3 id="vscode-WebviewPanel"><a href="#vscode-WebviewPanel" class="headerlink" title="vscode.WebviewPanel"></a>vscode.WebviewPanel</h3><p>一个包含 webview 的面板。</p><blockquote><p>本文首发于 <a href="https://juejin.cn/user/325111174662855/posts">掘金专栏</a>，同步于 <a href="https://blog.zisheng.pro/">紫升的博客</a> 和公众号 <a href="https://cdn.jsdelivr.net/gh/youngjuning/images/20210418112129.jpeg">紫升早茶馆</a>。</p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/1bbed3d2a249/</id>
    <link href="https://blog.zisheng.pro/posts/1bbed3d2a249/"/>
    <published>2023-02-26T05:56:50.000Z</published>
    <summary>人们总是喜欢炫耀自己的新技能，紫升也不例外，毕竟新技能需要大量的实战练习。在入门了 VS Code 扩展开发后，遇到开发痛点我就习惯地用 VS Code 插件的思路去解决。不过大多扩展 idea 都已经名花有主，直到我想到可以将 yarn.lock 文件可视化并搜索相关插件无果后，我知道机会来了。</summary>
    <title>如何开发一款 VS Code yarn.lock 预览插件</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="Google" scheme="https://blog.zisheng.pro/categories/Google/"/>
    <category term="Adsense" scheme="https://blog.zisheng.pro/categories/Google/Adsense/"/>
    <category term="站点运营" scheme="https://blog.zisheng.pro/categories/%E7%AB%99%E7%82%B9%E8%BF%90%E8%90%A5/"/>
    <category term="广告" scheme="https://blog.zisheng.pro/categories/%E7%AB%99%E7%82%B9%E8%BF%90%E8%90%A5/%E5%B9%BF%E5%91%8A/"/>
    <category term="google adsense" scheme="https://blog.zisheng.pro/tags/google-adsense/"/>
    <category term="google adsense cpc" scheme="https://blog.zisheng.pro/tags/google-adsense-cpc/"/>
    <category term="google adsense cpc 优化" scheme="https://blog.zisheng.pro/tags/google-adsense-cpc-%E4%BC%98%E5%8C%96/"/>
    <category term="优化 google adsense cpc" scheme="https://blog.zisheng.pro/tags/%E4%BC%98%E5%8C%96-google-adsense-cpc/"/>
    <content>
      <![CDATA[<blockquote><p>2025 年更新：RPM 同样适用！！！</p></blockquote><p>作为一个技术博客的博主，我一直在寻找提高 Google Adsense CPC 的方法，因为 CPC 是 Adsense 的收入来源，所以 CPC 越高，收入越多。本文就将介绍如何提高 Google Adsense CPC。</p><h2 id="CPC-是什么"><a href="#CPC-是什么" class="headerlink" title="CPC 是什么"></a>CPC 是什么</h2><p>每次点击费用 (CPC，Cost Per Click) 是每次用户点击您的广告时您赚取的金额，即单价。任何广告的 CPC 均由广告商确定；一些广告商可能愿意为每次点击支付比其他人更多的费用，这取决于他们宣传的内容。</p><h3 id="CTR-vs-CPC"><a href="#CTR-vs-CPC" class="headerlink" title="CTR vs CPC"></a>CTR vs CPC</h3><p>对于标准广告，广告点击率 (CTR，Clickthrough Rate) 是指广告的点击次数与其展示次数的比值。例如，如果您的广告通过 1000 次广告展示获得了 5 次点击，则您的广告点击率为 0.5%。</p><p>CTR 固然重要，但是如果你的单价很低，那么你的收入也会很低。因此，某种意义上来说 CPC 的优化是比 CTR 更紧迫的，前期没有大流量的情况下，CPC 尤其重要。</p><h2 id="影响-CPC-的几个因素"><a href="#影响-CPC-的几个因素" class="headerlink" title="影响 CPC 的几个因素"></a>影响 CPC 的几个因素</h2><ul><li>市场趋势：比如学生暑假、雨伞卖家、羽绒服等等。你可以通过 Google Trends 和 Google Adsense 的报告来查看市场趋势。</li><li>屏蔽控制：可以屏蔽一些敏感类别，注意不能屏蔽太多，否则会影响广告收入。</li><li>广告单元尺寸：将固定尺寸的广告单元替换为自适应尺寸的广告单元。<br><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1677292096541.png" alt="Google Adsense 广告单元尺寸"></li><li>主题(关键字)：你要选择一些高CPC的主题去经营你的博客。</li><li>内容：你必须写一些可以回答读者查询的内容。因此，在撰写博客之前，请尝试查找读者在互联网上搜索的内容，然后尝试向他们提供解决方案。请记住，您应该始终直接与读者互动。</li><li>区域：可以提高单价(CPC)的最重要因素之一就是您要定位的国家&#x2F;地区。例如，点击来自美国的广告可以支付高达2美元到3美元的费用，点击来自印度的同一广告最终只能支付20到30美分。</li><li>图文形式：图片广告在点击率较高(CTR)方面表现较好，但每次点击费用(CPC)不如文字广告效果好。</li><li>位置：在博文中放置 2 个广告，在帖子外放置 1 个广告。至于放置内部然后在标题下方的博客顶部放置一个广告，并在博客帖子之间放置第二个广告。剩余的一个广告可以放在您帖子外面的右侧。最高点击次数和更高每次点击费用将来自博客帖子中的 2 个广告，尤其是在顶部。</li></ul><h2 id="高-CPC-关键词"><a href="#高-CPC-关键词" class="headerlink" title="高 CPC 关键词"></a>高 CPC 关键词</h2><ol><li>保险（Insurance）：$61 CPC</li><li>燃气&#x2F;电力（Gas&#x2F;Electricity）：$58 CPC</li><li>贷款（Loans）：$50 CPC</li><li>律师（Attorney）：$48 CPC</li><li>抵押（Mortgage）：$46 CPC</li><li>律师（Lawyer）：$42 CPC</li><li>捐助（Donate）：$42 CPC</li><li>电话会议（Conference Call）：$42 CPC</li></ol><p>上面列的不一定符合博客的主题，这里列出一些其他高回报率的主题(关键字)，我按照CPC递减的顺序进行排列：</p><ul><li>域名 – 互联网域名博客像GoDaddy，Namecheap 等。它支付最高的CPC；</li><li>电子产品(Gadgets) – 比如苹果类的科技电子产品</li><li>GOOGLE – GOOGLE产品</li><li>微软产品 – 微软办公类产品</li><li>数字加密货币</li></ul><blockquote><p>注意：约会和浪漫 CPC 最低</p></blockquote><h2 id="屏蔽控制功能"><a href="#屏蔽控制功能" class="headerlink" title="屏蔽控制功能"></a>屏蔽控制功能</h2><p>善用该功能可以确保所展示的广告不会影响您的品牌形象，这些工具可以帮助您在网站上提供最佳广告体验。我们最关心当然还是如何帮助提高 CPC。</p><h3 id="广告客户网址"><a href="#广告客户网址" class="headerlink" title="广告客户网址"></a>广告客户网址</h3><p>该功能用以屏蔽由具体广告客户投放的广告，可以通过下面的方式访问：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1677287023394.png" alt="Google Adsense 广告客户网址"></p><p>然后将下面的网址添加到屏蔽列表中：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1677287190848.png" alt="Google Adsense 低 CPC 广告客户网站"></p><div class="note primary modern"><p>数据全是自己手动统计，如有需要可以加我微信：<code>AAIII1689888</code>（备注 CPC），发 <code>$10</code> 红包获取。</p></div><h3 id="敏感类别"><a href="#敏感类别" class="headerlink" title="敏感类别"></a>敏感类别</h3><p>该功能用以根据您的受众群体，管理您网站上最适合展示的广告类型，禁用不适合您网站的广告类型。例如，您的网站可能包含有关性内容，但您不希望在网站上显示有关性广告。您可以在此处禁用这些广告类型。</p><p>这样做不仅可以帮助您遵守法律法规，还可以帮助您提高广告收入，下图是我禁用的一些类别。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1677288027340.png" alt="Google Adsense 敏感类别"></p><h3 id="常规类别"><a href="#常规类别" class="headerlink" title="常规类别"></a>常规类别</h3><p>屏蔽较常规的广告类别。注意，这样做可能会导致收入减少。但是也有一些大家公认的低 CPC 类别，建议屏蔽掉：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/202311022322047.png" alt="Google Adsense 常规类别"></p><p>可以看到健康类别展示占比 4%，收入占比 3.7%，虽然名字很健康，但是效益比较差。</p><h3 id="广告联盟"><a href="#广告联盟" class="headerlink" title="广告联盟"></a>广告联盟</h3><h4 id="低-CPC-广告联盟"><a href="#低-CPC-广告联盟" class="headerlink" title="低 CPC 广告联盟"></a>低 CPC 广告联盟</h4><p>你可以屏蔽不适合自己品牌的第三方广告联盟，但也有一些公认的低 CPC 广告联盟，建议屏蔽掉：</p><div class="note primary modern"><p>数据全是自己手动统计，如有需要可以加我微信：<code>AAIII1689888</code>（备注 CPC），发 <code>$20</code> 红包获取。</p></div><p>除此之外你还可以通过分析广告网络报告来决定是否屏蔽某个广告联盟。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1677288686112.png" alt="Google Adsense 广告网络"></p><p>屏蔽原则是：低 CPC 的广告联盟，低 CTR 的广告联盟，低收入的广告联盟，都可以屏蔽掉。</p><h4 id="高-CPC-广告联盟"><a href="#高-CPC-广告联盟" class="headerlink" title="高 CPC 广告联盟"></a>高 CPC 广告联盟</h4><details class="toggle" ><summary class="toggle-button" style="">高 CPC 广告联盟</summary><div class="toggle-content"><ol><li>HearstDigitalMedia：$10.80</li><li>InPowered：$10.19</li><li>Kapanlagi：$10.13</li><li>Yandex.ru：$10.11</li><li>OpenInventory：$9.24</li><li>WebAds：$8.71</li><li>MadhouseMobile：$8.12</li><li>CPXInteractive：$7.75</li><li>GuruMedia：$7.41</li><li>AdvertisingAlliance：$6.59</li><li>CompareGroup：$6.33</li><li>transcosmos：$6.09</li><li>eHealthcareSolutions：$6.06</li><li>KPISolutions：$5.70</li><li>ScaleOut：$5.65</li><li>Bannerconnect：$5.43</li><li>IndependentTraveler：$5.40</li><li>Quantcast：$5.29</li><li>MediaScience：$5.17</li><li>AdKnowledge：$5.11</li><li>BaronsMedia：$4.91</li><li>SpecificMedia：$4.81</li><li>InterCLICK：$4.73</li><li>Dun&amp;Bradstreet：$4.70</li><li>AdvantageMedia：$4.45</li><li>BridgeMarketing：$4.41</li><li>Belgacom：$4.39</li><li>Connexity：$4.32</li><li>HealthiNation：$4.30</li><li>AdvertisingTechnologies：$4.28</li><li>remerge：$4.19</li><li>MediaDecision：$4.17</li><li>PerformanceAdvertising：$4.04</li><li>AdGear：$3.93</li><li>BusinessAd：$3.93</li><li>CyberCommunications：$3.69</li><li>Motricity：$3.66</li><li>TruEffect：$3.60</li><li>TCSBank：$3.46</li><li>MediaMind：$3.43</li><li>GroupM：$3.36</li><li>Videology：$3.29</li><li>SEEKCommercial：$3.26</li><li>Proclivity：$3.15</li><li>AdvanceMedia：$3.13</li><li>Neustar：$3.11</li><li>iPromote：$3.10</li><li>AdLabs：$3.09</li><li>InteractiveMediaSales：$3.07</li><li>Technorati：$3.05</li><li>MetaNetwork：$3.02</li><li>Yandex：$2.95</li><li>Adform：$2.90</li><li>REAGroup：$2.86</li><li>Fiksu：$2.84</li><li>ActiveAgent：$2.81</li><li>StartApp：$2.77</li><li>Didit：$2.74</li><li>Weborama：$2.74</li><li>TremorMedia：$2.72</li><li>PlatformOne：$2.71</li><li>Sogou：$2.63</li><li>Hybrid：$2.62</li><li>DownstreamMarketing：$2.61</li><li>PCWorldCommunications：$2.58</li><li>Digitize&#x2F;Medialink1.USAccount：$2.57</li><li>ARAnet：$2.54</li><li>UnrulyMedia：$2.51</li><li>OpenX：$2.49</li><li>OutsideHub：$2.45</li><li>CyberAgent：$2.44</li><li>AutoScout24：$2.39</li><li>CollectiveMedia：$2.39</li><li>MediaCity：$2.38</li><li>MediaNewsGroup：$2.35</li><li>eMar：$2.30</li><li>CarWale：$2.29</li><li>VideoInternational：$2.28</li><li>InternetBrands：$2.24</li><li>Undertone：$2.21</li><li>CirrusMedia：$2.17</li><li>AgendaMedia：$2.14</li><li>Decisive：$2.14</li><li>IgniteVideo：$2.14</li><li>Twenga：$2.04</li><li>AMNET：$2.01</li><li>PlatformID：$2.01</li><li>LookSmart：$2.00</li><li>NirvanaInformation：$1.99</li><li>Innity：$1.93</li><li>BurstMedia：$1.91</li><li>CBSInteractive：$1.89</li><li>Tencent：$1.89</li><li>Demandbase：$1.88</li><li>MediaWorks：$1.88</li><li>MobileTheory：$1.88</li><li>iContext：$1.87</li><li>Adience：$1.85</li><li>Baidu：$1.84</li><li>Affinity：$1.80</li><li>Inneractive：$1.79</li><li>DigitalResponse：$1.78</li><li>MarsTechnologies：$1.77</li><li>IQM：$1.75</li><li>Rubedo：$1.72</li><li>Magnetic：$1.70</li><li>InspiredMobile：$1.68</li><li>Timesinternet：$1.67</li><li>Torrential：$1.65</li><li>Scripps：$1.60</li><li>SteelHouse：$1.60</li><li>DennisPublishing：$1.59</li><li>PRIMEDIA：$1.58</li><li>FutureUS：$1.57</li><li>BreakMedia：$1.56</li><li>CareerOne：$1.55</li><li>SiftMedia：$1.55</li><li>Trovit：$1.54</li><li>evania：$1.53</li><li>OneScreen：$1.52</li><li>Conversant：$1.51</li><li>Netmining：$1.48</li><li>Carsales：$1.47</li><li>OzoneMedia：$1.46</li><li>AdView：$1.45</li><li>Glimr：$1.45</li><li>One97：$1.45</li><li>Xad：$1.45</li><li>xBid：$1.45</li><li>DemandMedia：$1.44</li><li>ConradAdvertising：$1.43</li><li>VPON：$1.43</li><li>Digitouch：$1.42</li><li>DynamicVideo：$1.41</li><li>Quartic：$1.41</li><li>Pilot：$1.38</li><li>ikoo：$1.36</li><li>FinancialTimes：$1.34</li><li>MantaMedia：$1.34</li><li>YuMe：$1.34</li><li>Ritorno：$1.31</li><li>Verizonw&#x2F;DBM：$1.31</li><li>Adjust：$1.29</li><li>PickmeUp：$1.29</li><li>Unique：$1.29</li><li>TheReachGroup：$1.27</li><li>AdPepper：$1.26</li><li>BlackBoxMedia：$1.26</li><li>Lotame：$1.26</li><li>NetEdge：$1.26</li><li>Voetbal：$1.26</li><li>eprofessional：$1.25</li><li>PriceRunner：$1.24</li><li>Begun：$1.23</li><li>VoodooVideo：$1.22</li><li>SGN：$1.20</li><li>InteractiveOne：$1.19</li><li>Wikia：$1.19</li><li>GREE：$1.17</li><li>Longtail：$1.17</li><li>Mediasmart：$1.17</li><li>AnswerMedia：$1.16</li><li>Pulse360：$1.16</li><li>Turn：$1.15</li><li>Emerse：$1.14</li><li>Microsoftw&#x2F;AppNexus：$1.13</li><li>ZAPR：$1.13</li><li>SmartMedia：$1.11</li><li>Sojern：$1.11</li><li>Micro：$1.10</li><li>Allrecipes.com：$1.06</li><li>TVNZ：$1.06</li><li>OneSpot：$1.05</li><li>Smartclip：$1.05</li><li>YD：$1.05</li><li>eType：$1.04</li><li>IMT：$1.04</li><li>Bizo：$1.02</li><li>DataLabUSA：$1.01</li><li>SET.tv：$1.01</li><li>Merkle：$0.99</li><li>Tekka：$0.99</li><li>TheBridge：$0.99</li><li>AdMedia：$0.96</li><li>Housing.com：$0.96</li><li>Metacafe：$0.95</li><li>BellCanada：$0.94</li><li>HarvestDigital：$0.93</li><li>advancedSTORE：$0.92</li><li>IgnitionOne：$0.91</li><li>RocketFuel：$0.91</li><li>Adwell：$0.90</li><li>BEHE：$0.89</li><li>Kargo：$0.89</li><li>Theorem：$0.88</li><li>TapValue：$0.87</li><li>DataMind：$0.86</li><li>SalesSpider：$0.86</li><li>India.com：$0.85</li><li>Suite66：$0.85</li><li>HealthGrades：$0.83</li><li>Philly.com：$0.83</li><li>Wavenet：$0.83</li><li>OandaCorporation：$0.82</li><li>Trulia：$0.79</li><li>Bonnier：$0.78</li><li>Mixmarket：$0.77</li><li>RakutenUS：$0.76</li><li>Belo：$0.75</li><li>SheKnows：$0.75</li><li>Opera：$0.74</li><li>Papaya：$0.74</li><li>BBCw&#x2F;DBM：$0.73</li><li>Chango：$0.73</li><li>Pocketmath：$0.72</li><li>Hurra：$0.71</li><li>NetSeer：$0.71</li><li>Admeta：$0.70</li><li>Alveo：$0.69</li><li>ibibo：$0.69</li><li>WiseMedia：$0.69</li><li>Jubii：$0.68</li><li>Kadam：$0.68</li><li>Avocet：$0.67</li><li>DoublePositive：$0.67</li><li>MartiniMedia：$0.66</li><li>Rakuten：$0.64</li><li>Axonix：$0.63</li><li>TravelClick：$0.63</li><li>Yoyi：$0.62</li><li>ReduxMedia：$0.60</li><li>ComuneS&#x2F;A：$0.59</li><li>DigitalThrottle：$0.57</li><li>TribalFusion：$0.55</li><li>Batanga：$0.54</li><li>NewsInternational：$0.54</li><li>Xplosion：$0.54</li><li>FunBox：$0.53</li><li>Sokrati：$0.52</li><li>Ntree：$0.51</li><li>OrbitzWorldwide：$0.51</li><li>Rockerbox：$0.51</li><li>USMediaConsulting：$0.51</li><li>EJAM：$0.47</li><li>3Interactive：$0.46</li><li>AccountNow：$0.45</li><li>BabylonLimited：$0.44</li><li>ZiffDavis：$0.44</li><li>Populis：$0.43</li><li>Bridgewell：$0.42</li></ol></div></details><h2 id="广告审核中心"><a href="#广告审核中心" class="headerlink" title="广告审核中心"></a>广告审核中心</h2><p>为了保证广告的质量和效益，可以综合前文所述对广告进行审核，禁用不适合您网站的广告类型。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1677290992695.png" alt="Google Adsense 广告审核中心"></p><h2 id="广告尺寸和类型"><a href="#广告尺寸和类型" class="headerlink" title="广告尺寸和类型"></a>广告尺寸和类型</h2><p>如图，一定要选择让 Google 来为您优化移动广告的尺寸</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1684731891963.png" alt="广告尺寸和类型"></p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/729a93dae902/</id>
    <link href="https://blog.zisheng.pro/posts/729a93dae902/"/>
    <published>2023-02-24T15:58:16.000Z</published>
    <summary>作为一个技术博客的博主，我一直在寻找优化 Google Adsense CPC 并提高广告收入的方法，因为 CPC 是 Adsense 的收入来源，所以 CPC 越高，收入越多。本文就将介绍如何提高 Google Adsense CPC。</summary>
    <title>如何优化 Google Adsense CPC 提高广告收入</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="站点运营" scheme="https://blog.zisheng.pro/categories/%E7%AB%99%E7%82%B9%E8%BF%90%E8%90%A5/"/>
    <category term="hexo" scheme="https://blog.zisheng.pro/tags/hexo/"/>
    <category term="hexo-theme-butterfly" scheme="https://blog.zisheng.pro/tags/hexo-theme-butterfly/"/>
    <category term="魔改" scheme="https://blog.zisheng.pro/tags/%E9%AD%94%E6%94%B9/"/>
    <category term="美化" scheme="https://blog.zisheng.pro/tags/%E7%BE%8E%E5%8C%96/"/>
    <content>
      <![CDATA[<blockquote><p>当前魔改基于 hexo 6.3.0 和 hexo-theme-butterfly 4.7.0</p></blockquote><h2 id="patch-package-配置"><a href="#patch-package-配置" class="headerlink" title="patch-package 配置"></a>patch-package 配置</h2><p>patch-package 可以将你的魔改记录保存到 <code>patches</code> 文件夹下，方便下次更新主题后应用魔改。按照下面的代码修改 package.json 即可：</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  &quot;scripts&quot;: &#123;</span><br><span class="line"><span class="addition">+    &quot;postinstall&quot;: &quot;npx patch-package&quot;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果你想直接应用我的魔改，可以直接复制本文的代码到 <code>patches/hexo-theme-butterfly+4.7.0.patch</code></p><h1 id="两个小人"><a href="#两个小人" class="headerlink" title="两个小人"></a>两个小人</h1><div class="tabs"><div class="nav-tabs"><button type="button" class="tab active">改动点</button><button type="button" class="tab">效果预览</button></div><div class="tab-contents"><div class="tab-item-content active"><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">diff --git a/node_modules/hexo-theme-butterfly/layout/includes/widget/card_announcement.pug b/node_modules/hexo-theme-butterfly/layout/includes/widget/card_announcement.pug</span></span><br><span class="line"><span class="comment">index 9e63627..905342a 100644</span></span><br><span class="line"><span class="comment">--- a/node_modules/hexo-theme-butterfly/layout/includes/widget/card_announcement.pug</span></span><br><span class="line"><span class="comment">+++ b/node_modules/hexo-theme-butterfly/layout/includes/widget/card_announcement.pug</span></span><br><span class="line"><span class="meta">@@ -4,3 +4,4 @@</span> if theme.aside.card_announcement.enable</span><br><span class="line">       i.fas.fa-bullhorn.fa-shake</span><br><span class="line">       span= _p(&#x27;aside.card_announcement&#x27;)</span><br><span class="line">     .announcement_content!= theme.aside.card_announcement.content</span><br><span class="line"><span class="addition">+      .twopeople!= &#x27;&lt;div class=&quot;twopeople&quot;&gt;&lt;div class=&quot;container&quot; style=&quot;height:200px;&quot;&gt;&lt;canvas class=&quot;illo&quot; width=&quot;800&quot; height=&quot;800&quot; style=&quot;max-width: 200px; max-height: 200px; touch-action: none; width: 640px; height: 640px;&quot;&gt;&lt;/canvas&gt;&lt;/div&gt;&lt;script src=&quot;https://cdn.jsdelivr.net/gh/Justlovesmile/CDN/js/twopeople1.js&quot;&gt;&lt;/script&gt;&lt;script src=&quot;https://cdn.jsdelivr.net/gh/Justlovesmile/CDN/js/zdog.dist.js&quot;&gt;&lt;/script&gt;&lt;script id=&quot;rendered-js&quot; src=&quot;https://cdn.jsdelivr.net/gh/Justlovesmile/CDN/js/twopeople.js&quot;&gt;&lt;/script&gt;&lt;style&gt;.twopeople&#123;margin: 0;align-items: center;justify-content: center;text-align: center;&#125;canvas &#123;display: block;margin: 0 auto;cursor: move;&#125;&lt;/style&gt;&lt;/div&gt;&#x27;</span></span><br></pre></td></tr></table></figure></div><div class="tab-item-content"><p><img src="https://picbed.qunarzz.com/f3d661e2088ea4fd706709ee1024adce.png" alt="效果预览"></p></div></div><div class="tab-to-top"><button type="button" aria-label="scroll to top"><i class="fas fa-arrow-up"></i></button></div></div><blockquote><p>鸣谢 <a href="https://guole.fun/posts/butterfly-custom/">https://guole.fun/posts/butterfly-custom/</a></p></blockquote><h2 id="站点-Logo-圆角"><a href="#站点-Logo-圆角" class="headerlink" title="站点 Logo 圆角"></a>站点 Logo 圆角</h2><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">diff --git a/node_modules/hexo-theme-butterfly/source/css/_layout/head.styl b/node_modules/hexo-theme-butterfly/source/css/_layout/head.styl</span></span><br><span class="line"><span class="comment">index ade0db3..956a69e 100644</span></span><br><span class="line"><span class="comment">--- a/node_modules/hexo-theme-butterfly/source/css/_layout/head.styl</span></span><br><span class="line"><span class="comment">+++ b/node_modules/hexo-theme-butterfly/source/css/_layout/head.styl</span></span><br><span class="line"><span class="meta">@@ -289,6 +289,7 @@</span></span><br><span class="line">       margin-right: 6px</span><br><span class="line">       height: 36px</span><br><span class="line">       vertical-align: middle</span><br><span class="line"><span class="addition">+      border-radius: 50%</span></span><br><span class="line"></span><br><span class="line">   #toggle-menu</span><br><span class="line">     display: none</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/3f81e8c6ddd1/</id>
    <link href="https://blog.zisheng.pro/posts/3f81e8c6ddd1/"/>
    <published>2023-02-22T06:18:09.000Z</published>
    <summary>本文将介绍本站对 hexo-theme-butterfly 进行的一些美化和魔改。</summary>
    <title>hexo-theme-butterfly 魔改与美化</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="软考" scheme="https://blog.zisheng.pro/categories/%E8%BD%AF%E8%80%83/"/>
    <category term="信息系统项目管理师" scheme="https://blog.zisheng.pro/tags/%E4%BF%A1%E6%81%AF%E7%B3%BB%E7%BB%9F%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86%E5%B8%88/"/>
    <category term="软考高项" scheme="https://blog.zisheng.pro/tags/%E8%BD%AF%E8%80%83%E9%AB%98%E9%A1%B9/"/>
    <category term="信息化" scheme="https://blog.zisheng.pro/tags/%E4%BF%A1%E6%81%AF%E5%8C%96/"/>
    <category term="信息系统" scheme="https://blog.zisheng.pro/tags/%E4%BF%A1%E6%81%AF%E7%B3%BB%E7%BB%9F/"/>
    <category term="信息化和信息系统" scheme="https://blog.zisheng.pro/tags/%E4%BF%A1%E6%81%AF%E5%8C%96%E5%92%8C%E4%BF%A1%E6%81%AF%E7%B3%BB%E7%BB%9F/"/>
    <content>
      <![CDATA[<div class="note warning modern"><p>我在备考的时候教材换成了，也因为其他事情就中断了备考，等我调整好时间会继续更新的，如果你有什么问题可以在评论区留言，我会尽快回复的。</p></div><h2 id="信息系统与信息化"><a href="#信息系统与信息化" class="headerlink" title="信息系统与信息化"></a>信息系统与信息化</h2><h3 id="信息的基本概念"><a href="#信息的基本概念" class="headerlink" title="信息的基本概念"></a>信息的基本概念</h3><h4 id="信息的定义"><a href="#信息的定义" class="headerlink" title="信息的定义"></a>信息的定义</h4><p>信息就是用来消除不确定性的东西。</p><h4 id="信息的特征"><a href="#信息的特征" class="headerlink" title="信息的特征"></a>信息的特征</h4><ol><li><strong>客观性</strong>：信息是客观事物在人脑中的反映，而反映的对象则有主观和客观的区别，因此，信息可分为主观信息（例如，决策、指令和计划等）和客观信息（例如，国际形式、经济发展和一年四季等）。主观信息必然要转化成客观信息，例如，决策和计划等主观信息要转化成实际行动。</li><li><strong>普遍性</strong>：物质具有普遍性，信息属于物质，物质的普遍性决定了信息的普遍存在。</li><li><strong>无限性</strong>：客观世界是无限的，信息是客观事物&#x2F;世界在人脑中的反映，反映客观世界的信息自然也是无限的。无限性可分为两个层次，一是无限的事物产生无限的信息，即信息的总量是无限的；二是每个具体事物或有限个食物的集合所能产生的信息也可以是无限的。</li><li><strong>动态性</strong>：信息是随着时间的变化而变化的。</li><li><strong>相对性</strong>：不同的认识主体从同一事物中获取的信息及信息量可能是不同的。这会取决于认识主体的生长环境、教育、工作环境、工作性质等外界因素。</li><li><strong>依附性</strong>：信息的依附性可以从两个方面来解释，一方面，信息是客观世界的反映，任何信息必然由客观事物所产生，不存在无源的信息；另一方面，任何信息都要依附于一定的载体而存在，需要有物质的承担者，信息不能完全脱离物质而独立存在。</li><li><strong>变换性</strong>：信息通过处理可以实现变化或转换，使其形式和内容发生变化，以适应特定的需要。比较明显的是数据可视化分过程，实现信息从文字到表格，最终到图表的展示，通过变化，信息的可读性越来越强。</li><li><strong>传递性</strong>：信息在时间上的传递就是存储，在空间上的传递就是转移或扩散。</li><li><strong>层次性</strong>：客观世界是分层次的，反映它的信息也是分层次的。</li><li><strong>系统性</strong>：信息可以表示为一种合集，不同类别的信息可以形成不同的整体。因此，可以形成与现实世界相对应的信息系统。例如：经济领域中的商业系统和金融系统，自然界中的水利系统和生态系统等。</li><li><strong>转化性</strong>：信息的产生不能没有物质，信息的传递不能没有能量，但有效地使用信息，可以使信息转化为物质或能量。</li></ol><p><strong>信息质量属性⭐</strong></p><ol><li><strong>精确性</strong>：对事物状态描述的精准程度。</li><li><strong>完整性</strong>：对事物状态描述的全面程度，完整信息应包括所有重要事实。</li><li><strong>可靠性</strong>：指信息的来源、采集方法、传输过程是可以信任的，符合预期。</li><li><strong>及时性</strong>，指获得信息的时刻与事件发生时刻的间隔长短。</li><li><strong>经济性</strong>，指信息获取、传输带来的成本在可以接受的范围之内。</li><li><strong>可验证性</strong>：指信息的主要质量属性可以被证实或者证伪的程度。</li><li><strong>安全性</strong>：指在信息的生命周期中，信息可以被非授权访问的可能性，可能性越低，安全性越高。</li></ol><h4 id="信息的功能"><a href="#信息的功能" class="headerlink" title="信息的功能"></a>信息的功能</h4><p>无重要考点</p><h4 id="信息的传输模型⭐"><a href="#信息的传输模型⭐" class="headerlink" title="信息的传输模型⭐"></a>信息的传输模型⭐</h4><ul><li><strong>信源</strong>：产生信息的实体，信息产生后，由这个实体向外传播。</li><li><strong>信宿</strong>：信息的归宿或者接受者。</li><li><strong>信道</strong>：传送信息的通道。</li><li><strong>编码器</strong>：在信息论中是泛指所有变换信号的设备，实际上就是终端机的发送部分。它包括从信源到信道的所有设备，如量化器、压缩编码器、调制器等，使信源输出的信号转换成适用于信道传送的信号。</li><li><strong>译码器</strong>：译码器是编码器的逆变换设备，把信道上传来的信号（原始信号与噪声的叠加）转换成信宿能接受的信号。</li><li><strong>噪声</strong>：噪声可以理解为干扰。</li></ul><p><img src="https://picbed.qunarzz.com/771ed4f1600bfe927bc97c5f5e6e2512.png" alt="信息传输模型"></p><h3 id="信息系统的基本概念"><a href="#信息系统的基本概念" class="headerlink" title="信息系统的基本概念"></a>信息系统的基本概念</h3><h4 id="系统的特性"><a href="#系统的特性" class="headerlink" title="系统的特性"></a>系统的特性</h4><p><strong>总体特性</strong></p><ol><li><strong>目的性</strong>：定义一个系统、组成一个系统或者抽象出一个系统，都有明确的目标或者目的，目标性决定了系统的功能。</li><li><strong>整体性</strong>：系统是一个整体，元素是为了达到一定的目的，按照一定的原则，有序地排列起来组成系统，从而产生出系统的特定功能。</li><li><strong>层次性</strong>：系统化是由多个元素组成的，系统和元素是相对的概念。元素是相对于它所处的系统而言的。系统是从它包含元素的角度来看的，如果研究问题的角度变一变，系统就成为更高一级的元素，也成为子系统。</li><li><strong>稳定性</strong>：是规则的约束，系统的内部结构和秩序应该是可以预见的；系统的状态以及演化路径有限并能被预测；系统的功能发生作用导致的后果也是可以预估的。稳定性强的系统使得系统在受到外部作用的同时，内部结构和秩序仍然能够保持。</li><li><strong>突变性</strong>：突变性是指系统通过失稳，从一种状态进入另一种状态的一种剧烈变化过程，它是系统质变的一种基本形式。</li><li><strong>自组织性</strong>：开放系统在系统内外因素的作用下，自发组织起来，使系统从无序到有序，从低级有序到高级有序。</li><li><strong>相似性</strong>：系统具有同构和同态的性质，体现在系统结构、存在方式和演化过程具有共同性。系统具有相似性，根本原因在于世界的物质统一性。</li><li><strong>相关性</strong>：元素是可分的和相互联系的，组成系统的元素必须有明确的边界，可以与别的元素区分开来。另外，元素之间是相互联系的，不是哲学上所说的那种普遍联系，而是实实在在的、具体的联系。</li><li><strong>适应环境性</strong>：系统总处在一定的环境中，与环境发生相互作用。系统和环境之间总是在发生着一定的物质和能量交换。</li></ol><p><strong>突出特征</strong></p><ol><li>开放性：系统的开放性是指系统的可访问性。</li><li>脆弱性🔥：这个特性与系统的稳定性相应，即系统可能存在着丧失结构、功能、秩序的特性，这个特性往往是隐藏不易被外界感知的。</li><li>健壮性：系统具有能够抵御出现非预期状态的特性称为健壮性，也称鲁棒性（robustness）。要求具有高可用性的信息系统，会采用冗余技术、容错技术、身份识别技术、可靠性技术等来抵御系统出现的非预期的状态，保持系统的稳定性。</li></ol><h4 id="信息系统定义"><a href="#信息系统定义" class="headerlink" title="信息系统定义"></a>信息系统定义</h4><p>简单地说，信息系统就是输入数据，通过加工处理，产生信息的系统。信息系统可以是手工的，也可以是计算机化的。</p><h4 id="信息系统集成"><a href="#信息系统集成" class="headerlink" title="信息系统集成"></a>信息系统集成</h4><p>采用现代管理理论（例如，软件工程、项目管理等）作为计划、设计、控制的方法论，将硬件、软件、数据库、网络等部件按照规划的结构和秩序，有机地整合一个有清晰边界的信息系统中，以达到既定系统的目标，这个过程称为信息系统集成。</p><h3 id="信息化的基本概念"><a href="#信息化的基本概念" class="headerlink" title="信息化的基本概念"></a>信息化的基本概念</h3><h4 id="信息化从“小”到“大”的五个层次⭐️"><a href="#信息化从“小”到“大”的五个层次⭐️" class="headerlink" title="信息化从“小”到“大”的五个层次⭐️"></a>信息化从“小”到“大”的五个层次⭐️</h4><ol><li><strong>产品信息化</strong></li><li><strong>企业信息化</strong></li><li><strong>产业信息化</strong></li><li><strong>国民经济信息化</strong></li><li><strong>社会生活信息化</strong></li></ol><h4 id="信息化的内涵🔥"><a href="#信息化的内涵🔥" class="headerlink" title="信息化的内涵🔥"></a>信息化的内涵🔥</h4><ol><li><strong>信息化的主体</strong>：<strong>全体社会成员</strong>，包括政府、企业、事业、团体和个人。</li><li><strong>时域</strong>：一个长期的过程</li><li><strong>空域</strong>：政治、经济、文化、军事和社会的一切领域</li><li><strong>手段</strong>：基于现代信息技术的先进社会生产工具</li><li><strong>途径</strong>：是创建信息时代的社会生产力，推动社会生产关系及社会上层建筑的改革</li><li><strong>目标</strong>：使国家的综合实力、社会的文明素质和人民的生活质量全面提升</li></ol><h4 id="国家级信息系统"><a href="#国家级信息系统" class="headerlink" title="国家级信息系统"></a>国家级信息系统</h4><ul><li>两网：是指政务内网和政务外网</li><li>一站：是指政府门户网站</li><li>四库：即建立人口、法人单位、空间地理和自然资源、宏观经济等四大基础数据库。</li><li>十二金</li></ul><h4 id="信息化的六要素🔥"><a href="#信息化的六要素🔥" class="headerlink" title="信息化的六要素🔥"></a>信息化的六要素🔥</h4><ol><li><strong>信息资源</strong>：信息资源的开发和利用是国家信息化的核心人任务，是国家信息化建设取得实效的关键，也是我国信息化的薄弱环节。（信息技术、设备、设施、信息生产者等）</li><li><strong>信息网络</strong>：信息网络是信息资源开发和利用的基础设施，包括电信网，广播电视和计算机网络。</li><li><strong>信息技术应用</strong>：是指把信息技术广泛应用于经济和社会各个领域，他直接反映了效率、效果、效益。信息技术应用是信息化体系六要素中的龙头，是国家信息化建设的主阵地，集中体现了国家信息化建设的需求与效益。</li><li><strong>信息技术与产业</strong>：是信息化的物质基础</li><li><strong>信息化人才</strong>：人才是信息化的成功之本</li><li><strong>信息化政策法规和标准规范</strong>：信息化政策和法规、标准、规范用于规范和协调信息化体系要素之间的关系，是国家信息化快速，有序，健康和持续发展的保障。</li></ol><p><img src="https://picbed.qunarzz.com/81eb01b74890743fd48386c1e2d6c876.png" alt="信息化的六要素"></p><h3 id="信息系统生命周期"><a href="#信息系统生命周期" class="headerlink" title="信息系统生命周期"></a>信息系统生命周期</h3><h4 id="4-大方面"><a href="#4-大方面" class="headerlink" title="4 大方面"></a>4 大方面</h4><ol><li>立项（系统规划）</li><li>开发（系统分析、系统设计、系统实施）</li><li>运维（运行维护）</li><li>消亡</li></ol><h4 id="5-小方面"><a href="#5-小方面" class="headerlink" title="5 小方面"></a>5 小方面</h4><ol><li>系统规划</li><li>系统分析</li><li>系统设计</li><li>系统实施</li><li>运行维护</li></ol><h2 id="信息系统开发方法"><a href="#信息系统开发方法" class="headerlink" title="信息系统开发方法"></a>信息系统开发方法</h2><h3 id="结构化方法"><a href="#结构化方法" class="headerlink" title="结构化方法"></a>结构化方法</h3><p><img src="https://picbed.qunarzz.com/b5330d132e19a9931bfb32134f234fdf.png" alt="孵小鸡"></p><h4 id="定义"><a href="#定义" class="headerlink" title="定义"></a>定义</h4><p>也称为生命周期法，是一种传统的信息系统开发方法。</p><h4 id="组成"><a href="#组成" class="headerlink" title="组成"></a>组成</h4><ul><li>结构分析（Structured Analysis，SA）</li><li>结构化设计（Structured Design，SD）</li><li>结构化程序设计（Structured Programming，SP）</li></ul><h4 id="精髓🔥"><a href="#精髓🔥" class="headerlink" title="精髓🔥"></a>精髓🔥</h4><p>自顶向下、逐步求精和模块化设计</p><h4 id="主要特点"><a href="#主要特点" class="headerlink" title="主要特点"></a>主要特点</h4><ol><li>开发目标清晰化</li><li>开发工作阶段化</li><li>开发文档规范化</li><li>设计方法结构化</li></ol><h4 id="适用场景"><a href="#适用场景" class="headerlink" title="适用场景"></a>适用场景</h4><p>特别适合于数据处理领域的问题</p><h4 id="不适用场景"><a href="#不适用场景" class="headerlink" title="不适用场景"></a>不适用场景</h4><p>不适用于规模较大、比较复杂的系统开发</p><h4 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h4><ol><li>开发周期较长</li><li>难以适应需求变化</li><li>很少考虑数据结构</li></ol><h3 id="面向对象方法"><a href="#面向对象方法" class="headerlink" title="面向对象方法"></a>面向对象方法</h3><p><img src="https://picbed.qunarzz.com/e66b60d74e7b7591ca378194405f3841.png" alt="面向对象-面向小鸡"></p><h4 id="定义-1"><a href="#定义-1" class="headerlink" title="定义"></a>定义</h4><p>面向对象（OO）方法认为，客观世界是由各种对象组成的（一切皆对象）。</p><h4 id="与结构化方法的联系"><a href="#与结构化方法的联系" class="headerlink" title="与结构化方法的联系"></a>与结构化方法的联系</h4><p>与结构化方法类似，OO 方法也划分阶段，但其中的系统分析、系统设计和系统实现三个阶段之间已经没有“缝隙”，也就是说，这三个阶段的界限变得不明确。</p><h4 id="优点🔥"><a href="#优点🔥" class="headerlink" title="优点🔥"></a>优点🔥</h4><ol><li>符合人们的思维习惯</li><li>有利于系统开发过程中用户与开发人员的沟通与交流，缩短开发周期</li></ol><h4 id="缺点🔥"><a href="#缺点🔥" class="headerlink" title="缺点🔥"></a>缺点🔥</h4><p>必须依靠一定的 OO 技术支持，在大型项目开发上具有一定的局限性，不能涉足系统分析以前的开发环节。</p><h4 id="应用场景"><a href="#应用场景" class="headerlink" title="应用场景"></a>应用场景</h4><p>当前，一些大型信息系统的开发，通常是将结构化方法和 OO 方法结合起来，首先，使用结构化方法进行自顶向下的整体划分；然后，自底向上地采用 OO 方法进行开发。</p><h3 id="原型化方法"><a href="#原型化方法" class="headerlink" title="原型化方法"></a>原型化方法</h3><p><img src="https://picbed.qunarzz.com/1c7a2ea4d0e571de005d72cf898a1352.png" alt="原型化方法"></p><h4 id="定义-2"><a href="#定义-2" class="headerlink" title="定义"></a>定义</h4><p>原型化方法也称为快速原型法，或者简称原型法。它是一种根据用户初步需求，利用系统开发工具，快速建立一个系统模型给用户，在此基础上与用户交流，最终实现用户需求的信息系统快速的开发方法。</p><h4 id="分类"><a href="#分类" class="headerlink" title="分类"></a>分类</h4><p>按是否实现功能划分：水平原型、垂直原型<br>按最终结果划分：抛弃式原型、演化式原型</p><h4 id="开发过程🔥"><a href="#开发过程🔥" class="headerlink" title="开发过程🔥"></a>开发过程🔥</h4><ol><li>确定用户的基本需求</li><li>设计系统原型</li><li>试用和评价原型</li><li>修改和完善原型</li><li>整理原型、提供文档</li></ol><p><img src="https://picbed.qunarzz.com/8a4081f6975446bb19470ea120cd78b1.png" alt="原型法的开发过程"></p><h4 id="特点🔥"><a href="#特点🔥" class="headerlink" title="特点🔥"></a>特点🔥</h4><ol><li>原型法可以使系统开发的周期缩短，成本和风险降低，速度加快，获得较高的综合开发效益。</li><li>原型法是以用户为中心来开发系统，用户参与的程度大大提高，开发的系统符合用户的需求，因而增加客户的满意度，提高了系统开发的成功率。</li><li>由于用户参与了系统开发的全过程，对系统的功能和结构容易理解和接受，有利于系统的移交，有利于系统的运行和维护。</li></ol><h4 id="不足🔥"><a href="#不足🔥" class="headerlink" title="不足🔥"></a>不足🔥</h4><ol><li>开发环境要求高</li><li>管理水平要求高</li></ol><h4 id="适用场景🔥"><a href="#适用场景🔥" class="headerlink" title="适用场景🔥"></a>适用场景🔥</h4><ol><li>原型法适用于那些需求不明确的系统开发</li><li>事实上，对于分析层面难度大，技术层面难度不大的系统，适用于原型法开发</li><li>而对于技术层面的困难远大于其分析层面的系统，则不宜用原型法。</li></ol><h3 id="面向服务"><a href="#面向服务" class="headerlink" title="面向服务"></a>面向服务</h3><p><img src="https://picbed.qunarzz.com/145bb50bf142fc865414fc290012e273.png" alt="面向服务"></p><h4 id="产生的背景"><a href="#产生的背景" class="headerlink" title="产生的背景"></a>产生的背景</h4><p>OO 的应用构建在类和对象上，随后发展起来的建模技术将相关对象按业务功能进行分组，就形成了构件（Component）的概念。</p><h4 id="交互的方式"><a href="#交互的方式" class="headerlink" title="交互的方式"></a>交互的方式</h4><p>对于跨构件的功能调用，则采用<strong>接口</strong>的形式暴露出来。进一步将接口的定义与实现进项解耦，则催生了服务和面向服务（Service-Oriented，SO）的开发方法。</p><h4 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h4><ol><li>提高系统可复用性</li><li>信息资源共享</li><li>系统之间的互操作性</li></ol><h2 id="常规信息系统集成技术"><a href="#常规信息系统集成技术" class="headerlink" title="常规信息系统集成技术"></a>常规信息系统集成技术</h2><h3 id="网络标准与网络协议"><a href="#网络标准与网络协议" class="headerlink" title="网络标准与网络协议"></a>网络标准与网络协议</h3><h4 id="OSI-协议"><a href="#OSI-协议" class="headerlink" title="OSI 协议"></a>OSI 协议</h4><ol><li>物理层</li></ol><ul><li>定义：盖层包括物理联网媒介，比如电缆连线连接器。</li><li>协议：RS232、V.35、<strong>RJ-45</strong>（水晶头）、FDDI<br>2.数据链路层</li><li>定义：它控制网络层与物理层之间的通信。</li><li>协议：<strong>IEEE 802.3&#x2F;.2</strong>、AHDLC、PPP、TM</li></ul><ol start="3"><li>网络层</li></ol><ul><li>定义：其主要功能是将网络地址（例如：IP 地址）翻译成对应的物理地址（例如：MAC 地址），并决定如何将数据从发送方路由到接收方。</li><li>协议：<strong>IP</strong>、<strong>ICMP</strong>、<strong>IGMP</strong>、IPX、<strong>ARP</strong>、<strong>RARP</strong></li></ul><ol start="4"><li>传输层</li></ol><ul><li>定义：主要负责确保数据可靠、顺序、无错地从 A 传输到 B 点。</li><li>协议：<strong>TCP</strong>、<strong>UDP</strong>、SPX</li></ul><ol start="5"><li>会话层</li></ol><ul><li>定义：负责在网络中的两个节点之间建立和维持通信，以及提供交互会话的管理功能。如三种数据流方向控制，即一路交互、两路交互和两路同时会话模式。</li><li>协议：RPC、SQL、NFS</li></ul><ol start="6"><li>表示层</li></ol><ul><li>定义：如同应用程序和网络之间的翻译官，在表示层，数据将按照网络能理解的方案进行格式化；这种格式化也因所使用的网络的类型不同而不同。表示层管理数据的解密加密、数据转换、格式化和文本压缩。</li><li>协议：<strong>JPEG</strong>、<strong>ASCLL</strong>、<strong>GIF</strong>、<strong>DES</strong>、<strong>MPEG</strong></li></ul><ol start="7"><li>应用层</li></ol><ul><li>定义：负责对软件提供接口以使程序能使用网路服务，如事务处理程序、文件传送协议和网络管理等。</li><li>协议：<strong>FTP</strong>、<strong>TFTP</strong>、<strong>HTTP</strong>、<strong>SMTP</strong>、<strong>DHCP</strong>、<strong>Telnet</strong>、<strong>DNS</strong>、<strong>SNMP</strong></li></ul><h4 id="网络协议与标准"><a href="#网络协议与标准" class="headerlink" title="网络协议与标准"></a>网络协议与标准</h4><ul><li>IEEE 802.3（局域网协议）</li><li>IEEE 802.11（无线局域网协议）</li></ul><h4 id="TCP-IP"><a href="#TCP-IP" class="headerlink" title="TCP&#x2F;IP"></a>TCP&#x2F;IP</h4><p>TCP&#x2F;IP 协议是 Internet 的核心</p><ol><li>应用层协议</li></ol><ul><li><strong>FTP</strong>（File Transport Protocol，文件传输协议）：运行在<em>TCP</em>之上，FTP 在客户端与服务器之间需要建立两条 TCP 连接，一条用于传输控制信息（使用 21 号端口）：另外一条用于传送文件内容（使用 20 端口）。</li><li><strong>TFTP</strong>（Trivial File Transfer Protocol），简单文件传输协议），建立在<em>UDP</em>之上，提供不可靠的数据流传输服务。</li><li><strong>HTTP</strong>（HyperText Transfer Protocol，超文本传输协议）：是用于从 WWW 服务器传输超文本到本地浏览器的传输协议，建立在<em>TCP</em>之上。</li><li><strong>SMTP</strong>（Simple Mail Transfer Protocol，简单邮件传输协议）：建立在<em>TCP</em>之上，是一种提供可靠且有效的电子邮件传输协议。</li><li><strong>DHCP</strong>（Dynamic Host Configuration Protocol，动态主机配置协议）：建立在<em>UDP</em>之上，基于客户端&#x2F;服务器模型设计的。所有的 IP 网络设定数据都由 DHCP 服务器集中管理，并负责客户端的 DHCP 请求。</li><li><strong>Telnet</strong>（远程登录协议）：是登录和仿真程序，建立在<em>TCP</em>之上，他的基础功能是允许用户登录进入远程计算机。</li><li><strong>DNS</strong>（Domain Name System，域名系统）：实现域名解析，建立在<em>UDP</em>之上。</li><li><strong>SNMP</strong>（Simple Network Management Protocol，简单网络管理协议）：由一组网络管理的标准组成，包含一个应用层协议、数据库模型和一组资源对象。该协议能够支持网络管理系统，用以监测连接到网络上的设备是否有任何引起管理上注意的情况。</li></ul><ol start="2"><li>传输层协议</li></ol><ul><li><strong>TCP</strong>（Transmission Control Protocol，传输控制协议）：TCP 提供了一个可靠的，面向连接的，全双工的数据传输服务。TCP 一般用于传输数据量比较少，且对可靠性要求高的场合。</li><li><strong>UDP</strong>（User Datagram Protocol，用户数据报协议）：UDP 是一种不可靠的，无连接的协议，可以保证应用程序间的通信，与 TCP 相比，UDP 是一种无连接的协议，它的错误检测功能要弱的多，UDP 协议一般用于传输数据量大，对于可靠性要求不是很高，但要求速度快的场合。</li></ul><ol start="3"><li>网络层协议</li></ol><ul><li><strong>IP</strong>（Internet Protocol，互联网协议）：所提供的服务是无连接和可靠的。</li><li><strong>ICMP</strong>（Internet Control Message Protocol，互联网控制报文协议）：一种专门用于发送错误报文的协议，即传送的数据可能丢失、重复、延迟或乱序传递，所以需要一种尽量避免差错并能在发生差错时报告的机制，这就是 ICMP 的功能。</li><li><strong>IGMP</strong>（Internet Group Management Protocol，互联网组管理协议）：允许在 Internet 中的计算机参加多播，是计算机用做向相邻多路由器报告多目组成员的协议。</li><li><strong>ARP</strong>（Address Resolution Protocol，地址解析协议）：用于动态地完成 IP 到物理地址的转换。</li><li><strong>RARP</strong> （Reverse Address Resolution Protocol，反向地址解析协议）：用于动态地完成物理地址到 IP 地址的转换。</li></ul><h3 id="网络设备"><a href="#网络设备" class="headerlink" title="网络设备"></a>网络设备</h3><h4 id="网络交换定义"><a href="#网络交换定义" class="headerlink" title="网络交换定义"></a>网络交换定义</h4><p>通过一定的设备，如交换机等，将不同的信号或者信号转换为对方可识别的信号类型从而达到通信目的的一种交换形式。</p><h4 id="网络交换分类"><a href="#网络交换分类" class="headerlink" title="网络交换分类"></a>网络交换分类</h4><ol><li>物理交换（如电话网）</li><li>链路层交换（二层交换，对 MAC 地址进行变更🔥）</li><li>网络层交换（三层交换，对 IP 地址进行变更）</li><li>传输层交换（四层交换，对端口进行变更，比较少见）</li><li>应用层交换</li></ol><h4 id="互联设备定义"><a href="#互联设备定义" class="headerlink" title="互联设备定义"></a>互联设备定义</h4><p>在网络互连时，各节点一般不能简单地直接相连，而是需要通过一个中间设备来实现。</p><h4 id="互联设备分类"><a href="#互联设备分类" class="headerlink" title="互联设备分类"></a>互联设备分类</h4><ol><li>中继器（实现物理层协议转换，在电缆间进行二进制信号）</li><li>网桥（实现物理层和数据链路层协议转换）</li><li>路由器（实现网络层协议和以下各层协议的转换）（🔥在网络层）</li><li>网关（提供从最底层到传输层或以上各层的协议转换）</li><li>交换机</li></ol><h4 id="无线网络设备"><a href="#无线网络设备" class="headerlink" title="无线网络设备"></a>无线网络设备</h4><ol><li>无线网卡</li><li>无线 AP</li><li>无线网桥</li><li>无线路由器</li></ol><h3 id="网络服务器"><a href="#网络服务器" class="headerlink" title="网络服务器"></a>网络服务器</h3><blockquote><p>无重要考点</p></blockquote><h3 id="网络存储技术"><a href="#网络存储技术" class="headerlink" title="网络存储技术"></a>网络存储技术</h3><h4 id="DAS"><a href="#DAS" class="headerlink" title="DAS"></a>DAS</h4><p><strong>定义🔥</strong></p><p>直接附加存储（Direct Attached Storage），DAS 将存储设备通过 SCSI 电缆直接连到服务器，其本身是硬件的堆叠，存储依赖于服务器，不带有任何存储操作系统。</p><p><strong>缺点🔥</strong></p><p>当存储容量增加时，DAS 方式很难扩展，这对存储容量的升级是一个巨大的瓶颈。</p><h4 id="NAS"><a href="#NAS" class="headerlink" title="NAS"></a>NAS</h4><p><strong>定义🔥</strong></p><p>网络附加存储（Network Attached Storage），通过网络接口与网络相连，由用户通过网络访问。</p><p><strong>工作原理🔥</strong></p><p>NAS 技术支持多种 TCP&#x2F;IP 网络协议，主要是 NFS（网络文件存储系统）和 CIFS（通用 Internet 文件）来进行文件访问。</p><p><strong>特点🔥</strong></p><ul><li>即插即用</li><li>小文件级的共享存取</li></ul><p><img src="https://picbed.qunarzz.com/7bb699e595dcd42ffa6d6a6b9ad5e982.png" alt="NAS 存储系统的结构"></p><h4 id="SAN🔥"><a href="#SAN🔥" class="headerlink" title="SAN🔥"></a>SAN🔥</h4><p><strong>定义</strong></p><p>存储区域网络（Storage Area Network），通过专用交换机将磁盘阵列与服务器连接起来的高速专用子网。</p><p><strong>技术分类</strong></p><ol><li><strong>FC SAN</strong>：光纤通道的主要特性有：热插拔性、高速带宽、远程连接数量大。</li><li><strong>IP SAN</strong>：基于 IP 网络实现数据块级别存储方式的存储网络。</li><li><strong>IB SAN</strong>：是一种交换结构 I&#x2F;O 技术，其设计思路是通过一套中心机构在远程存储器、网络以及服务器等设备之间建立一个单一的连接链路，并由 IB 交换机来指挥流量。</li></ol><p><img src="https://picbed.qunarzz.com/61c61873ce628e907c850fe99eb9fe3a.png" alt="紫升"></p><h3 id="网络接入技术"><a href="#网络接入技术" class="headerlink" title="网络接入技术"></a>网络接入技术</h3><h4 id="有线接入"><a href="#有线接入" class="headerlink" title="有线接入"></a>有线接入</h4><ol><li>PSTN</li><li>ISDN</li><li>ADSL</li><li>FTTX+LAN</li><li>HFC<br>1.3.5网络接入技术</li></ol><h4 id="无线接入"><a href="#无线接入" class="headerlink" title="无线接入"></a>无线接入</h4><p>无线网络是指以无线电波作为信息传输媒介🔥</p><p><strong>技术分类：</strong></p><ol><li>GPRS</li><li>无线接入</li><li>3G</li><li>4G</li><li>WIFI</li></ol><h3 id="网络规划与设计"><a href="#网络规划与设计" class="headerlink" title="网络规划与设计"></a>网络规划与设计</h3><h4 id="网路分层设计🔥"><a href="#网路分层设计🔥" class="headerlink" title="网路分层设计🔥"></a>网路分层设计🔥</h4><ul><li><strong>核心层🔥</strong>：主要目的通过<strong>高速的转发通信</strong>，提供优化，可靠的骨干传输结构，因此，核心层交换机应拥有更高的可靠性，性能和吞吐量。</li><li><strong>汇聚层🔥</strong>：是核心层与接入层的分界面，完成<strong>网络访问策略控制，数据包处理、过滤、寻址</strong>、以及其他的数据处理任务。</li><li><strong>接入层🔥</strong>：目的是允许终端用户连接到网络，因此，接入层交换机（或路由器）接入层具有<strong>低成</strong>本和<strong>高密度</strong>特性。</li></ul><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1677082670910.png" alt="网络规划分层"></p><h4 id="网络设计工作"><a href="#网络设计工作" class="headerlink" title="网络设计工作"></a>网络设计工作</h4><ol><li>网络拓扑结构设计</li><li>主干网络设计（核心层）设计</li><li>汇聚层和接入层设计</li><li>广域网连接与远程访问设计</li><li>无线网络设计</li><li>网络安全设计</li></ol><ul><li>信息安全的基本要素🔥</li></ul><ol start="7"><li>设备选型</li></ol><h4 id="信息安全的基本要素🔥"><a href="#信息安全的基本要素🔥" class="headerlink" title="信息安全的基本要素🔥"></a>信息安全的基本要素🔥</h4><ol><li><strong>机密性🔥</strong>：确保信息不暴露给未授权的实体或进程</li><li><strong>完整性🔥</strong>：只有得到允许的人才能修改数据，并且能够判别出来数据是否已被篡改。</li><li><strong>可用性🔥</strong>：得到授权的实体在需要的时候可访问数据，即攻击者不能占用所有的资源而阻碍授权者的工作</li><li><strong>可控性🔥</strong>：可以控制授权范围内的信息流向及行为方式</li><li><strong>可审查性🔥</strong>：对出现的网络安全问题提供调查的依据和手段</li></ol><h3 id="数据库管理系统"><a href="#数据库管理系统" class="headerlink" title="数据库管理系统"></a>数据库管理系统</h3><h4 id="关系型数据库"><a href="#关系型数据库" class="headerlink" title="关系型数据库"></a>关系型数据库</h4><ul><li><strong>Oracle</strong></li><li><strong>MySQL</strong></li><li><strong>SqlServer</strong></li><li>DB2</li><li>PostgreSQL</li><li>SQLite</li><li>Access</li></ul><h4 id="非关系型数据库"><a href="#非关系型数据库" class="headerlink" title="非关系型数据库"></a>非关系型数据库</h4><ul><li><strong>MongoDB</strong></li><li>Redis</li><li>HBase</li><li>Neo4j</li><li>Elasticsearch</li></ul><h3 id="数据仓库技术"><a href="#数据仓库技术" class="headerlink" title="数据仓库技术"></a>数据仓库技术</h3><h4 id="特点🔥-1"><a href="#特点🔥-1" class="headerlink" title="特点🔥"></a>特点🔥</h4><p>数据仓库是一个面向主题的，集成的，非易失的，且随时间变化的数据集合，用于支持管理决策。</p><h4 id="体系结构"><a href="#体系结构" class="headerlink" title="体系结构"></a>体系结构</h4><ol><li><strong>数据源</strong>：是数据仓库系统的基础，是整个系统数据源泉。</li><li><strong>数据的存储与管理</strong>：是整个系统数据仓库系统的核心。</li><li><strong>OLAP（Online Analysis Processor，联机分析处理）服务器</strong>：对分析需要的数据进行有效集成，按多维模型予以组织，以便进行多角度、多层次的分析，并发现趋势。</li><li><strong>前端工具</strong>：主要包括各种查询工具，报表工具，分析工具，数据挖掘工具以及各种基于数据仓库或数据集的应用开发工具。其中数据分析工具主要针对OLAP，报表工具，数据挖掘工具主要针对数据仓库。</li></ol><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1677083599886.png" alt="数据仓库体系结构"></p><h2 id="练一练"><a href="#练一练" class="headerlink" title="练一练"></a>练一练</h2><p>1、对象和类是面向对象中两个重要的概念，关于对象和类，不正确的是（）。 <em>19年11月第1题</em></p><p>A、对象是类的实例<br>B、类是对象的抽象<br>C、一个类只能产生一个对象<br>D、类中包含方法和属性</p><div class="hide-block"><button type="button" class="hide-button" style="">答案解析</button><div class="hide-content"><p>【重要性】一定要掌握<br>【答案】<strong>C</strong><br>【解析】一个类可以产生多个对象</p></div></div><hr><p>2、TCP&#x2F;IP 模型中，Telnet 属于（）协议。 <em>19年5月19题</em></p><p>A、接口层<br>B、网络层<br>C、传输层<br>D、应用层</p><div class="hide-block"><button type="button" class="hide-button" style="">答案解析</button><div class="hide-content"><p>【重要性】一定要掌握<br>【答案】<strong>D</strong><br>【解析】Telnet 是应用层协议。</p></div></div><hr><p>3、在信息系统的生命周期中，开发阶段不包括（）。 <em>19年5月3题</em></p><p>A、系统规划<br>B、系统设计<br>C、系统分析<br>D、系统实施</p><div class="hide-block"><button type="button" class="hide-button" style="">答案解析</button><div class="hide-content"><p>【重要性】一定要掌握<br>【答案】<strong>A</strong><br>【解析】系统规划是立项阶段的，高级教材第三版 P10。</p></div></div><hr><p>4、（）的设计方法满足了信息系统快速响应需求与环境的变化，组织内部、组织之间各种应用系统的互相通信要求，提高了系统可复用性、信息资源共享和系统之间的互操作性。 <em>18年11月28题</em></p><p>A、面向对象<br>B、面向过程<br>C、基于构件<br>D、面向服务</p><div class="hide-block"><button type="button" class="hide-button" style="">答案解析</button><div class="hide-content"><p>【重要性】一定要掌握<br>【答案】<strong>D</strong><br>【解析】从应用的角度来看，组织内部、组织之间各种应用系统的互相通信和互操作性直接影响着组织<br>对信息的掌握程度和处理速度。如何使信息系统快速响应需求与环境变化，提高系统可复用性、信息资<br>源共享和系统之间的互操作性，成为影响信息化建设效率的关键问题，而SO的思维方式恰好满足了这<br>种需求。</p></div></div><hr><p>5、在OSI七层协议中，UDP是（）的协议。 <em>18年11月第19题</em></p><p>A、网络层<br>B、传输层<br>C、会话层<br>D、应用层</p><div class="hide-block"><button type="button" class="hide-button" style="">答案解析</button><div class="hide-content"><p>【重要性】一定要掌握<br>【答案】<strong>B</strong><br>【解析】(4)传输层：主要负责确保数据可靠、顺序、无错地从A点传输到B点。如提供建立、维护相拆<br>除传送连接的功能；选择网络层提供最合适的服务；在系统之间提供可靠的透明的数据传送，提供端到<br>端的错误恢复和流量控制。在TCP&#x2F;IP协议中，具体协议有TCP、UDP、SPX。</p></div></div><hr><p>6、（）的任务是：根据系统说明书规定的功能要求，考虑实际条件，具体设计实现逻辑模型的技术方<br>案。 <em>18年11月第3题</em></p><p>A、系统规划阶段<br>B、系统分析阶段<br>C、系统设计阶段<br>D、系统实施阶段</p><div class="hide-block"><button type="button" class="hide-button" style="">答案解析</button><div class="hide-content"><p>【重要性】一定要掌握<br>【答案】<strong>C</strong><br>【解析】简单地说，系统分析阶段的任务是回答系统“做什么”的问题，而系统设计阶段要回答的问题<br>是“怎么做”。该阶段的任务是根据系统说明书中规定的功能要求，考虑实际条件，具体设计实现逻辑<br>模型的技术方案，也就是设计新系统的物理模型。这个阶段又称为物理设计阶段，可分为总体设计（概<br>要设计）和详细设计两个子阶段。这个阶段的技术文档是系统设计说明书。</p></div></div><hr><p>7、IEEE 802 规范定义了网卡如何访问传输介质，以及如何在传输介质上传输数据的方法。其中，（）<br>是重要的局域网协议。 <em>18年5月第11题</em></p><p>A、IEEE 802.1<br>B、IEEE 802.3<br>C、IEEE 802.6<br>D、IEEE 802.11</p><div class="hide-block"><button type="button" class="hide-button" style="">答案解析</button><div class="hide-content"><p>【重要性】一定要掌握<br>【答案】<strong>B</strong><br>【解析】 IEEE 802.3 ：以太网介质访问控制协议 （CSMA&#x2F;CD）及物理层技术规范 [2]。<br>IEEE 802.11：无线局域网（WLAN）的介质访问控制协议及物理层技术规范。</p></div></div><hr><p>8、在开放系统互连参考模型(OSI)中，（ ）的主要功能是将网络地址翻译成对应的物理地址，并决定如<br>何将数据从发送方经路由送达到接收方。 <em>18年5月第20题</em></p><p>A、数据链路层<br>B、物理层<br>C、网络层<br>D、传输层</p><div class="hide-block"><button type="button" class="hide-button" style="">答案解析</button><div class="hide-content"><p>【重要性】一定要掌握<br>【答案】<strong>C</strong><br>【解析】网络层中的协议主要有IP、ICMP、IGMP、ARP、和RARP等。 （1）IP，所提供的服务是无连接和不可靠的。<br>（2）ICMP(Internet Control Message Protocol，网络控制报文协议)，一种专门用于发送错报文的<br>协议，即传送的数据可能丢失、重复、延迟、或乱序传递，所以需要一种尽量避免差错并能发生差错时<br>报告的机制，这就是ICMP的功能。<br>（3）IGMP(Internet Group Management Protocol，网际组管理协议)允许在Internet中的计算机参<br>加多播，是计算机用做向相邻多路由器报告多目组成员的协议。<br>（4）ARP(Address Resolution Protocol，地址解析协议) 用于动态地完成IP到物理地址的转换。<br>（5）RARP(Reverse Address Resolution Protocol，反向地址解析协议)用于动态完成物理地址向IP<br>地址的转换。</p></div></div><hr><p>9、 TCP&#x2F;IP是internet的核心协议，应用程序通过用应用层协议利用网络完成数据交互的任务，其中，<br>（）是用来在客户机与服务器之间进行简单文件的传输的协议，提供不复杂，开销不大的文件传输服务。 <em>18年5月第19题</em></p><p>A、FTP<br>B、TFTP<br>C、HTTP<br>D、SMTP</p><div class="hide-block"><button type="button" class="hide-button" style="">答案解析</button><div class="hide-content"><p>【重要性】一定要掌握<br>【答案】<strong>B</strong><br>【解析】TFTP（Trivial File Transfer Protocol,简单文件传输协议）是TCP&#x2F;IP协议族中的一个用来在客<br>户机与服务器之间进行简单文件传输的协议，提供不复杂、开销不大的文件传输服务。端口号为69。</p></div></div><hr><p>10、某快消品连锁企业委托科技公司A开发部署电子商务平台，A公司根据系统设计任务书所确定的范<br>围，确定系统的基本目标和逻辑功能要求，提出新系统的逻辑模型，这属于信息系统生命周期中（）阶<br>段的工作。 <em>18年5月第3题</em></p><p>A、系统规划<br>B、系统分析<br>C、系统设计<br>D、系统实施</p><div class="hide-block"><button type="button" class="hide-button" style="">答案解析</button><div class="hide-content"><p>【重要性】一定要掌握<br>【答案】<strong>B</strong><br>【解析】系统分析阶段的任务是根据系统设计任务书所确定的范围，对现行系统进行详细调查，描述现<br>行系统的业务流程，指出现行系统的局限性和不足之处，确定新系统的基本目标和逻辑功能要求，即提<br>出新系统的逻辑模型。</p></div></div><hr><p>11、在软件需求分析中，（）分别用来表示功能模型和行为模型。 <em>19年11月第7题</em></p><p>A、数据流图、状态转换图<br>B、状态转换图、E-R图<br>C、状态转换图、数据流图<br>D、E-R图、状态转换图</p><div class="hide-block"><button type="button" class="hide-button" style="">答案解析</button><div class="hide-content"><p>【重要性】一定要掌握<br>【答案】<strong>A</strong><br>【解析】E-R图是用来表示数据模型的，行为一般与状态有关。</p></div></div><hr><p>12、中间件是一种独立的系统软件或服务程序，（）不属于中间件。 <em>19年11月第2题</em><br>A、Tomcat<br>B、Websphere<br>C、ODBC<br>D、Python</p><div class="hide-block"><button type="button" class="hide-button" style="">答案解析</button><div class="hide-content"><p>【重要性】一定要掌握<br>【答案】<strong>D</strong><br>【解析】Python 属于开发语言</p></div></div><hr><p>13、项目成员张工绘制了四张类图，其中不正确的是（）。 <em>19年5月第27题</em></p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1677084628615.png" alt="紫升"></p><div class="hide-block"><button type="button" class="hide-button" style="">答案解析</button><div class="hide-content"><p>【答案】<strong>B</strong></p></div></div><hr><p>14、在 UML 的顺序图中，通常由左向右分层排列各个对象，正确的排列方法是（）。<em>19年5月第26题</em></p><p>A、执行者角色、控制类、用户接口、业务层、后台数据库<br>B、执行者角色、用户接口、控制类、业务层、后台数据库<br>C、执行者角色、控制类、用户接口、后台数据库、业务层<br>D、执行者角色、用户接口、业务层、控制类、后台数据库</p><div class="hide-block"><button type="button" class="hide-button" style="">答案解析</button><div class="hide-content"><p>【重要性】一定要掌握<br>【答案】<strong>A</strong></p></div></div><hr><p>15、执行者与用例之间的关系是（）。 <em>19年5月第25题</em></p><p>A、包含关系<br>B、泛化关系<br>C、关联关系<br>D、扩展关系</p><div class="hide-block"><button type="button" class="hide-button" style="">答案解析</button><div class="hide-content"><p>【重要性】一定要掌握<br>【答案】C<br>【解析】在用例图中执行者与用例之间是关联关系。</p></div></div><hr><p>16、UML的（）描述了一个特定对象的所有可能状态以及由于各种事件的发生而引起的状态之间的转移。 <em>18年11月第27题</em></p><p>A、控制图<br>B、状态图<br>C、协作图<br>D、序列图</p><div class="hide-block"><button type="button" class="hide-button" style="">答案解析</button><div class="hide-content"><p>【重要性】一定要掌握<br>【答案】<strong>B</strong><br>【解析】状态图（state diagram）：状态图描述一个状态机，它由状态、转移、事件和活动组成。状态图给出了对象的动态视图。它对于接口、类或协作的行为建模尤为重要，而且它强调事件导致的对象行为，这非常有助于对反应式系统建模。</p></div></div><hr><p>17、某企业要建设信息系统平台，要求系统可用性达到99.99%。系统A平均无故障时间10000小时，故<br>障平均维修时间1小时：系统B平均无故障时间14000小时，故障平均维修时间1.5小时。以下说法中，<br>正确的是（）。 <em>18年11月第22题</em></p><p>A、只有系统A符合可用性要求<br>B、系统A和系统B均符合可用性要求<br>C、只有系统B符合可用性要求<br>D、系统A和系统B都不符合可用性要求</p><div class="hide-block"><button type="button" class="hide-button" style="">答案解析</button><div class="hide-content"><p>【重要性】一定要掌握<br>【答案】<strong>A</strong><br>【解析】计算机系统的可用性用平均无故障时间(MTTF)来度量，即计算机系统平均能够正常运行多长<br>时间，才发生一次故障。系统的可用性越高，平均无故障时间越长。可维护性用平均维修时间(MTTR)<br>来度量，即系统发生故障后维修和重新恢复正常运行平均花费的时间。系统的可维护性越好，平均维修<br>时间越短。计算机系统的可用性定义为：MTTF&#x2F; (MTTF+MTTR) ,kl00%。由此可见，计算机系统的可<br>用性定义为系统保持正常运行时间的百分比。所以，想要提高一个系统的可用性，要么提升系统的单次<br>正常工作的时长，要么减少故障修复时间。<br>A的可用性&#x3D;10000&#x2F;10001&#x3D;99.99%<br>B的可用性&#x3D;14000&#x2F;14001.5 &#x3D; 99.98%<br>只有系统A负荷可用性要求</p></div></div><hr><p>18、关于网络存储技术的描述，正确的是：（）。<br>A、DAS 是一种易于扩展的存储技术<br>B、NAS 系统与 DAS 系统相同，都没有自己的文件系统<br>C、NAS 可以使用 TCP&#x2F;IP 作为其网络传输协议<br>D、SAN 采用了文件共享存取方式</p><div class="hide-block"><button type="button" class="hide-button" style="">答案解析</button><div class="hide-content"><p>【重要性】一定要掌握<br>【答案】<strong>C</strong><br>【解析】NAS 技术支持多种 TCP&#x2F;IP 网络协议，主要是NFS (Net File System，网络文件系统)和 CIFS<br>（Common Internet File System，通用 Internet 文件系统）来进行文件访问，所以 NAS 的性能特点是<br>进行小文件级的共享存取。在具体使用时，NAS设备通常配置为文件服务器，通过使用基于 Web 的管<br>理界面来实现系统资源的配置、用户配置管理和用户访问登录等。</p></div></div><hr><p>19、软件需求是多层次的，包括业务需求、用户需求、系统需求，其中业务需求（）。 <em>18年11月第7题</em></p><p>A、反应了企业或客户对系统高层次的目标需求<br>B、描述了用户具体目标或者用户要求系统必须完成的任务<br>C、从系统角度来说明软件的需求，包括功能需求、非功能需求和设计约束<br>D、描述了用户任务系统应该具备的功能和性能</p><div class="hide-block"><button type="button" class="hide-button" style="">答案解析</button><div class="hide-content"><p>【重要性】一定要掌握<br>【答案】<strong>A</strong><br>【解析】业务需求。业务需求是指反映企业或客户对系统高层次的目标要求，通常来自项目投资人、购买产品的客户、客户单位的管理人员、市场营销部门或产品策划部门等。通过业务需求可以确定项目视图和范围，项目视图和范围文档把业务需求集中在一个简单、紧凑的文档中，该文档为以后的开发工作奠定了基础。</p></div></div><hr><p>20、UML图不包括（）。 <em>18年5月第27题</em></p><p>A、用例图<br>B、序列图<br>C、组件图<br>D、继承图</p><div class="hide-block"><button type="button" class="hide-button" style="">答案解析</button><div class="hide-content"><p>【重要性】一定要掌握<br>【答案】<strong>D</strong><br>【解析】类图表示类、接口和它们之间的协作关系，没有继承图</p></div></div>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/e896d9d3a8e1/</id>
    <link href="https://blog.zisheng.pro/posts/e896d9d3a8e1/"/>
    <published>2023-02-19T15:40:41.000Z</published>
    <summary>信息系统项目管理师考试属于全国计算机技术与软件专业技术资格考试中的一个高级考试。</summary>
    <title>信息系统项目管理师笔记の第一章、信息化和信息系统</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="软考" scheme="https://blog.zisheng.pro/categories/%E8%BD%AF%E8%80%83/"/>
    <category term="信息系统项目管理师" scheme="https://blog.zisheng.pro/tags/%E4%BF%A1%E6%81%AF%E7%B3%BB%E7%BB%9F%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86%E5%B8%88/"/>
    <category term="软考高项" scheme="https://blog.zisheng.pro/tags/%E8%BD%AF%E8%80%83%E9%AB%98%E9%A1%B9/"/>
    <category term="信息化" scheme="https://blog.zisheng.pro/tags/%E4%BF%A1%E6%81%AF%E5%8C%96/"/>
    <category term="信息系统" scheme="https://blog.zisheng.pro/tags/%E4%BF%A1%E6%81%AF%E7%B3%BB%E7%BB%9F/"/>
    <category term="高项" scheme="https://blog.zisheng.pro/tags/%E9%AB%98%E9%A1%B9/"/>
    <category term="计算机" scheme="https://blog.zisheng.pro/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA/"/>
    <content>
      <![CDATA[<h2 id="考试介绍"><a href="#考试介绍" class="headerlink" title="考试介绍"></a>考试介绍</h2><p>全国计算机技术与软件专业技术资格考试设三个级别层次，五个专业，共有 27 种岗位资格考试。而<strong>信息系统项目管理师</strong>考试属于全国计算机技术与软件专业技术资格考试中的一个<strong>高级考试</strong>。</p><h2 id="报考条件"><a href="#报考条件" class="headerlink" title="报考条件"></a>报考条件</h2><p>信息系统项目管理师考试报考条件不设学历与资历条件、年龄以及专业等限制，考生可根据自己的技术水平选择合适的级别以及合适的资格进行报考。</p><blockquote><p>注意：每次考试每个人只允许报考一种资格。</p></blockquote><h2 id="考试流程"><a href="#考试流程" class="headerlink" title="考试流程"></a>考试流程</h2><ol><li><a href="https://bm.ruankao.org.cn/sign/welcome">报名</a>（2月 - 3月）</li><li>打印准考证（考前一周）</li><li>考试（5 月 27 日）</li><li>公布成绩（考后一个月左右）</li><li>领取证书（9 月份左右）</li></ol><h2 id="考题分析"><a href="#考题分析" class="headerlink" title="考题分析"></a>考题分析</h2><table><thead><tr><th align="center">科目</th><th align="center">内容</th><th align="center">时长</th><th align="center">考试时间</th></tr></thead><tbody><tr><td align="center">综合知识</td><td align="center">75道选择题，知识范围很广，包含：IT 知识、时事政治、项目管理、5 分的运筹学、5 分英语题</td><td align="center">150 分钟</td><td align="center">上午<br />9:00 - 11:00</td></tr><tr><td align="center">案例分析</td><td align="center">3 道大题：1 道计算题，2 道分析题</td><td align="center">90 分钟</td><td align="center">下午<br />1:30 - 3:00</td></tr><tr><td align="center">论文写作</td><td align="center">1 道论文题，考试范围：<strong>十大管理</strong>、安全管理、合同管理、招投标</td><td align="center">120 分钟</td><td align="center">下午<br />3:20 - 5:20</td></tr></tbody></table><h2 id="学习计划"><a href="#学习计划" class="headerlink" title="学习计划"></a>学习计划</h2><p>计划共历时 100 天，每天投入 2 - 3 小时，总投入预计 200 - 300 小时。</p><ul><li>第一阶段（35 天）(2023&#x2F;2&#x2F;15 - 2023&#x2F;3&#x2F;21)<ul><li>基础知识</li><li>计算专题</li></ul></li><li>第二阶段（50 天）(2023&#x2F;3&#x2F;22 - 2023&#x2F;5&#x2F;10)<ul><li>历年真题</li><li>案例分析</li><li>写作专题</li></ul></li><li>第三阶段 (15 天)（2023&#x2F;5&#x2F;11 - 2023&#x2F;5&#x2F;26）<ul><li>考前冲刺</li></ul></li></ul><h2 id="资源"><a href="#资源" class="headerlink" title="资源"></a>资源</h2><ul><li><a href="https://www.ruankao.org.cn/">中国计算机技术资格网</a></li><li>软考真题 App<ul><li><a href="https://apps.apple.com/hk/app/%E8%BD%AF%E8%80%83%E7%9C%9F%E9%A2%98-2022%E4%BF%A1%E6%81%AF%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86-%E7%B3%BB%E7%BB%9F%E9%9B%86%E6%88%90/id1468131830">iOS</a></li><li><a href="https://www.wandoujia.com/apps/7873801">Android</a></li></ul></li><li>软考通 App<ul><li><a href="https://apps.apple.com/cn/app/%E8%BD%AF%E8%80%83%E9%80%9A-2022%E5%B9%B4%E8%BD%AF%E4%BB%B6%E6%B0%B4%E5%B9%B3%E8%80%83%E8%AF%95%E6%8F%90%E5%88%86%E7%A5%9E%E5%99%A8/id1498444438">iOS</a></li><li><a href="https://www.wandoujia.com/apps/8095598">Android</a></li></ul></li><li>软考刷题王<ul><li>#小程序:&#x2F;&#x2F;软考刷题王&#x2F;I4Jk3d4HRskRbaw</li></ul></li><li><img src="https://cdn.jsdelivr.net/gh/youngjuning/images@main/1677062492249.png" alt="杭州 E 类人才福利"></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/9698ac1384d7/</id>
    <link href="https://blog.zisheng.pro/posts/9698ac1384d7/"/>
    <published>2023-02-15T16:04:55.000Z</published>
    <summary>信息系统项目管理师考试属于全国计算机技术与软件专业技术资格考试中的一个高级考试。</summary>
    <title>信息系统项目管理师备考指南</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="CSS" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/CSS/"/>
    <category term="CSS" scheme="https://blog.zisheng.pro/tags/CSS/"/>
    <category term="word-wrap" scheme="https://blog.zisheng.pro/tags/word-wrap/"/>
    <category term="word-break" scheme="https://blog.zisheng.pro/tags/word-break/"/>
    <content>
      <![CDATA[<p>word-wrap 和 word-break 是一对用于单词断句的双生属性，但是又有微妙的区别，每次要用的时候还要重新翻一下 MDN 文档，本文力求通过一张流程图快速记忆两者如何使用。</p><h2 id="word-wrap"><a href="#word-wrap" class="headerlink" title="word-wrap"></a>word-wrap</h2><p>css 的 word-wrap 属性用来标明是否允许浏览器在单词内进行断句，这是为了防止当一个字符串太长而找不到它的自然断句点时产生溢出现象。</p><ul><li>normal：就是大家平常见得最多的正常的换行规则。</li><li>break-word：一行单词中实在没有其他靠谱的换行点的时候在单词内换行。</li></ul><h2 id="word-break"><a href="#word-break" class="headerlink" title="word-break"></a>word-break</h2><p>css 的 word-break 属性用来标明怎么样进行单词内的断句。</p><ul><li>normal：使用默认的换行规则，即如果一个单词很长，导致一行中剩下的空间已经放不下它时，则浏览器会把这个单词挪到下一行去。</li><li>break-all：允许单词断行，即如果一个单词很长，导致一行中剩下的空间已经放不下它时，则浏览器不会把这个单词挪到下一行去，而是在单词内换行。</li></ul><h2 id="搭配使用，换行不累"><a href="#搭配使用，换行不累" class="headerlink" title="搭配使用，换行不累"></a>搭配使用，换行不累</h2><p><img src="https://picbed.qunarzz.com/3d7e329ab089d28c7bde5be75573f1d3.png" alt="紫升"></p><blockquote><p>参考资料来自 <a href="https://www.cnblogs.com/2050/archive/2012/08/10/2632256.html">你真的了解word-wrap和word-break的区别吗？</a> 和 <a href="https://www.zhangxinxu.com/wordpress/2015/11/diff-word-break-break-all-word-wrap-break-word/">word-break:break-all和word-wrap:break-word的区别</a></p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/c607ec2b5110/</id>
    <link href="https://blog.zisheng.pro/posts/c607ec2b5110/"/>
    <published>2023-02-13T05:47:56.000Z</published>
    <summary>word-wrap 和 word-break 是一对用于单词断句的双生属性，但是又有微妙的区别，每次要用的时候还要重新翻一下 MDN 文档，本文力求通过一张流程图快速记忆两者如何使用。</summary>
    <title>word-wrap 和 word-break</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="人工智能" scheme="https://blog.zisheng.pro/categories/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/"/>
    <category term="swiper" scheme="https://blog.zisheng.pro/tags/swiper/"/>
    <category term="AI" scheme="https://blog.zisheng.pro/tags/AI/"/>
    <category term="人工智能" scheme="https://blog.zisheng.pro/tags/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/"/>
    <category term="OPenAI" scheme="https://blog.zisheng.pro/tags/OPenAI/"/>
    <category term="ChatGPT" scheme="https://blog.zisheng.pro/tags/ChatGPT/"/>
    <category term="ChatGPT 注册" scheme="https://blog.zisheng.pro/tags/ChatGPT-%E6%B3%A8%E5%86%8C/"/>
    <content>
      <![CDATA[<p>OpenAI 推出超神 ChatGPT，非常火爆。但是呢，由于 ChatGPT 注册的问题，大部分人无法体验到。这里我分享一下我的国内 ChatGPT 注册攻略。</p><h2 id="准备"><a href="#准备" class="headerlink" title="准备"></a>准备</h2><ul><li>VPN：保证能访问 Google（前置条件，没有梯子的可以看下我收藏的<a href="https://blog.zisheng.pro/vpn/">科学上网工具</a>）</li><li>国外手机号：如果你没有国外手机号，我推荐使用 <a href="https://sms-activate.org/">SMS Activate 在线接码平台</a></li></ul><h2 id="注册短信平台并充值"><a href="#注册短信平台并充值" class="headerlink" title="注册短信平台并充值"></a>注册短信平台并充值</h2><ul><li>先行注册 <a href="https://sms-activate.org/">https://sms-activate.org</a></li><li>注册好之后进行对应的充值</li></ul><p><img src="https://s2.loli.net/2023/08/08/kIczosFWCVKmnN1.png" alt="SMS Activate 注册"></p><p>接码费用一次为 10.5 卢布，大约 1.2 人民币。因为充值默认为美元，可以选择充值 1 美元进去，</p><p><img src="https://s2.loli.net/2023/08/08/JvGwg4q3Fino7b2.png" alt="SMS Activate 注册付款"></p><h2 id="注册-OpenAI-账号"><a href="#注册-OpenAI-账号" class="headerlink" title="注册 OpenAI 账号"></a>注册 OpenAI 账号</h2><ul><li>打开<a href="https://beta.openai.com/signup">https://beta.openai.com/signup</a> 页面进行相应的注册。<blockquote><p>这里同样需要你能访问 Google 且 ip 不是香港，最好是美国、新加坡等等，不然会提示不能在当前国家服务。</p></blockquote></li><li>注册成功进入下面填写手机号的页面<blockquote><p>下面记得切换下国家区号，这里的区号默认是你代理的。</p></blockquote></li></ul><p><img src="https://s2.loli.net/2023/08/08/89JjkV3rAGt5W1x.png" alt="Open AI ChatGPT 注册"></p><h2 id="准备接码"><a href="#准备接码" class="headerlink" title="准备接码"></a>准备接码</h2><blockquote><p>实测可接收到码的国家（2023-3-16）：印尼🇮🇩、巴西🇧🇷，激活不成功删除的话不扣除积分。</p></blockquote><p><img src="https://s2.loli.net/2023/08/08/NFf9M1IjcLCg3rm.png" alt="SMS ChatGPT 接码"></p><p><img src="https://s2.loli.net/2023/08/08/GkbyQTwei8n7sVp.png" alt="SMS ChatGPT 接不到码"></p><ul><li>然后再刚刚填写手机号码的页面填入申请的手机号</li></ul><h2 id="开始使用-ChatGPT"><a href="#开始使用-ChatGPT" class="headerlink" title="开始使用 ChatGPT"></a>开始使用 ChatGPT</h2><p>注册完后，我们去ChatGPT网站去登陆。<a href="https://chat.openai.com/auth/login">https://chat.openai.com/auth/login</a></p><p><img src="https://s2.loli.net/2023/08/08/5jm8LtC2Pg3QG6K.png" alt="OpenAI ChatGPT 使用"></p><p><img src="https://s2.loli.net/2023/08/08/7DyYTXdcM13QoiO.png" alt="OpenAI ChatGPT 使用"></p><h2 id="2022-12-6-更新"><a href="#2022-12-6-更新" class="headerlink" title="2022-12-6 更新"></a>2022-12-6 更新</h2><ul><li>如果你的页面出现下面提示，说明你代理不行或者是节点不对~~</li></ul><p><img src="https://s2.loli.net/2023/08/08/bafQWJ3sXRde6CF.png" alt="ChatGPT VPN 节点"></p><ul><li>接不到码，可以在那个有效期内退回换个号试试。<blockquote><p>有人直接把发的电话输入框里，没去掉区号也可以收到</p></blockquote></li><li>这个接码网站很全面，我演示充值是 1 美元，你也可以冲一个 0.18美元</li><li>目前完全支持的是印度+巴西。你也可以选择其他国家的 any+other 选择合适的费用即可。</li></ul><h2 id="2023-04-09-更新"><a href="#2023-04-09-更新" class="headerlink" title="2023-04-09 更新"></a>2023-04-09 更新</h2><ul><li>VS Code 插件推荐：<a href="https://marketplace.visualstudio.com/items?itemName=genieai.chatgpt-vscode">ChatGPT - Genie AI</a></li><li>Chrome 扩展：<a href="https://chrome.google.com/webstore/detail/chatgpt-for-google/jgjaeacdkonaoafenlfkkkmbaopkbilf/related?hl=zh-CN">ChatGPT for Google</a>、<a href="https://monica.im/?c=UH68PBPR">Monica</a></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/d744d1d3d1e8/</id>
    <link href="https://blog.zisheng.pro/posts/d744d1d3d1e8/"/>
    <published>2023-02-06T07:58:31.000Z</published>
    <summary>OpenAI 推出超神 ChatGPT，非常火爆。但是呢，由于 ChatGPT 注册的问题，大部分人无法体验到。这里我分享一下我的国内 ChatGPT 注册攻略。</summary>
    <title>OpenAI 推出超神 ChatGPT 注册攻略来了</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="npm" scheme="https://blog.zisheng.pro/tags/npm/"/>
    <category term="命令" scheme="https://blog.zisheng.pro/tags/%E5%91%BD%E4%BB%A4/"/>
    <content>
      <![CDATA[<h2 id="全局"><a href="#全局" class="headerlink" title="全局"></a>全局</h2><ul><li><code>npm root --location=global</code>：查看全局包安装路径<blockquote><p>Mac 下默认的全局路径是：<code>/usr/local/lib/node_modules</code><br>Ubuntu 下默认的全局路径是: <code>/usr/lib/node_modules</code></p></blockquote></li><li><code>npm ls --location=global</code>：查看全局包列表<ul><li><code>npm ls --location=global --depth 0</code>：仅查看一级包</li><li><code>npm ls --location=global --depth 0 | grep packageName</code>：查看一级包并搜索相关包</li></ul></li></ul><h2 id="owner"><a href="#owner" class="headerlink" title="owner"></a>owner</h2><p>管理已经发布的包的所有权</p><ul><li><code>npm owner add &lt;user&gt; [&lt;@scope&gt;/]&lt;pkg&gt;</code>：给某个包添加用户</li><li><code>npm owner rm &lt;user&gt; [&lt;@scope&gt;/]&lt;pkg&gt;</code>：删除某个包的某个用户</li><li><code>npm owner ls [&lt;@scope&gt;/]&lt;pkg&gt;</code>：列出某个包的所有用户</li></ul><h2 id="将指定版本设置为指定标签"><a href="#将指定版本设置为指定标签" class="headerlink" title="将指定版本设置为指定标签"></a>将指定版本设置为指定标签</h2><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ npm dist-tag add @youngjuning/playground@1.0.3 latest|alpha|beta</span><br></pre></td></tr></table></figure><h2 id="展示-dist-tag"><a href="#展示-dist-tag" class="headerlink" title="展示 dist-tag"></a>展示 dist-tag</h2><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ npm dist-tag <span class="built_in">ls</span> @youngjuning/playground</span><br></pre></td></tr></table></figure><h2 id="废弃某个版本"><a href="#废弃某个版本" class="headerlink" title="废弃某个版本"></a>废弃某个版本</h2><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ npm deprecate @youngjuning/playground@1.0.3 <span class="string">&quot;垃圾&quot;</span></span><br></pre></td></tr></table></figure><h2 id="package-json-字段解释"><a href="#package-json-字段解释" class="headerlink" title="package.json 字段解释"></a>package.json 字段解释</h2><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;wx-promise-pro&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;3.2.2&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;强大的、优雅的小程序 Promise 库&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;main&quot;</span><span class="punctuation">:</span> <span class="string">&quot;dist/wx-promise-pro.js&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;types&quot;</span><span class="punctuation">:</span> <span class="string">&quot;index.d.ts&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;build&quot;</span><span class="punctuation">:</span> <span class="string">&quot;rollup -c&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;postversion&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn build &amp;&amp; git push --tags &amp;&amp; git push &amp;&amp; npm publish&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;repository&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;git&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;git+https://github.com/youngjuning/wx-promise-pro.git&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;keywords&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;promise&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;es6&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;then&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;catch&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;finally&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;小程序&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;weapp&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;miniapp&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;weixin&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;author&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;紫升&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;email&quot;</span><span class="punctuation">:</span> <span class="string">&quot;youngjuning@aliyun.com&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://blog.zisheng.pro&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;license&quot;</span><span class="punctuation">:</span> <span class="string">&quot;MIT&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;bugs&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://github.com/youngjuning/wx-promise-pro/issues&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;homepage&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://github.com/youngjuning/wx-promise-pro#readme&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;devDependencies&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;@babel/core&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^7.8.7&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;@babel/preset-env&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^7.8.7&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;rollup&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^1.28.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;rollup-plugin-babel&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^4.3.3&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;rollup-plugin-commonjs&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^10.1.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;rollup-plugin-json&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^4.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;rollup-plugin-node-resolve&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^5.2.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;rollup-plugin-terser&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^5.1.3&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/8e944d3670b0/</id>
    <link href="https://blog.zisheng.pro/posts/8e944d3670b0/"/>
    <published>2022-11-17T04:02:58.000Z</published>
    <summary>
      <![CDATA[<h2 id="全局"><a href="#全局" class="headerlink" title="全局"></a>全局</h2><ul>
<li><code>npm root --location=global</code>：查看全局包安装路径<blockquote>
<p]]>
    </summary>
    <title>NPM 常用命令</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="前端开发" scheme="https://blog.zisheng.pro/tags/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    <category term="NPM" scheme="https://blog.zisheng.pro/tags/NPM/"/>
    <category term="前端" scheme="https://blog.zisheng.pro/tags/%E5%89%8D%E7%AB%AF/"/>
    <category term="前端工程化" scheme="https://blog.zisheng.pro/tags/%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%8C%96/"/>
    <category term="swiper" scheme="https://blog.zisheng.pro/tags/swiper/"/>
    <content>
      <![CDATA[<p>帮助开发者从 NPM 包选型的角度感知和建立前端工程化体系。</p><h2 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h2><h3 id="单元测试"><a href="#单元测试" class="headerlink" title="单元测试"></a>单元测试</h3><p><a href="https://www.npmjs.com/package/tape">tape</a></p><h2 id="编译工具"><a href="#编译工具" class="headerlink" title="编译工具"></a>编译工具</h2><ul><li><a href="https://www.npmjs.com/package/babel">babel</a></li></ul><h2 id="构建工具"><a href="#构建工具" class="headerlink" title="构建工具"></a>构建工具</h2><ul><li><a href="https://www.npmjs.com/package/webpack">webpack</a></li><li><a href="https://www.npmjs.com/package/rollup">rollup</a></li><li><a href="https://www.npmjs.com/package/esbuild">esbuild</a></li></ul><h2 id="dotenv"><a href="#dotenv" class="headerlink" title="dotenv"></a>dotenv</h2><ul><li><a href="https://www.npmjs.com/package/dotenv">dotenv</a></li></ul><h2 id="Node-js-CLI"><a href="#Node-js-CLI" class="headerlink" title="Node.js CLI"></a>Node.js CLI</h2><h3 id="框架"><a href="#框架" class="headerlink" title="框架"></a>框架</h3><ul><li><a href="https://www.npmjs.com/package/commander">commander</a>: Node.js命令行界面的完整解决方案,受Ruby Commander启发。 前端开发node cli 必备技能。</li><li><a href="https://www.npmjs.com/package/yargs">yargs</a>: 功能比较全，但是使用起来比较复杂。</li></ul><h3 id="命令行参数解析"><a href="#命令行参数解析" class="headerlink" title="命令行参数解析"></a>命令行参数解析</h3><ul><li><a href="https://www.npmjs.com/package/yargs-parser">yargs-parser</a>：yargs 命令行框架内置的命令行参数解析库，功能比较全，比较适合工程化实用。</li><li><a href="https://www.npmjs.com/package/minimist">minimist</a>：如果你喜欢轻量级的技术，那么 minimist 足够简单好用，代码量也很少（只有几百行），非常适合研读。</li><li>inquire.js</li><li>chalk: 美化命令行的模块</li><li>ora: 优雅的 spinner</li><li>ncp: 异步的拷贝文件，包含空文件夹</li><li>recursive-readdir: 递归地列出目录和子目录下的所有文件，不包含目录本身。</li><li>shelljs: 在 Node.js 中执行 Unix shell 命令</li><li>log-symbols: 打印日志的特殊标志</li><li><a href="https://www.npmjs.com/package/download-git-repo">download-git-repo</a>: Download and extract a git repository (GitHub, GitLab, Bitbucket) from node</li><li><a href="https://www.npmjs.com/package/global-prefix">global-prefix</a>: 获取 npm 全局安装的前缀</li><li><a href="https://www.npmjs.com/package/exec-sh">exec-sh</a>: 执行 shell 命令转发所有 stdio 流，比 shelljs exec 命令好用</li><li><a href="https://www.npmjs.com/package/wml">wml</a>: wml 侦听某个文件夹中的更改（使用 watchman），然后将更改的文件复制到另一个文件夹中。</li><li><a href="https://blog.ihaiu.com/cmd-open/">cmd-open</a>: 扩展 Windows 命令 open</li><li><a href="https://github.com/shelljs/shx">shx</a>: shx is a wrapper around ShellJS Unix commands, providing an easy solution for simple Unix-like, cross-platform commands in npm package scripts.</li></ul><h2 id="CSS"><a href="#CSS" class="headerlink" title="CSS"></a>CSS</h2><h3 id="css-modules"><a href="#css-modules" class="headerlink" title="css modules"></a>css modules</h3><ul><li><a href="https://www.npmjs.com/package/clsx">clsx</a></li><li><a href="https://www.npmjs.com/package/classnames">classnames</a></li></ul><h3 id="css-in-js"><a href="#css-in-js" class="headerlink" title="css-in-js"></a>css-in-js</h3><ul><li><a href="https://www.npmjs.com/package/styled-components">styled-components</a></li></ul><h2 id="React"><a href="#React" class="headerlink" title="React"></a>React</h2><h3 id="状态管理"><a href="#状态管理" class="headerlink" title="状态管理"></a>状态管理</h3><ul><li><a href="https://www.npmjs.com/package/redux">redux</a></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/db7a22999938/</id>
    <link href="https://blog.zisheng.pro/posts/db7a22999938/"/>
    <published>2022-11-02T07:58:31.000Z</published>
    <summary>本文主要收了一些常用 NPM 包，帮助开发者从 NPM 包选型的角度感知和建立前端工程化体系。</summary>
    <title>2023 常用 NPM 包推荐</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="网络编程" scheme="https://blog.zisheng.pro/tags/%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B/"/>
    <category term="Token 刷新" scheme="https://blog.zisheng.pro/tags/Token-%E5%88%B7%E6%96%B0/"/>
    <content>
      <![CDATA[<p>为了安全考虑，一般后端都要求在请求接口时传递登录的 Token。为了防止 Token 泄漏的风险，服务器的 Token 一般不会设置太长时间，比如我最近联调的，两个小时就会过期，token 过期就需要重新登陆。频繁要求登陆会造成用户体验差，所以后端同时会提供刷新 Token 的接口，本文就是记录一下几种无感知刷新的方法。</p><h2 id="方案一"><a href="#方案一" class="headerlink" title="方案一"></a>方案一</h2><p>在登录时，后端返回过期时间，前端每次请求就判断 token 的过期时间，如果快到过期时间，就去调用刷新 token 接口，我们可以封装一个 refreshToken 方法：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">refreshToken</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="title function_">dayjs</span>().<span class="title function_">diff</span>(<span class="params">LOCAL_REFRESH_TIME &gt; LOCAL_EXPIRE</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">global</span>.<span class="property">workPromise</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="variable language_">global</span>.<span class="property">workPromise</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="variable language_">global</span>.<span class="property">workPromise</span> = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="keyword">async</span> (resolve) =&gt; &#123;</span><br><span class="line">      <span class="keyword">const</span> &#123;data&#125; = <span class="keyword">await</span> <span class="title function_">request</span>(&#123;</span><br><span class="line">        <span class="attr">url</span>: <span class="string">`https://api.com/login`</span>,</span><br><span class="line">        <span class="comment">// other config</span></span><br><span class="line">        <span class="attr">method</span>: <span class="string">&#x27;POST&#x27;</span>,</span><br><span class="line">      &#125;);</span><br><span class="line">      <span class="comment">// 更新 LOCAL_REFRESH_TIME</span></span><br><span class="line">      <span class="variable language_">global</span>.<span class="property">workPromise</span> = <span class="literal">null</span>;</span><br><span class="line">      <span class="title function_">resolve</span>()</span><br><span class="line">    &#125;)</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">global</span>.<span class="property">workPromise</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="title class_">Access</span>_Token</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>该方法主要原理是通过将 refresh 接口请求存在全局的 workPromise 中来保证在并发请求接口时只存在一个请求。</p><h2 id="方案二"><a href="#方案二" class="headerlink" title="方案二"></a>方案二</h2><p>登录时设置定时器刷新 token 接口，请求的时候判断当前是否有 workPromise 存在，如果存在就等刷新完成。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 登录后设置定时器</span></span><br><span class="line"><span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">global</span>.<span class="property">workPromise</span> = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="keyword">async</span> (resolve) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123;data&#125; = <span class="keyword">await</span> <span class="title function_">request</span>(&#123;</span><br><span class="line">      <span class="attr">url</span>: <span class="string">`https://api.com/login`</span>,</span><br><span class="line">      <span class="comment">// other config</span></span><br><span class="line">      <span class="attr">method</span>: <span class="string">&#x27;POST&#x27;</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">    cookies.<span class="title function_">set</span>(<span class="string">&#x27;refresh_time&#x27;</span>)</span><br><span class="line">    <span class="variable language_">global</span>.<span class="property">workPromise</span> = <span class="literal">null</span>;</span><br><span class="line">    <span class="title function_">resolve</span>()</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;, <span class="variable constant_">EXPIRE</span> / <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">request</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="variable language_">global</span>.<span class="property">workPromise</span>) &#123;</span><br><span class="line">    <span class="keyword">await</span> <span class="variable language_">global</span>.<span class="title function_">workPromise</span>()</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>该方案由于有定时器一直存在，会额外消耗资源，不推荐使用。</p><h2 id="方案三"><a href="#方案三" class="headerlink" title="方案三"></a>方案三</h2><p>如果使用了 axios，可以在请求响应拦截器中拦截，判断 token 返回过期后，调用刷新 token 接口。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&#x27;axios&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 是否正在刷新的标记</span></span><br><span class="line"><span class="keyword">let</span> isRefreshing = <span class="literal">false</span></span><br><span class="line"><span class="comment">//重试队列</span></span><br><span class="line"><span class="keyword">let</span> requests = []</span><br><span class="line">service.<span class="property">interceptors</span>.<span class="property">response</span>.<span class="title function_">use</span>(</span><br><span class="line">  <span class="function"><span class="params">response</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">//约定code 409 token 过期</span></span><br><span class="line">    <span class="keyword">if</span> (response.<span class="property">data</span>.<span class="property">code</span> === <span class="number">409</span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (!isRefreshing) &#123;</span><br><span class="line">        isRefreshing = <span class="literal">true</span></span><br><span class="line">        <span class="comment">//调用刷新token的接口</span></span><br><span class="line">        <span class="keyword">return</span> <span class="title function_">refreshToken</span>().<span class="title function_">then</span>(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">          <span class="keyword">const</span> &#123; token &#125; = res.<span class="property">data</span></span><br><span class="line">          <span class="comment">// 替换token</span></span><br><span class="line">          <span class="title function_">setToken</span>(token)</span><br><span class="line">          response.<span class="property">headers</span>.<span class="property">Authorization</span> = <span class="string">`<span class="subst">$&#123;token&#125;</span>`</span></span><br><span class="line">           <span class="comment">// token 刷新后将数组的方法重新执行</span></span><br><span class="line">          requests.<span class="title function_">forEach</span>(<span class="function">(<span class="params">cb</span>) =&gt;</span> <span class="title function_">cb</span>(token))</span><br><span class="line">          requests = [] <span class="comment">// 重新请求完清空</span></span><br><span class="line">          <span class="keyword">return</span> <span class="title function_">service</span>(response.<span class="property">config</span>)</span><br><span class="line">        &#125;).<span class="title function_">catch</span>(<span class="function"><span class="params">err</span> =&gt;</span> &#123;</span><br><span class="line">          <span class="comment">//跳到登录页</span></span><br><span class="line">          <span class="title function_">removeToken</span>()</span><br><span class="line">          router.<span class="title function_">push</span>(<span class="string">&#x27;/login&#x27;</span>)</span><br><span class="line">          <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(err)</span><br><span class="line">        &#125;).<span class="title function_">finally</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">          isRefreshing = <span class="literal">false</span></span><br><span class="line">        &#125;)</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 返回未执行 resolve 的 Promise</span></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function"><span class="params">resolve</span> =&gt;</span> &#123;</span><br><span class="line">          <span class="comment">// 用函数形式将 resolve 存入，等待刷新后再执行</span></span><br><span class="line">          requests.<span class="title function_">push</span>(<span class="function"><span class="params">token</span> =&gt;</span> &#123;</span><br><span class="line">            response.<span class="property">headers</span>.<span class="property">Authorization</span> = <span class="string">`<span class="subst">$&#123;token&#125;</span>`</span></span><br><span class="line">            <span class="title function_">resolve</span>(<span class="title function_">service</span>(response.<span class="property">config</span>))</span><br><span class="line">          &#125;)</span><br><span class="line">        &#125;)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> response &amp;&amp; response.<span class="property">data</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="function">(<span class="params">error</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">reject</span>(error)</span><br><span class="line">  &#125;</span><br><span class="line">)</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/ac2c22966f31/</id>
    <link href="https://blog.zisheng.pro/posts/ac2c22966f31/"/>
    <published>2022-10-31T05:43:33.000Z</published>
    <summary>为了安全考虑，一般后端都要求在请求接口时传递登录的 Token。为了防止 Token 泄漏的风险，服务器的 Token 一般不会设置太长时间，比如我最近联调的，两个小时就会过期，token 过期就需要重新登陆。</summary>
    <title>前端无感知刷新 Token</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="JavaScript" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/JavaScript/"/>
    <category term="JavaScript 隐式类型转换" scheme="https://blog.zisheng.pro/tags/JavaScript-%E9%9A%90%E5%BC%8F%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2/"/>
    <category term="JavaScript 相等操作符" scheme="https://blog.zisheng.pro/tags/JavaScript-%E7%9B%B8%E7%AD%89%E6%93%8D%E4%BD%9C%E7%AC%A6/"/>
    <content>
      <![CDATA[<p>隐式类型转换多存在于使用 <code>==</code> 进行比较的场景，例如：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">var</span> b = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;a == b&#x27;</span>); <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>不同类型的值用相等运算符比较后的结果，可以用下表总结。</p><p><img src="https://s2.loli.net/2022/04/16/WZboPTdBRmXQkja.png" alt="紫升"></p><p><code>toNumber</code> 方法对不同类型返回的结果如下。</p><p><img src="https://s2.loli.net/2022/04/17/VQ7BxmTqcOFUh34.png" alt="紫升"></p><p><code>toPrimitive()</code>：如果对象的 <code>valueOf</code> 方法的结果是原始值，返回原始值；如果对象的 <code>toString</code> 方法返回原始值，就返回这个值；其他情况都返回一个错误。</p><p>用例子来验证一下表格中的结果。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="literal">true</span> == <span class="string">&#x27;true&#x27;</span>); <span class="comment">// false</span></span><br></pre></td></tr></table></figure><ul><li>首先，布尔值会被 <code>toNumber</code> 方法转换为数字，因此得到 <code>true == 1</code></li><li>其次，用 <code>toNumber</code> 转换字符串值。因为字符串包含字母，所以会被转成 <code>NaN</code>，因此表达式就变成了 <code>1 == NaN</code>，结果就是 false。</li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/f1f7395da421/</id>
    <link href="https://blog.zisheng.pro/posts/f1f7395da421/"/>
    <published>2022-04-16T09:32:32.000Z</published>
    <summary>JavaScript 隐式类型转换之相等操作符</summary>
    <title>JavaScript 隐式类型转换之相等操作符</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="JavaScript" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/JavaScript/"/>
    <category term="面试题" scheme="https://blog.zisheng.pro/tags/%E9%9D%A2%E8%AF%95%E9%A2%98/"/>
    <category term="for...in" scheme="https://blog.zisheng.pro/tags/for-in/"/>
    <category term="for...of" scheme="https://blog.zisheng.pro/tags/for-of/"/>
    <category term="JavaScript Array" scheme="https://blog.zisheng.pro/tags/JavaScript-Array/"/>
    <category term="JavaScript Map" scheme="https://blog.zisheng.pro/tags/JavaScript-Map/"/>
    <category term="JavaScript Set" scheme="https://blog.zisheng.pro/tags/JavaScript-Set/"/>
    <content>
      <![CDATA[<h2 id="迭代的输入不同"><a href="#迭代的输入不同" class="headerlink" title="迭代的输入不同"></a>迭代的输入不同</h2><ul><li><code>for...in</code> 可以对任何对象进行操作；它通常被用作检查对象的属性。</li><li><code>for...of</code> 只能对可迭代对象进行操作；它通常被用作迭代数组的元素。</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> obj = &#123;</span><br><span class="line">  <span class="attr">a</span>: <span class="number">1</span>,</span><br><span class="line">  <span class="attr">b</span>: <span class="number">2</span>,</span><br><span class="line">  <span class="attr">c</span>: <span class="number">3</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">var</span> key <span class="keyword">in</span> obj) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(key); <span class="comment">// &quot;a&quot;, &quot;b&quot;, &quot;c&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">var</span> value <span class="keyword">of</span> obj) &#123; <span class="comment">// Uncaught TypeError: obj is not iterable</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(value);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="迭代的输出不同"><a href="#迭代的输出不同" class="headerlink" title="迭代的输出不同"></a>迭代的输出不同</h2><ul><li><code>for...in</code> 返回被迭代对象的键列表</li><li><code>for...of</code> 返回被迭代对象的数字属性的值列表</li></ul><h3 id="Array"><a href="#Array" class="headerlink" title="Array"></a>Array</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> list = [<span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>];</span><br><span class="line">list.<span class="property">foo</span> = <span class="string">&quot;bar&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i <span class="keyword">in</span> list) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(i); <span class="comment">// &quot;0&quot;, &quot;1&quot;, &quot;2&quot;, &quot;foo&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i <span class="keyword">of</span> list) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(i); <span class="comment">// &quot;&quot;4&quot;, &quot;5&quot;, &quot;6&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Set"><a href="#Set" class="headerlink" title="Set"></a>Set</h3><p><img src="https://s2.loli.net/2022/04/12/RycBMqHSUPr8uXg.png" alt="JavaScript Set"></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> pets = <span class="keyword">new</span> <span class="title class_">Set</span>([<span class="string">&#x27;cat&#x27;</span>, <span class="string">&#x27;dog&#x27;</span>, <span class="string">&#x27;bat&#x27;</span>]);</span><br><span class="line">pets[<span class="string">&quot;species&quot;</span>] = <span class="string">&quot;mammal&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> pet <span class="keyword">in</span> pets) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(pet); <span class="comment">// &quot;species&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> pet <span class="keyword">of</span> pets) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(pet); <span class="comment">// &quot;cat&quot;, &quot;dog&quot;, &quot;bat&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Map"><a href="#Map" class="headerlink" title="Map"></a>Map</h3><p><img src="https://s2.loli.net/2022/04/12/x3WkK54cEUq2pDn.png" alt="JavaScript Map"></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> pets = <span class="keyword">new</span> <span class="title class_">Map</span>([[<span class="string">&quot;1&quot;</span>, <span class="string">&quot;cat&quot;</span>],[<span class="string">&quot;2&quot;</span>, <span class="string">&quot;dog&quot;</span>],[<span class="string">&quot;3&quot;</span>, <span class="string">&quot;bat&quot;</span>]]);</span><br><span class="line">pets[<span class="string">&quot;species&quot;</span>] = <span class="string">&quot;mammal&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> pet <span class="keyword">in</span> pets) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(pet); <span class="comment">// &quot;species&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> pet <span class="keyword">of</span> pets) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(pet); <span class="comment">// [&quot;1&quot;, &quot;cat&quot;], [&quot;2&quot;, &quot;dog&quot;], [&quot;3&quot;, &quot;bat&quot;]</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ul><li><a href="https://stackoverflow.com/questions/29285897/what-is-the-difference-between-for-in-and-for-of-statements">What is the difference between ( for… in ) and ( for… of ) statements?</a></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/124e984dfd7e/</id>
    <link href="https://blog.zisheng.pro/posts/124e984dfd7e/"/>
    <published>2022-04-12T09:13:30.000Z</published>
    <summary>本文详细介绍了 for-in 和 for-of 的区别，包括迭代的输入不同、迭代的输出不同、Array、Set、Map 等。</summary>
    <title>for-in 和 for-of 的区别</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="全栈开发" scheme="https://blog.zisheng.pro/categories/%E5%85%A8%E6%A0%88%E5%BC%80%E5%8F%91/"/>
    <category term="MongoDB" scheme="https://blog.zisheng.pro/tags/MongoDB/"/>
    <content>
      <![CDATA[<h1 id="MongoDB-介绍"><a href="#MongoDB-介绍" class="headerlink" title="MongoDB 介绍"></a>MongoDB 介绍</h1><blockquote><p>一个面向文档对象的数据库。</p></blockquote><p>MongoDB 是一个基于分布式文件存储的数据库。由 C++ 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。</p><p>MongoDB 是一个介于关系数据库和非关系数据库之间的产品，是非关系数据库当中功能最丰富，最像关系数据库的。</p><h1 id="管理-MongoDB"><a href="#管理-MongoDB" class="headerlink" title="管理 MongoDB"></a>管理 MongoDB</h1><h2 id="安装-MongoDB"><a href="#安装-MongoDB" class="headerlink" title="安装 MongoDB"></a>安装 MongoDB</h2><p>首先确保你的包是最新的：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt update &amp;&amp; sudo apt upgrade -y</span><br></pre></td></tr></table></figure><p>然后安装 MongoDB：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt install mongodb</span><br></pre></td></tr></table></figure><p>检查 mongodb 服务的状态：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo systemctl status mongodb</span><br></pre></td></tr></table></figure><p><img src="https://s2.loli.net/2022/04/13/YX7SqhNajgwHuZv.png" alt="紫升"></p><p>可以看到，服务正常启动了。</p><h2 id="管理-MongoDB-1"><a href="#管理-MongoDB-1" class="headerlink" title="管理 MongoDB"></a>管理 MongoDB</h2><h3 id="服务状态管理"><a href="#服务状态管理" class="headerlink" title="服务状态管理"></a>服务状态管理</h3><p>MongoDB 目前是一个 <code>systemd</code> 服务，因此我们使用 <code>systemctl</code> 来检查和修改它的状态，使用以下命令：</p><ul><li>查看运行状态：<code>sudo systemctl status mongodb</code></li><li>停止服务状态：<code>sudo systemctl stop mongodb</code></li><li>启动服务状态：<code>sudo systemctl start mongodb</code></li><li>重启服务状态：<code>sudo systemctl restart mongodb</code></li><li>启用开机自启动：<code>sudo systemctl enable mongodb</code></li><li>禁用开机自启动：<code>sudo systemctl disable mongodb</code></li></ul><h3 id="配置管理"><a href="#配置管理" class="headerlink" title="配置管理"></a>配置管理</h3><ul><li>查看配置文件：<code>sudo nano /etc/mongodb.conf</code></li></ul><h3 id="日志管理"><a href="#日志管理" class="headerlink" title="日志管理"></a>日志管理</h3><ul><li>查看日志：<code>sudo journalctl -u mongodb</code></li><li>查看错误日志：<code>sudo journalctl -u mongodb --no-pager -n 100 -f | grep -i error</code></li><li>查看警告日志：<code>sudo journalctl -u mongodb --no-pager -n 100 -f | grep -i warning</code></li><li>查看提示日志：<code>sudo journalctl -u mongodb --no-pager -n 100 -f | grep -i notice</code></li></ul><h3 id="卸载-MongoDB"><a href="#卸载-MongoDB" class="headerlink" title="卸载 MongoDB"></a>卸载 MongoDB</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">sudo systemctl stop mongodb</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">sudo apt purge mongodb</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">sudo apt autoremove</span></span><br></pre></td></tr></table></figure><h2 id="远程管理-MongoDB"><a href="#远程管理-MongoDB" class="headerlink" title="远程管理 MongoDB"></a>远程管理 MongoDB</h2><p>默认无法远程访问 MongoDB，我们需要先在服务器安全组中开放 <code>27017</code> 端口，然后再进行配置：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">sudo /etc/mongodb.conf</span></span><br></pre></td></tr></table></figure><p>修改  <code>bind_ip = 0.0.0.0</code> 为 <code>bind_ip = 0.0.0.0</code>，然后重启服务：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">sudo systemctl restart mongodb</span></span><br></pre></td></tr></table></figure><p>安装 <a href="https://studio3t.com/free">Studio 3T for Free</a>，然后连接：</p><p><img src="https://s2.loli.net/2022/04/13/tUBEgnVpSAc715F.png" alt="紫升"></p><h2 id="安全管理"><a href="#安全管理" class="headerlink" title="安全管理"></a>安全管理</h2><p>默认我们不需要认证即可连接 MongoDB，这是很危险的，我们需要设置一个管理员用户来认证：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mongo</span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">use admin</span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">db.createUser(&#123;user:<span class="string">&quot;admin&quot;</span>, <span class="built_in">pwd</span>:<span class="string">&quot;123456&quot;</span>, roles:[&#123;role:<span class="string">&quot;userAdminAnyDatabase&quot;</span>, db:<span class="string">&quot;admin&quot;</span>&#125;]&#125;)</span></span><br></pre></td></tr></table></figure><p>然后配置 MongoDB 的安全策略：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">sudo nano /etc/mongodb.conf</span></span><br></pre></td></tr></table></figure><p>将 <code>security.authorization = enabled</code> 写入文件，然后重启服务：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo systemctl restart mongodb</span><br></pre></td></tr></table></figure><p>在 Studio 3T 中配置用户名密码即可，需要注意的是 Authentication DB 需要填写对应的。</p><p><img src="https://s2.loli.net/2022/04/13/fLhd9t1sZ8KTaRA.png" alt="紫升"></p><p>更多安全知识参考 <a href="https://help.aliyun.com/document_detail/112035.html">MongoDB数据库未授权访问漏洞防御最佳实践</a></p><h1 id="MongoDB-概念"><a href="#MongoDB-概念" class="headerlink" title="MongoDB 概念"></a>MongoDB 概念</h1><ul><li>database：数据库，是 MongoDB 的核心，用来存储数据，每个数据库都有自己的集合，集合中存储的是数据。</li><li>collection：集合，相当于关系型数据库中的表（table）</li><li>document：文档，相当于关系型数据库中的行（row）</li><li>field：字段，相当于关系型数据库中的列（column）</li><li>index：索引，相当于关系型数据库中的索引</li><li>primary key：主键，相当于关系型数据库中的主键，MongoDB 中主键的名称默认为 <code>_id</code></li></ul><h2 id="数据库"><a href="#数据库" class="headerlink" title="数据库"></a>数据库</h2><p>一个 mongodb 中可以建立多个数据库。 MongoDB 的默认数据库为 <code>db</code>，该数据库存储在 data 目录中。</p><p>MongoDB 的单个实例可以容纳多个独立的数据库，每一个都有自己的集合和权限，不同的数据库也防止在不同的文件中。</p><p><code>show dbs</code> 命令可以显示所有数据的列表。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">mongo</span></span><br><span class="line">MongoDB shell version v3.6.3</span><br><span class="line">connecting to: mongodb://127.0.0.1:27017</span><br><span class="line">MongoDB server version: 3.6.3</span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">show dbs</span></span><br><span class="line">admin   0.000GB</span><br><span class="line">config  0.000GB</span><br><span class="line">local   0.000GB</span><br></pre></td></tr></table></figure><p>运行 <code>use</code> 命令可以切换数据库，如果不存在则会自动创建。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">use <span class="built_in">test</span></span></span><br><span class="line">switched to db test</span><br></pre></td></tr></table></figure><p>执行 <code>db</code> 命令可以显示当前数据库对象或集合。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">db</span></span><br><span class="line">test</span><br></pre></td></tr></table></figure><h3 id="数据库命名规范"><a href="#数据库命名规范" class="headerlink" title="数据库命名规范"></a>数据库命名规范</h3><ul><li>不能是空字符</li><li>不能含有 <code>&#39; &#39;</code>（空格）、<code>.</code>、<code>/</code>、<code>\</code> 和 <code>\0</code>（空字符）</li><li>应全部小写</li><li>最多 64 字节</li></ul><h3 id="默认数据库"><a href="#默认数据库" class="headerlink" title="默认数据库"></a>默认数据库</h3><ul><li>admin：从权限的角度来看，这是 root 数据库。要是将一个用户添加到这个数据库，这个用户自动继承所有数据库的权限。一些特定的服务器端命令也只能从这个数据库运行，比如列出所有的数据库或者关闭服务器。</li><li>local：这个数据永远不会被复制，可以用来存储限于本地单台服务器的任意集合。</li><li>config：当 Mongo 用于分片设置时，config 数据库在内部使用，用于保存分片的相关信息。</li></ul><h1 id="连接-MongoDB"><a href="#连接-MongoDB" class="headerlink" title="连接 MongoDB"></a>连接 MongoDB</h1><p>标准 URI 连接语法：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]</span><br></pre></td></tr></table></figure><ul><li><code>mongodb://</code>：这是固定的格式，必须指定</li><li><code>username:password@</code>：可选项，如果设置，在连接数据库服务器之后，驱动都会尝试登录这个数据库</li><li><code>host1</code>：必须的，指定至少一个 host，host1 是这个 URI 唯一要填写的。它指定了要连接服务器的地址。如果要连接复制集，请指定多个主机地址。</li><li><code>portX</code>：可选的指定端口，如果不填，默认为 27017</li><li><code>/database</code>：如果指定 <code>username:password@</code>，连接并验证登录指定数据库。若不指定，默认打开 test 数据库。</li><li><code>?options</code>：是连接选项。如果不使用 <code>/database</code>，则前面需要加上 <code>/</code>。所有连接选项都是键值对 <code>name=value</code>，键值对之间通过 <code>&amp;</code> 或 <code>;</code> 隔开</li></ul><h2 id="nodejs"><a href="#nodejs" class="headerlink" title="nodejs"></a>nodejs</h2><h3 id="安装驱动"><a href="#安装驱动" class="headerlink" title="安装驱动"></a>安装驱动</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">yarn add mongodb</span></span><br></pre></td></tr></table></figure><h3 id="连接数据库"><a href="#连接数据库" class="headerlink" title="连接数据库"></a>连接数据库</h3><p>要在 MongoDB 中创建一个数据库，首先我们需要创建一个 MongoClient 对象，然后配置好指定的 URL 和端口号。</p><p>如果数据库不存在，MongoDB 将创建数据库并建立连接。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title class_">MongoClient</span> = <span class="built_in">require</span>(<span class="string">&#x27;mongodb&#x27;</span>).<span class="property">MongoClient</span>;</span><br><span class="line"><span class="keyword">var</span> url = <span class="string">&quot;mongodb://localhost:27017/luozhu&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title class_">MongoClient</span>.<span class="title function_">connect</span>(url, <span class="keyword">function</span>(<span class="params">err, db</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (err) <span class="keyword">throw</span> err;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;数据库已创建！&quot;</span>);</span><br><span class="line">  db.<span class="title function_">close</span>();</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="创建集合"><a href="#创建集合" class="headerlink" title="创建集合"></a>创建集合</h3><p>我们可以使用 <code>createCollection()</code> 方法来创建一个集合，这个集合可以是空的，也可以包含数据。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title class_">MongoClient</span> = <span class="built_in">require</span>(<span class="string">&#x27;mongodb&#x27;</span>).<span class="property">MongoClient</span>;</span><br><span class="line"><span class="keyword">var</span> url = <span class="string">&quot;mongodb://localhost:27017/luozhu&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title class_">MongoClient</span>.<span class="title function_">connect</span>(url, <span class="keyword">function</span>(<span class="params">err, db</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (err) <span class="keyword">throw</span> err;</span><br><span class="line">  <span class="keyword">var</span> dbo = db.<span class="title function_">db</span>(<span class="string">&quot;luozhu&quot;</span>);</span><br><span class="line">  dbo.<span class="title function_">createCollection</span>(<span class="string">&quot;site&quot;</span>, <span class="keyword">function</span>(<span class="params">err, res</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (err) <span class="keyword">throw</span> err;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;集合已创建！&quot;</span>);</span><br><span class="line">    db.<span class="title function_">close</span>();</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="插入数据"><a href="#插入数据" class="headerlink" title="插入数据"></a>插入数据</h3><p>以下实例我们连接数据库 luozhu 的 site 表，并插入一条数据。使用 <code>insertOne()</code> 。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title class_">MongoClient</span> = <span class="built_in">require</span>(<span class="string">&#x27;mongodb&#x27;</span>).<span class="property">MongoClient</span>;</span><br><span class="line"><span class="keyword">var</span> url = <span class="string">&quot;mongodb://localhost:27017/luozhu&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title class_">MongoClient</span>.<span class="title function_">connect</span>(url, <span class="keyword">function</span>(<span class="params">err, db</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (err) <span class="keyword">throw</span> err;</span><br><span class="line">  <span class="keyword">var</span> dbo = db.<span class="title function_">db</span>(<span class="string">&quot;luozhu&quot;</span>);</span><br><span class="line">  <span class="keyword">var</span> myobj = &#123; <span class="attr">name</span>: <span class="string">&quot;Google&quot;</span>, <span class="attr">address</span>: <span class="string">&quot;https://google.com&quot;</span> &#125;;</span><br><span class="line"></span><br><span class="line">  dbo.<span class="title function_">collection</span>(<span class="string">&quot;site&quot;</span>).<span class="title function_">insertOne</span>(myobj, <span class="keyword">function</span>(<span class="params">err, res</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (err) <span class="keyword">throw</span> err;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;文档插入成功&quot;</span>);</span><br><span class="line">    db.<span class="title function_">close</span>();</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>如果要插入多条数据，可以使用 <code>insertMany()</code> 。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title class_">MongoClient</span> = <span class="built_in">require</span>(<span class="string">&quot;mongodb&quot;</span>).<span class="property">MongoClient</span>;</span><br><span class="line"><span class="keyword">var</span> url = <span class="string">&quot;mongodb://localhost:27017/&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title class_">MongoClient</span>.<span class="title function_">connect</span>(url, <span class="keyword">function</span>(<span class="params">err, db</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (err) <span class="variable language_">console</span>.<span class="title function_">log</span>(err);</span><br><span class="line">  <span class="keyword">var</span> dbo = db.<span class="title function_">db</span>(<span class="string">&quot;luozhu&quot;</span>);</span><br><span class="line">  <span class="keyword">var</span> myobj = [</span><br><span class="line">    &#123; <span class="attr">name</span>: <span class="string">&#x27;Facebook&#x27;</span>, <span class="attr">website</span>: <span class="string">&#x27;https://www.facebook.com/&#x27;</span> &#125;,</span><br><span class="line">    &#123; <span class="attr">name</span>: <span class="string">&#x27;Github&#x27;</span>, <span class="attr">website</span>: <span class="string">&#x27;https://github.com&#x27;</span>&#125;</span><br><span class="line">  ]</span><br><span class="line">  dbo.<span class="title function_">collection</span>(<span class="string">&quot;site&quot;</span>).<span class="title function_">insertMany</span>(myobj, <span class="keyword">function</span>(<span class="params">err, res</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (err) <span class="variable language_">console</span>.<span class="title function_">log</span>(err);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;文档插入成功&quot;</span>, res.<span class="property">insertedCount</span>);</span><br><span class="line">    db.<span class="title function_">close</span>();</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h3 id="查询数据"><a href="#查询数据" class="headerlink" title="查询数据"></a>查询数据</h3><h4 id="查询所有数据"><a href="#查询所有数据" class="headerlink" title="查询所有数据"></a>查询所有数据</h4><p>可以使用 <code>find()</code> 来查找数据。<code>find()</code> 可以返回符合条件的所有数据。如果未指定条件，<code>find()</code> 返回集合中的所有数据。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title class_">MongoClient</span> = <span class="built_in">require</span>(<span class="string">&quot;mongodb&quot;</span>).<span class="property">MongoClient</span>;</span><br><span class="line"><span class="keyword">var</span> url = <span class="string">&quot;mongodb://localhost:27017/&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title class_">MongoClient</span>.<span class="title function_">connect</span>(url, <span class="keyword">function</span>(<span class="params">err, db</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span>(err) <span class="variable language_">console</span>.<span class="title function_">log</span>(err);</span><br><span class="line">  <span class="keyword">var</span> dbo = db.<span class="title function_">db</span>(<span class="string">&quot;luozhu&quot;</span>);</span><br><span class="line"></span><br><span class="line">  dbo.<span class="title function_">collection</span>(<span class="string">&quot;site&quot;</span>).<span class="title function_">find</span>(&#123;&#125;).<span class="title function_">toArray</span>(<span class="keyword">function</span>(<span class="params">err, result</span>) &#123; <span class="comment">// 返回集合中所有数据</span></span><br><span class="line">    <span class="keyword">if</span>(err) <span class="variable language_">console</span>.<span class="title function_">log</span>(err);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(result);</span><br><span class="line">    db.<span class="title function_">close</span>();</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h4 id="查询指定条件的数据"><a href="#查询指定条件的数据" class="headerlink" title="查询指定条件的数据"></a>查询指定条件的数据</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title class_">MongoClient</span> = <span class="built_in">require</span>(<span class="string">&quot;mongodb&quot;</span>).<span class="property">MongoClient</span>;</span><br><span class="line"><span class="keyword">var</span> url = <span class="string">&quot;mongodb://localhost:27017/&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title class_">MongoClient</span>.<span class="title function_">connect</span>(url, <span class="keyword">function</span>(<span class="params">err, db</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span>(err) <span class="variable language_">console</span>.<span class="title function_">log</span>(err);</span><br><span class="line">  <span class="keyword">var</span> dbo = db.<span class="title function_">db</span>(<span class="string">&quot;luozhu&quot;</span>);</span><br><span class="line">  <span class="keyword">var</span> whereStr = &#123;<span class="string">&quot;name&quot;</span>:<span class="string">&#x27;Github&#x27;</span>&#125;;  <span class="comment">// 查询条件</span></span><br><span class="line">  dbo.<span class="title function_">collection</span>(<span class="string">&quot;site&quot;</span>).<span class="title function_">find</span>(whereStr).<span class="title function_">toArray</span>(<span class="keyword">function</span>(<span class="params">err, result</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span>(err) <span class="variable language_">console</span>.<span class="title function_">log</span>(err);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(result);</span><br><span class="line">    db.<span class="title function_">close</span>();</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="更新文档"><a href="#更新文档" class="headerlink" title="更新文档"></a>更新文档</h3><h4 id="更新一条文档"><a href="#更新一条文档" class="headerlink" title="更新一条文档"></a>更新一条文档</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title class_">MongoClient</span> = <span class="built_in">require</span>(<span class="string">&quot;mongodb&quot;</span>).<span class="property">MongoClient</span>;</span><br><span class="line"><span class="keyword">var</span> url = <span class="string">&quot;mongodb://localhost:27017/&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title class_">MongoClient</span>.<span class="title function_">connect</span>(url, <span class="keyword">function</span>(<span class="params">err, db</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span>(err) <span class="variable language_">console</span>.<span class="title function_">log</span>(err);</span><br><span class="line">  <span class="keyword">var</span> dbo = db.<span class="title function_">db</span>(<span class="string">&quot;luozhu&quot;</span>);</span><br><span class="line">  <span class="keyword">var</span> whereStr = &#123; <span class="string">&quot;name&quot;</span>: <span class="string">&#x27;Github&#x27;</span> &#125;;  <span class="comment">// 查询条件</span></span><br><span class="line">  <span class="keyword">var</span> updateStr = &#123; <span class="attr">$set</span>:&#123; <span class="string">&quot;website&quot;</span>: <span class="string">&quot;https://github.com/youngjuning&quot;</span> &#125;&#125;;  <span class="comment">// 更新内容</span></span><br><span class="line">  dbo.<span class="title function_">collection</span>(<span class="string">&quot;site&quot;</span>).<span class="title function_">updateOne</span>(whereStr, updateStr, <span class="keyword">function</span> (<span class="params">err, res</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span>(err) <span class="variable language_">console</span>.<span class="title function_">log</span>(err);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;文档更新成功&quot;</span>, res.<span class="property">matchedCount</span>);</span><br><span class="line">    db.<span class="title function_">close</span>();</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>执行成功后，进入 mongo 管理工具查看数据已修改：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&gt; db.site.find(&#123; name: <span class="string">&quot;GitHub&quot;</span> &#125;).<span class="function"><span class="title">pretty</span></span>()</span><br><span class="line">&#123;</span><br><span class="line">    <span class="string">&quot;_id&quot;</span> : ObjectId(<span class="string">&quot;5a794e36763eb821b24db854&quot;</span>),</span><br><span class="line">    <span class="string">&quot;name&quot;</span> : <span class="string">&quot;Github&quot;</span>,</span><br><span class="line">    <span class="string">&quot;url&quot;</span> : <span class="string">&quot;https://www.github.com&quot;</span>     <span class="comment"># 已修改为 https</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="更新多条文档"><a href="#更新多条文档" class="headerlink" title="更新多条文档"></a>更新多条文档</h4><p>如果要跟新所有符合条件的文档数据，可以使用 <code>updateMany()</code> 。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title class_">MongoClient</span> = <span class="built_in">require</span>(<span class="string">&#x27;mongodb&#x27;</span>).<span class="property">MongoClient</span>;</span><br><span class="line"><span class="keyword">var</span> url = <span class="string">&quot;mongodb://localhost:27018/&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title class_">MongoClient</span>.<span class="title function_">connect</span>(url, <span class="function">(<span class="params">err, db</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (err) <span class="variable language_">console</span>.<span class="title function_">log</span>(err);</span><br><span class="line">  <span class="keyword">var</span> dbo = db.<span class="title function_">db</span>(<span class="string">&quot;luozhu&quot;</span>);</span><br><span class="line">  <span class="keyword">var</span> whereStr = &#123;<span class="string">&quot;country&quot;</span>: <span class="string">&#x27;us&#x27;</span>&#125;;  <span class="comment">// 查询条件</span></span><br><span class="line">  <span class="keyword">var</span> updateStr = &#123;<span class="attr">$set</span>: &#123; <span class="string">&quot;country&quot;</span> : <span class="string">&quot;American&quot;</span> &#125;&#125;;</span><br><span class="line">  dbo.<span class="title function_">collection</span>(<span class="string">&quot;site&quot;</span>).<span class="title function_">updateMany</span>(whereStr, updateStr, <span class="function">(<span class="params">err, res</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (err) <span class="variable language_">console</span>.<span class="title function_">log</span>(err);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(res.<span class="property">modifiedCount</span> + <span class="string">&quot; 条文档被更新&quot;</span>);</span><br><span class="line">    db.<span class="title function_">close</span>();</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p><code>res.modifiedCount</code> 属性返回修改的文档数量。</p><h3 id="删除数据"><a href="#删除数据" class="headerlink" title="删除数据"></a>删除数据</h3><h4 id="删除一条数据"><a href="#删除一条数据" class="headerlink" title="删除一条数据"></a>删除一条数据</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title class_">MongoClient</span> = <span class="built_in">require</span>(<span class="string">&#x27;mongodb&#x27;</span>).<span class="property">MongoClient</span>;</span><br><span class="line"><span class="keyword">var</span> url = <span class="string">&quot;mongodb://localhost:27018/&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title class_">MongoClient</span>.<span class="title function_">connect</span>(url, <span class="function">(<span class="params">err, db</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span>(err) <span class="variable language_">console</span>.<span class="title function_">log</span>(err);</span><br><span class="line">  <span class="keyword">var</span> dbo = db.<span class="title function_">db</span>(<span class="string">&quot;luozhu&quot;</span>);</span><br><span class="line">  <span class="keyword">var</span> whereStr = &#123;<span class="string">&quot;name&quot;</span>: <span class="string">&#x27;Github&#x27;</span>&#125;;  <span class="comment">// 查询条件</span></span><br><span class="line">  dbo.<span class="title function_">collection</span>(<span class="string">&quot;site&quot;</span>).<span class="title function_">deleteOne</span>(whereStr, <span class="function">(<span class="params">err, res</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span>(err) <span class="variable language_">console</span>.<span class="title function_">log</span>(err);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(res.<span class="property">deletedCount</span> + <span class="string">&quot; 条文档被删除&quot;</span>);</span><br><span class="line">    db.<span class="title function_">close</span>();</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h4 id="删除多条数据"><a href="#删除多条数据" class="headerlink" title="删除多条数据"></a>删除多条数据</h4><p>如果要删除多条语句可以使用 <code>deleteMany()</code> 方法。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="title class_">MongoClient</span> = <span class="built_in">require</span>(<span class="string">&#x27;mongodb&#x27;</span>).<span class="property">MongoClient</span>;</span><br><span class="line"><span class="keyword">var</span> url = <span class="string">&quot;mongodb://localhost:27018/&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title class_">MongoClient</span>.<span class="title function_">connect</span>(url, <span class="keyword">function</span>(<span class="params">err, db</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (err) <span class="variable language_">console</span>.<span class="title function_">log</span>(err);</span><br><span class="line">  <span class="keyword">var</span> dbo = db.<span class="title function_">db</span>(<span class="string">&quot;luozhu&quot;</span>);</span><br><span class="line">  <span class="keyword">var</span> whereStr = &#123;<span class="attr">country</span>: <span class="string">&quot;American&quot;</span>&#125;;  <span class="comment">// 查询条件</span></span><br><span class="line">  dbo.<span class="title function_">collection</span>(<span class="string">&quot;site&quot;</span>).<span class="title function_">deleteMany</span>(whereStr, <span class="function">(<span class="params">err, res</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span>(err) <span class="variable language_">console</span>.<span class="title function_">log</span>(err);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(res.<span class="property">deletedCount</span>, <span class="string">&quot;条文档被删除&quot;</span>);</span><br><span class="line">    db.<span class="title function_">close</span>();</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p><code>res.deletedCount</code> 属性返回删除的文档数量。</p><h1 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h1><ul><li><a href="https://zhuanlan.zhihu.com/p/76349679">如何在 Ubuntu 上安装 MongoDB</a></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/69a1dcb04fe1/</id>
    <link href="https://blog.zisheng.pro/posts/69a1dcb04fe1/"/>
    <published>2022-04-12T08:55:53.000Z</published>
    <summary>
      <![CDATA[<h1 id="MongoDB-介绍"><a href="#MongoDB-介绍" class="headerlink" title="MongoDB 介绍"></a>MongoDB 介绍</h1><blockquote>
<p>一个面向文档对象的数据库。</p>
</block]]>
    </summary>
    <title>MongoDB 学习笔记</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="nodejs" scheme="https://blog.zisheng.pro/tags/nodejs/"/>
    <category term="vscode" scheme="https://blog.zisheng.pro/tags/vscode/"/>
    <category term="ide" scheme="https://blog.zisheng.pro/tags/ide/"/>
    <category term="code-sever" scheme="https://blog.zisheng.pro/tags/code-sever/"/>
    <category term="webide" scheme="https://blog.zisheng.pro/tags/webide/"/>
    <content>
      <![CDATA[<h2 id="WebIDE-概述"><a href="#WebIDE-概述" class="headerlink" title="WebIDE 概述"></a>WebIDE 概述</h2><h3 id="WebIDE-介绍"><a href="#WebIDE-介绍" class="headerlink" title="WebIDE 介绍"></a>WebIDE 介绍</h3><p>WebIDE 是基于 H5 支持多语言的在线集成开发环境。相较于传统的本地 IDE，WebIDE 是开箱即用的，你不用配置 git 环境、node 环境、react 环境甚至 React Native 环境。</p><h3 id="WebIDE-发展与现状"><a href="#WebIDE-发展与现状" class="headerlink" title="WebIDE 发展与现状"></a>WebIDE 发展与现状</h3><ul><li>基于 <a href="https://codemirror.net/">CodeMirror</a> 和 <a href="https://ace.c9.io/">Ace</a> 实现编辑器部分，其余部分自己实现</li><li>以 <a href="https://codesandbox.io/">CodeSandbox</a> 为代表的的在线代码编辑器服务，私有部署较麻烦</li><li><a href="https://gitlab.com/youngjuning/react">Gitlab ide</a>：集成在 Gitlab 中的 IDE</li><li><a href="https://github.com/facebook/react/">Github ide</a>：基于 vscode 实现的与 github 结合的 IDE，限制是命令行使用有限制，</li><li><a href="https://codingcorp.cloudstudio.net/ws/qmbngm">Cloud Studio</a>：可以理解为国产 CodeSandbox，以前是基于 Ace 做的，现在也改成了基于 vscode，自定义功能通过 vscode 的插件体系来做</li></ul><p>通过比对市场上常见的 WebIDE 产品，我们能发现趋势都趋于统一了，就是依托 vscode 强大的能力来实现的，扩展能力也是基于 vscode 来实现。</p><h3 id="WebIDE-通用架构"><a href="#WebIDE-通用架构" class="headerlink" title="WebIDE 通用架构"></a>WebIDE 通用架构</h3><p><img src="https://s2.loli.net/2022/03/23/UfjaX4QpTmg5MKu.png" alt="紫升"></p><h3 id="WebIDE-的优势"><a href="#WebIDE-的优势" class="headerlink" title="WebIDE 的优势"></a>WebIDE 的优势</h3><ul><li>在具有一致开发环境的任何设备上编写代码</li><li>使用云服务器加速测试、编译、下载等来节省旅途中的笔记本电量</li><li>所有密集型任务都在您的服务器上运行，节省笔记本内存</li></ul><h3 id="工程化意义"><a href="#工程化意义" class="headerlink" title="工程化意义"></a>工程化意义</h3><ul><li>安全性：服务部署在内网，完全不用担心安全问题，且插件服务也可以内网部署。</li><li>规范性：通过定制编辑器，我们可以规范开发阶段的流程与环境</li><li>提效性：通过实时预览技术，可以实现开发阶段通过 mock 数据即可预览面板。另外由于是针对 OS 业务定制，所以开发套件都是定制好的，开发只需要关心代码</li><li>平台化：可以和内部任何平台打通交互，比如一键打包、一键发布</li></ul><h2 id="基于-code-server-搭建-WebIDE-平台"><a href="#基于-code-server-搭建-WebIDE-平台" class="headerlink" title="基于 code-server 搭建 WebIDE 平台"></a>基于 code-server 搭建 WebIDE 平台</h2><p>code-server 是一个免费的基于浏览器的 IDE，它是基于 vscode 封装的跨平台 IDE。</p><h3 id="部署"><a href="#部署" class="headerlink" title="部署"></a>部署</h3><h4 id="安装-nodejs"><a href="#安装-nodejs" class="headerlink" title="安装 nodejs"></a>安装 nodejs</h4><p><strong>安装源</strong></p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash -</span><br></pre></td></tr></table></figure><p><strong>安装 nodejs</strong></p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-get install -y nodejs</span><br></pre></td></tr></table></figure><p><strong>更新 npm</strong></p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo npm install -g npm</span><br></pre></td></tr></table></figure><h4 id="安装-code-server"><a href="#安装-code-server" class="headerlink" title="安装 code-server"></a>安装 code-server</h4><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -fsSL https://code-server.dev/install.sh | sh</span><br></pre></td></tr></table></figure><p>执行下面的命令以支持 systemd 方式启动并开始开机自启动：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo systemctl <span class="built_in">enable</span> --now code-server@<span class="variable">$USER</span></span><br></pre></td></tr></table></figure><p>之后如果修改了配置文件，就可以使用 <code>sudo systemctl restart code-server@$USER</code> 重启服务。</p><p>或者你不需要后台服务，你可以执行下面的命令：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">code-server</span><br></pre></td></tr></table></figure><h4 id="nginx-反向代理"><a href="#nginx-反向代理" class="headerlink" title="nginx 反向代理"></a>nginx 反向代理</h4><p>安装 nginx：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo apt update</span><br><span class="line">sudo apt install -y nginx</span><br></pre></td></tr></table></figure><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">server &#123;</span><br><span class="line">    listen 80;</span><br><span class="line">    listen [::]:80;</span><br><span class="line">    server_name mydomain.com;</span><br><span class="line"></span><br><span class="line">    location / &#123;</span><br><span class="line">      proxy_pass http://localhost:8080/;</span><br><span class="line">      proxy_set_header Host <span class="variable">$host</span>;</span><br><span class="line">      proxy_set_header Upgrade <span class="variable">$http_upgrade</span>;</span><br><span class="line">      proxy_set_header Connection upgrade;</span><br><span class="line">      proxy_set_header Accept-Encoding gzip;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="扩展阅读"><a href="#扩展阅读" class="headerlink" title="扩展阅读"></a>扩展阅读</h2><ul><li><a href="https://www.zhihu.com/question/41050429">要实现一个Web IDE 需要哪些前端技术？</a></li><li><a href="https://developer.aliyun.com/article/646273">WebIDE 使用指南 - 阿里云</a></li><li><a href="https://docs.gitlab.com/ee/user/project/web_ide/">网络IDE - GitLab</a></li><li><a href="https://www.youtube.com/watch?v=lfYo4mfOyM8">What is CodeSandbox</a></li><li><a href="https://juejin.cn/post/7008428269317914661">如何在团队快速落地 WebIDE</a></li><li><a href="https://learnku.com/articles/42581">Ubuntu 安装最新版本 Node.js</a></li><li><a href="https://www.jianshu.com/p/648c48f146c6">腾讯云登录Root用户</a></li><li><a href="https://blog.zisheng.pro/21bdf0b4bd15/">写给前端的Linux实战教程</a></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/dfd0faf39738/</id>
    <link href="https://blog.zisheng.pro/posts/dfd0faf39738/"/>
    <published>2022-03-19T10:00:00.000Z</published>
    <summary>WebIDE 是基于 H5 支持多语言的在线集成开发环境。相较于传统的本地 IDE，WebIDE 是开箱即用的，你不用配置 git 环境、node 环境、react 环境甚至 React Native 环境。</summary>
    <title>基于 code-server 实现 WebIDE 的探索</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="加密货币" scheme="https://blog.zisheng.pro/categories/%E5%8A%A0%E5%AF%86%E8%B4%A7%E5%B8%81/"/>
    <category term="区块链" scheme="https://blog.zisheng.pro/categories/%E5%8A%A0%E5%AF%86%E8%B4%A7%E5%B8%81/%E5%8C%BA%E5%9D%97%E9%93%BE/"/>
    <category term="MetaMask" scheme="https://blog.zisheng.pro/tags/MetaMask/"/>
    <category term="BSC" scheme="https://blog.zisheng.pro/tags/BSC/"/>
    <category term="币安智能链" scheme="https://blog.zisheng.pro/tags/%E5%B8%81%E5%AE%89%E6%99%BA%E8%83%BD%E9%93%BE/"/>
    <category term="区块链" scheme="https://blog.zisheng.pro/tags/%E5%8C%BA%E5%9D%97%E9%93%BE/"/>
    <category term="加密货币" scheme="https://blog.zisheng.pro/tags/%E5%8A%A0%E5%AF%86%E8%B4%A7%E5%B8%81/"/>
    <category term="以太坊" scheme="https://blog.zisheng.pro/tags/%E4%BB%A5%E5%A4%AA%E5%9D%8A/"/>
    <category term="Ethereum" scheme="https://blog.zisheng.pro/tags/Ethereum/"/>
    <category term="以太坊钱包" scheme="https://blog.zisheng.pro/tags/%E4%BB%A5%E5%A4%AA%E5%9D%8A%E9%92%B1%E5%8C%85/"/>
    <category term="Ethereum Wallet" scheme="https://blog.zisheng.pro/tags/Ethereum-Wallet/"/>
    <category term="以太坊钱包应用" scheme="https://blog.zisheng.pro/tags/%E4%BB%A5%E5%A4%AA%E5%9D%8A%E9%92%B1%E5%8C%85%E5%BA%94%E7%94%A8/"/>
    <content>
      <![CDATA[<p>币安智能链是目前世界上最大的以太坊交易所，MetaMask 则是专注服务于以太坊的钱包应用。</p><h2 id="入口"><a href="#入口" class="headerlink" title="入口"></a>入口</h2><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images/202112301451827.png" alt="紫升"></p><h2 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h2><ul><li>网络名称: <code>币安智能链</code></li><li>RPC URL: <code>https://bsc-dataseed1.binance.org/</code></li><li>链 ID: <code>56</code></li><li>Currency Symbol: <code>WBNB</code></li><li>区块浏览器: <code>https://bscscan.com/</code></li></ul><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://academy.binance.com/zh/articles/connecting-metamask-to-binance-smart-chain">在币安智能链中关联MetaMask钱包</a></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/5cb0c0ce25b8/</id>
    <link href="https://blog.zisheng.pro/posts/5cb0c0ce25b8/"/>
    <published>2021-12-30T02:36:17.000Z</published>
    <summary>币安智能链是目前世界上最大的以太坊交易所，MetaMask 则是专注服务于以太坊的钱包应用。</summary>
    <title>使用 MetaMask 连接到币安智能链（BSC）</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React/"/>
    <category term="react" scheme="https://blog.zisheng.pro/tags/react/"/>
    <category term="hooks" scheme="https://blog.zisheng.pro/tags/hooks/"/>
    <category term="useEffect" scheme="https://blog.zisheng.pro/tags/useEffect/"/>
    <category term="useState" scheme="https://blog.zisheng.pro/tags/useState/"/>
    <content>
      <![CDATA[<h2 id="Warning-Can’t-perform-a-React-state-update-on-an-unmounted-component"><a href="#Warning-Can’t-perform-a-React-state-update-on-an-unmounted-component" class="headerlink" title="Warning: Can’t perform a React state update on an unmounted component"></a>Warning: Can’t perform a React state update on an unmounted component</h2><p>由于 <code>useEffect</code> 中有异步操作，当组件卸载后异步操作结束并执行 state 操作，React 就会提醒存在内存泄漏的风险。在 React Hooks 中，解决方法是在异步操作中判断是否组件被卸载了。代码如下：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">useAsync</span>(<span class="params">asyncFn, onSuccess</span>) &#123;</span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> isActive = <span class="literal">true</span>;</span><br><span class="line">    <span class="title function_">asyncFn</span>().<span class="title function_">then</span>(<span class="function"><span class="params">data</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (isActive) <span class="title function_">onSuccess</span>(data);</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123; isActive = <span class="literal">false</span> &#125;;</span><br><span class="line">  &#125;, [asyncFn, onSuccess]);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>实际项目中，我们可以使用 ahooks 提供的功能更强大的 <code>useAsyncEffect</code>：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useAsyncEffect &#125; <span class="keyword">from</span> <span class="string">&#x27;ahooks&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">React</span>, &#123; useState &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">mockCheck</span>(<span class="params"></span>): <span class="title class_">Promise</span>&lt;boolean&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> <span class="title function_">resolve</span>(<span class="literal">true</span>), <span class="number">3000</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> [pass, setPass] = useState&lt;boolean&gt;(<span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line">  <span class="title function_">useAsyncEffect</span>(<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">    <span class="title function_">setPass</span>(<span class="keyword">await</span> <span class="title function_">mockCheck</span>());</span><br><span class="line">  &#125;, []);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;pass === null &amp;&amp; &#x27;Checking...&#x27;&#125;</span></span><br><span class="line"><span class="language-xml">      &#123;pass === true &amp;&amp; &#x27;Check passed.&#x27;&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/c22a00ea88bc/</id>
    <link href="https://blog.zisheng.pro/posts/c22a00ea88bc/"/>
    <published>2021-12-10T02:44:31.000Z</published>
    <summary>本文记录了一些 React Hooks 使用中的问题</summary>
    <title>React Hooks 问题记录</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="加密货币" scheme="https://blog.zisheng.pro/categories/%E5%8A%A0%E5%AF%86%E8%B4%A7%E5%B8%81/"/>
    <category term="区块链" scheme="https://blog.zisheng.pro/categories/%E5%8A%A0%E5%AF%86%E8%B4%A7%E5%B8%81/%E5%8C%BA%E5%9D%97%E9%93%BE/"/>
    <category term="Next.js" scheme="https://blog.zisheng.pro/tags/Next-js/"/>
    <category term="BSC" scheme="https://blog.zisheng.pro/tags/BSC/"/>
    <category term="DApp" scheme="https://blog.zisheng.pro/tags/DApp/"/>
    <content>
      <![CDATA[<h2 id="项目初始化"><a href="#项目初始化" class="headerlink" title="项目初始化"></a>项目初始化</h2><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npx create-next-app@latest --typescript</span><br></pre></td></tr></table></figure><p>安装完成后，根据指示开启开发模式的 server。然后尝试编辑 <code>pages/index.tsx</code> 并在浏览器查看结果。</p><h2 id="开发规范"><a href="#开发规范" class="headerlink" title="开发规范"></a>开发规范</h2><h3 id="commitlint"><a href="#commitlint" class="headerlink" title="commitlint"></a>commitlint</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npx @luozhu/create-commitlint</span><br></pre></td></tr></table></figure><h3 id="prettier"><a href="#prettier" class="headerlink" title="prettier"></a>prettier</h3><h4 id="安装依赖"><a href="#安装依赖" class="headerlink" title="安装依赖"></a>安装依赖</h4><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn add prettier eslint-config-prettier eslint-plugin-prettier @luozhu/prettier-config -D</span><br></pre></td></tr></table></figure><h4 id="配置-eslintrc-json"><a href="#配置-eslintrc-json" class="headerlink" title="配置 .eslintrc.json"></a>配置 .eslintrc.json</h4><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;extends&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;next/core-web-vitals&quot;</span><span class="punctuation">,</span> <span class="string">&quot;plugin:prettier/recommended&quot;</span><span class="punctuation">,</span> <span class="string">&quot;prettier&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;rules&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;no-unused-vars&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;react-hooks/exhaustive-deps&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;@next/next/no-img-element&quot;</span><span class="punctuation">:</span> <span class="number">0</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h4 id="配置-prettierrc-js"><a href="#配置-prettierrc-js" class="headerlink" title="配置 .prettierrc.js"></a>配置 .prettierrc.js</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = <span class="built_in">require</span>(<span class="string">&#x27;@luozhu/prettier-config&#x27;</span>);</span><br></pre></td></tr></table></figure><h3 id="editorconfig"><a href="#editorconfig" class="headerlink" title=".editorconfig"></a>.editorconfig</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"># EditorConfig is awesome: http://EditorConfig.org</span><br><span class="line"></span><br><span class="line"># top-most EditorConfig file</span><br><span class="line">root = true</span><br><span class="line"></span><br><span class="line"># Unix-style newlines with a newline ending every file</span><br><span class="line">[*]</span><br><span class="line">quote_type = single # Fix Prettier &quot;prettier.singleQuote&quot; not working in 1.40 vs code</span><br><span class="line">indent_style = space</span><br><span class="line">indent_size = 2</span><br><span class="line">end_of_line = lf</span><br><span class="line">charset = utf-8</span><br><span class="line">trim_trailing_whitespace = true</span><br><span class="line">insert_final_newline = true</span><br><span class="line"></span><br><span class="line">[*.md]</span><br><span class="line">trim_trailing_whitespace = false</span><br><span class="line"></span><br><span class="line">[Makefile]</span><br><span class="line">indent_style = tab</span><br></pre></td></tr></table></figure><h3 id="lint-staged"><a href="#lint-staged" class="headerlink" title="lint-staged"></a>lint-staged</h3><p>安装 lint-staged：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn add lint-staged -D</span><br></pre></td></tr></table></figure><p>在 <code>package.json</code> 文件中如下配置即可：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;gitHooks&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;pre-commit&quot;</span><span class="punctuation">:</span> <span class="string">&quot;lint-staged&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;commit-msg&quot;</span><span class="punctuation">:</span> <span class="string">&quot;commitlint -e -V&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;lint-staged&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;**/*.&#123;js,jsx,ts,tsx&#125;&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;eslint --fix&quot;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;**/*.&#123;md,json&#125;&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;prettier --write&quot;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="antd-开发"><a href="#antd-开发" class="headerlink" title="antd 开发"></a>antd 开发</h2><h3 id="安装依赖-1"><a href="#安装依赖-1" class="headerlink" title="安装依赖"></a>安装依赖</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn add antd</span><br></pre></td></tr></table></figure><h3 id="NextJS-使用-Less-编译-Antd"><a href="#NextJS-使用-Less-编译-Antd" class="headerlink" title="NextJS 使用 Less 编译 Antd"></a>NextJS 使用 Less 编译 Antd</h3><p>使用 yarn 安装 next-with-less 包，并顺带最新版本的 less 和 less-loader：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn add next-with-less less less-loader -D</span><br></pre></td></tr></table></figure><p>并修改 <code>next.config.js</code> 配置文件：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// next.config.js</span></span><br><span class="line"><span class="keyword">const</span> withLess = <span class="built_in">require</span>(<span class="string">&quot;next-with-less&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">/** <span class="doctag">@type</span> &#123;<span class="type">import(&#x27;next&#x27;).NextConfig</span>&#125; */</span></span><br><span class="line"><span class="keyword">let</span> config = &#123;</span><br><span class="line">  <span class="attr">reactStrictMode</span>: <span class="literal">true</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">config = <span class="title function_">withLess</span>(&#123;</span><br><span class="line">  ...config,</span><br><span class="line">  <span class="attr">lessLoaderOptions</span>: &#123;</span><br><span class="line">    <span class="attr">lessOptions</span>: &#123;</span><br><span class="line">      <span class="attr">modifyVars</span>: &#123;</span><br><span class="line">        <span class="string">&quot;@primary-color&quot;</span>: <span class="string">&quot;#f74a49&quot;</span>,</span><br><span class="line">        <span class="string">&quot;@border-radius-base&quot;</span>: <span class="string">&quot;.5em&quot;</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = config</span><br></pre></td></tr></table></figure><h3 id="引入-antd-less"><a href="#引入-antd-less" class="headerlink" title="引入 antd less"></a>引入 antd less</h3><p>在 <code>pages/_app.tsx</code> 中引入 less</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">&#x27;antd/lib/style/themes/default.less&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">&#x27;antd/dist/antd.less&#x27;</span></span><br></pre></td></tr></table></figure><h3 id="使用-dayjs-替换-moment-js"><a href="#使用-dayjs-替换-moment-js" class="headerlink" title="使用 dayjs 替换 moment.js"></a>使用 dayjs 替换 moment.js</h3><p>按照 <a href="https://ant.design/docs/react/replace-moment-cn#%E8%87%AA%E5%AE%9A%E4%B9%89%E7%BB%84%E4%BB%B6">自定义组件</a> 的方式自定义组件在 Next.js 中是不够的，需要使用 next-transpile-modules 做进一步的处理：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// next.config.js</span></span><br><span class="line"><span class="keyword">const</span> withTM = <span class="built_in">require</span>(<span class="string">&#x27;next-transpile-modules&#x27;</span>)([</span><br><span class="line">  <span class="string">&#x27;rc-picker&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;rc-util&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;antd&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;rc-pagination&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;rc-notification&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;@ant-design/icons&#x27;</span>,</span><br><span class="line">]);</span><br><span class="line"></span><br><span class="line"><span class="comment">/** <span class="doctag">@type</span> &#123;<span class="type">import(&#x27;next&#x27;).NextConfig</span>&#125; */</span></span><br><span class="line"><span class="keyword">let</span> config = &#123;</span><br><span class="line">  <span class="attr">reactStrictMode</span>: <span class="literal">true</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">config = <span class="title function_">withTM</span>(config);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = config</span><br></pre></td></tr></table></figure><p>在 <code>_app.tsx</code> 中初始化 dayjs：</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> dayjs <span class="keyword">from</span> <span class="string">&#x27;dayjs&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 初始化 dayjs</span></span><br><span class="line"><span class="keyword">import</span> <span class="string">&#x27;dayjs/locale/zh-cn&#x27;</span>;</span><br><span class="line">dayjs.<span class="title function_">locale</span>(<span class="string">&#x27;zh-cn&#x27;</span>);</span><br></pre></td></tr></table></figure><h3 id="布局"><a href="#布局" class="headerlink" title="布局"></a>布局</h3><p>布局使用的是 antd 的 Layout，我选用的是侧边栏布局。</p><h4 id="Layout-tsx"><a href="#Layout-tsx" class="headerlink" title="Layout.tsx"></a>Layout.tsx</h4><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Layout</span>, <span class="title class_">Menu</span>, <span class="title class_">Breadcrumb</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;antd&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Sider</span> <span class="keyword">from</span> <span class="string">&#x27;./Sider&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> &#123; <span class="title class_">Header</span>, <span class="title class_">Content</span>, <span class="title class_">Footer</span> &#125; = <span class="title class_">Layout</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">AppLayout</span> = (<span class="params">&#123;children&#125;</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">Layout</span> <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">minHeight:</span> &#x27;<span class="attr">100vh</span>&#x27; &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Sider</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Layout</span> <span class="attr">className</span>=<span class="string">&quot;site-layout&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Header</span> <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">padding:</span> <span class="attr">0</span>, <span class="attr">backgroundColor:</span> &#x27;#<span class="attr">ffffff</span>&#x27; &#125;&#125; /&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Content</span> <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">margin:</span> &#x27;<span class="attr">0</span> <span class="attr">16px</span>&#x27; &#125;&#125;&gt;</span>&#123;children&#125;<span class="tag">&lt;/<span class="name">Content</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Footer</span> <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">textAlign:</span> &#x27;<span class="attr">center</span>&#x27; &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">            Crypto Meta ©2021 Powered by&#123;&#x27; &#x27;&#125;</span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">a</span> <span class="attr">className</span>=<span class="string">&quot;default&quot;</span> <span class="attr">href</span>=<span class="string">&quot;https://nextjs.org/&quot;</span> <span class="attr">target</span>=<span class="string">&quot;_blank&quot;</span> <span class="attr">rel</span>=<span class="string">&quot;noreferrer&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">              Next.js</span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;/<span class="name">a</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;/<span class="name">Footer</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">Layout</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">Layout</span>&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="Sider-tsx"><a href="#Sider-tsx" class="headerlink" title="Sider.tsx"></a>Sider.tsx</h4><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Layout</span>, <span class="title class_">Menu</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;antd&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">React</span>, &#123; useState &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; useRouter &#125; <span class="keyword">from</span> <span class="string">&#x27;next/router&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Link</span> <span class="keyword">from</span> <span class="string">&#x27;next/link&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> menus <span class="keyword">from</span> <span class="string">&#x27;config/menus&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> &#123; <span class="title class_">Sider</span> &#125; = <span class="title class_">Layout</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">AppSider</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> router = <span class="title function_">useRouter</span>();</span><br><span class="line">  <span class="keyword">const</span> [collapsed, setCollapsed] = <span class="title function_">useState</span>(<span class="literal">false</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">onCollapse</span> = (<span class="params">collapsed: <span class="built_in">boolean</span></span>) =&gt; &#123;</span><br><span class="line">    <span class="title function_">setCollapsed</span>(collapsed);</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Sider</span> <span class="attr">breakpoint</span>=<span class="string">&quot;lg&quot;</span> <span class="attr">collapsible</span> <span class="attr">collapsed</span>=<span class="string">&#123;collapsed&#125;</span> <span class="attr">onCollapse</span>=<span class="string">&#123;onCollapse&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Menu</span> <span class="attr">theme</span>=<span class="string">&quot;dark&quot;</span> <span class="attr">mode</span>=<span class="string">&quot;inline&quot;</span> <span class="attr">selectedKeys</span>=<span class="string">&#123;[router.pathname]&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          &#123;menus.map(menu =&gt; (</span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">Menu.Item</span> <span class="attr">key</span>=<span class="string">&#123;menu.route&#125;</span> <span class="attr">icon</span>=<span class="string">&#123;menu.icon&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">              <span class="tag">&lt;<span class="name">Link</span> <span class="attr">href</span>=<span class="string">&#123;menu.route&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">                <span class="tag">&lt;<span class="name">a</span>&gt;</span>&#123;menu.name&#125;<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">              <span class="tag">&lt;/<span class="name">Link</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;/<span class="name">Menu.Item</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          ))&#125;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">Menu</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">Sider</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">AppSider</span>;</span><br></pre></td></tr></table></figure><h4 id="config-menus-tsx"><a href="#config-menus-tsx" class="headerlink" title="config&#x2F;menus.tsx"></a>config&#x2F;menus.tsx</h4><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> [</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">route</span>: <span class="string">&#x27;/cryptoyou&#x27;</span>,</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;Crypto You&#x27;</span>,</span><br><span class="line">    <span class="attr">icon</span>: (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">img</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">src</span>=<span class="string">&quot;https://cdn.jsdelivr.net/gh/youngjuning/images/202112081051602.png&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">width</span>=<span class="string">&quot;30&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">alt</span>=<span class="string">&quot;thecryptoyou&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      /&gt;</span></span></span><br><span class="line">    ),</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">route</span>: <span class="string">&#x27;/squid&#x27;</span>,</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;Squid NFT&#x27;</span>,</span><br><span class="line">    <span class="attr">icon</span>: (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">img</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">src</span>=<span class="string">&quot;https://cdn.jsdelivr.net/gh/youngjuning/images/202112082111091.png&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">width</span>=<span class="string">&quot;30&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">alt</span>=<span class="string">&quot;thecryptoyou&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      /&gt;</span></span></span><br><span class="line">    ),</span><br><span class="line">  &#125;,</span><br><span class="line">];</span><br></pre></td></tr></table></figure><h3 id="国际化"><a href="#国际化" class="headerlink" title="国际化"></a>国际化</h3><p>antd 提供了一个 React 组件 ConfigProvider 用于全局配置国际化文案。新建 <code>components/Layout/Provider.tsx</code>：</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">ConfigProvider</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;antd&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> zhCN <span class="keyword">from</span> <span class="string">&#x27;antd/lib/locale/zh_CN&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">Provider</span> = (<span class="params">&#123; children &#125;</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">ConfigProvider</span> <span class="attr">locale</span>=<span class="string">&#123;zhCN&#125;</span>&gt;</span>&#123;children&#125;<span class="tag">&lt;/<span class="name">ConfigProvider</span>&gt;</span></span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Provider</span>;</span><br></pre></td></tr></table></figure><p>然后在 <code>components/Layout/index.tsx</code> 中引入：</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">Provider</span> <span class="keyword">from</span> <span class="string">&#x27;./Provider&#x27;</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">AppLayout</span>(<span class="params">&#123; children &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">Provider</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      // ...</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">Provider</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="面包屑导航"><a href="#面包屑导航" class="headerlink" title="面包屑导航"></a>面包屑导航</h3><p>参考 <a href="https://stackoverflow.com/questions/64541235/breadcrumbs-and-nextjs">Breadcrumbs and NextJS</a> 封装了一个 Breadcrumb 组件：</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Breadcrumbs</span> <span class="keyword">from</span> <span class="string">&#x27;nextjs-antd-breadcrumbs&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">Example</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">Breadcrumbs</span> <span class="attr">rootLabel</span>=<span class="string">&quot;Home&quot;</span> <span class="attr">omitRootLabel</span>=<span class="string">&#123;false&#125;/</span>&gt;</span></span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="Next-js-开发"><a href="#Next-js-开发" class="headerlink" title="Next.js 开发"></a>Next.js 开发</h2><h3 id="基于根目录导入模块"><a href="#基于根目录导入模块" class="headerlink" title="基于根目录导入模块"></a>基于根目录导入模块</h3><p><code>baseUrl</code> 配置选项允许您直接从项目的根目录导入。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// tsconfig.json or jsconfig.json</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;compilerOptions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;baseUrl&quot;</span><span class="punctuation">:</span> <span class="string">&quot;.&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="模块路径别名"><a href="#模块路径别名" class="headerlink" title="模块路径别名"></a>模块路径别名</h3><p>在 tsconfig.json 中加入以下配置：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;compilerOptions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;baseUrl&quot;</span><span class="punctuation">:</span> <span class="string">&quot;.&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;paths&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;@/components/*&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;./components/*&quot;</span><span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>其他的地址依葫芦画瓢加到 paths 对象中即可。</p><h3 id="redirects-永久重定向"><a href="#redirects-永久重定向" class="headerlink" title="redirects 永久重定向"></a>redirects 永久重定向</h3><p>永久重定向不同于重写路由，它会在 url 中表现出来，在 Next.js 中重定向是在 <code>next.config.js</code> 中配置的：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> config = &#123;</span><br><span class="line">  <span class="keyword">async</span> <span class="title function_">redirects</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">source</span>: <span class="string">&#x27;/&#x27;</span>,</span><br><span class="line">        <span class="attr">destination</span>: <span class="string">&#x27;/cryptoyou&#x27;</span>,</span><br><span class="line">        <span class="attr">permanent</span>: <span class="literal">true</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">    ];</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = config;</span><br></pre></td></tr></table></figure><h3 id="在-pages-目录包含非页面文件"><a href="#在-pages-目录包含非页面文件" class="headerlink" title="在 pages 目录包含非页面文件"></a>在 pages 目录包含非页面文件</h3><p>要把测试文件、生成的文件或其他组件使用的文件放在 <code>pages</code> 目录中，你可以在扩展名前加上类似 <code>page</code> 的字样。</p><p>打开 <code>next.config.js</code> 并添加 <code>pageExtensions</code> 配置：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">pageExtensions</span>: [<span class="string">&#x27;page.tsx&#x27;</span>, <span class="string">&#x27;page.ts&#x27;</span>, <span class="string">&#x27;page.jsx&#x27;</span>, <span class="string">&#x27;page.js&#x27;</span>],</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后重命名你的页面，使其有一个包括 <code>.page</code> 的文件扩展名（例如，重命名 <code>MyPage.tsx</code> 为<code>MyPage.page.tsx</code>）。</p><blockquote><p>注意：确保你也重命名 <code>_document.js</code>、<code>_app.js</code>、<code>_middleware.js</code>，以及 <code>pages/api/</code> 下的文件。</p></blockquote><h3 id="styled-jsx"><a href="#styled-jsx" class="headerlink" title="styled-jsx"></a>styled-jsx</h3><p>next.js 内置支持 styled-jsx，我们要做的是配置支持 sass，首先安装 <code>@styled-jsx/plugin-sass</code>：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn add @styled-jsx/plugin-sass sass node-sass -D</span><br></pre></td></tr></table></figure><p>然后配置 <code>.babelrc</code>：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;presets&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;next/babel&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;styled-jsx&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;plugins&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;@styled-jsx/plugin-sass&quot;</span><span class="punctuation">]</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><blockquote><p>注意：开启 babel 后，会自动禁用 swc，目前还没有 swc 的支持方案，进度请关注 <a href="https://github.com/Thream/styled-jsx-plugin-sass/issues/100">[Improvement] Next.JS 12 support + SWC</a></p></blockquote><h3 id="自定义错误页面"><a href="#自定义错误页面" class="headerlink" title="自定义错误页面"></a>自定义错误页面</h3><h4 id="404-tsx"><a href="#404-tsx" class="headerlink" title="404.tsx"></a>404.tsx</h4><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Result</span>, <span class="title class_">Button</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;antd&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Link</span> <span class="keyword">from</span> <span class="string">&#x27;next/link&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Custom404</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">Result</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">status</span>=<span class="string">&quot;404&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">title</span>=<span class="string">&quot;404&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">subTitle</span>=<span class="string">&quot;Sorry, the page you visited does not exist.&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">extra</span>=<span class="string">&#123;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        &lt;<span class="attr">Button</span> <span class="attr">type</span>=<span class="string">&quot;primary&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Link</span> <span class="attr">href</span>=<span class="string">&quot;/&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">a</span>&gt;</span>返回首页<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;/<span class="name">Link</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">Button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#125;</span></span><br><span class="line"><span class="language-xml">    /&gt;</span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="500-tsx"><a href="#500-tsx" class="headerlink" title="500.tsx"></a>500.tsx</h4><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Result</span>, <span class="title class_">Button</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;antd&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Link</span> <span class="keyword">from</span> <span class="string">&#x27;next/link&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Custom500</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">Result</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">status</span>=<span class="string">&quot;500&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">title</span>=<span class="string">&quot;500&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">subTitle</span>=<span class="string">&quot;Sorry, something went wrong.&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">extra</span>=<span class="string">&#123;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        &lt;<span class="attr">Button</span> <span class="attr">type</span>=<span class="string">&quot;primary&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Link</span> <span class="attr">href</span>=<span class="string">&quot;/&quot;</span>&gt;</span>返回首页<span class="tag">&lt;/<span class="name">Link</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">Button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#125;</span></span><br><span class="line"><span class="language-xml">    /&gt;</span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Dapp"><a href="#Dapp" class="headerlink" title="Dapp"></a>Dapp</h2><h3 id="小狐狸钱包"><a href="#小狐狸钱包" class="headerlink" title="小狐狸钱包"></a>小狐狸钱包</h3><ol><li><a href="https://zhuanlan.zhihu.com/p/368736357">小狐狸钱包MetaMask新手使用教程</a></li><li><a href="https://blog.csdn.net/qq_33583069/article/details/115727642?ivk_sa=1024320u">使用MetaMask连接到币安智能链(BSC)</a></li></ol><h3 id="usedapp-core"><a href="#usedapp-core" class="headerlink" title="@usedapp&#x2F;core"></a>@usedapp&#x2F;core</h3><h4 id="安装依赖-2"><a href="#安装依赖-2" class="headerlink" title="安装依赖"></a>安装依赖</h4><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add @usedapp/core</span><br></pre></td></tr></table></figure><h4 id="设置-Provider"><a href="#设置-Provider" class="headerlink" title="设置 Provider"></a>设置 Provider</h4><p>你需要做的第一件事是用可选的配置设置 DAppProvider，并将你的整个应用程序包裹在其中。本文中我们编辑 <code>components/Layout/Provider.tsx</code>：</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">DAppProvider</span>, <span class="title class_">Config</span>, <span class="title class_">ChainId</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@usedapp/core&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="attr">config</span>: <span class="title class_">Config</span> = &#123;</span><br><span class="line">  <span class="attr">readOnlyChainId</span>: <span class="title class_">ChainId</span>.<span class="property">BSC</span>,</span><br><span class="line">  <span class="attr">readOnlyUrls</span>: &#123;</span><br><span class="line">    [<span class="title class_">ChainId</span>.<span class="property">BSC</span>]: <span class="string">&#x27;https://bsc-dataseed1.binance.org/&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">Provider</span> = (<span class="params">&#123; children &#125;</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">DAppProvider</span> <span class="attr">config</span>=<span class="string">&#123;config&#125;</span>&gt;</span>&#123;children&#125;<span class="tag">&lt;/<span class="name">DAppProvider</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Provider</span>;</span><br></pre></td></tr></table></figure><h4 id="连接钱包"><a href="#连接钱包" class="headerlink" title="连接钱包"></a>连接钱包</h4><p>然后你需要使用 activateBrowserWallet 来激活 provider。最好是在用户点击 “Connect” 按钮时进行。本文中我们新建组件 <code>components/ConnectButton.tsx</code>：</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Button</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;antd&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; useEthers &#125; <span class="keyword">from</span> <span class="string">&#x27;@usedapp/core&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">LoginOutlined</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@ant-design/icons&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">ConnectButton</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; activateBrowserWallet &#125; = <span class="title function_">useEthers</span>();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">handleConnectWallet</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="title function_">activateBrowserWallet</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">Button</span> <span class="attr">size</span>=<span class="string">&quot;large&quot;</span> <span class="attr">type</span>=<span class="string">&quot;primary&quot;</span> <span class="attr">icon</span>=<span class="string">&#123;</span>&lt;<span class="attr">LoginOutlined</span> /&gt;</span>&#125; onClick=&#123;handleConnectWallet&#125;&gt;</span></span><br><span class="line"><span class="language-xml">      Connect MetaMask</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">Button</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">ConnectButton</span>;</span><br></pre></td></tr></table></figure><h4 id="钱包余额"><a href="#钱包余额" class="headerlink" title="钱包余额"></a>钱包余额</h4><p><code>useEtherBalance(address: string)</code></p><p>提供一种获取账户余额的方法。以账户地址为参数，当数据不可用时（即未连接），返回 ·<code>BigNumber</code> 或 <code>undefined</code>。要获得当前连接的账户，请使用 <code>useEthers()</code>。</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; formatEther &#125; <span class="keyword">from</span> <span class="string">&#x27;@ethersproject/units&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">EtherBalance</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; account &#125; = <span class="title function_">useEthers</span>()</span><br><span class="line">  <span class="keyword">const</span> etherBalance = <span class="title function_">useEtherBalance</span>(account)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;etherBalance &amp;&amp; <span class="tag">&lt;<span class="name">p</span>&gt;</span>Balance: &#123;formatEther(etherBalance)&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span>&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="代币余额"><a href="#代币余额" class="headerlink" title="代币余额"></a>代币余额</h4><p><code>useTokenBalance(address: string, tokenAddress: string)</code></p><p>提供一种方法来获取由 <code>tokenAddress</code> 指定的 ERC20 代币在所提供地址的余额。当数据不可用时，返回 <code>BigNumber</code> 或 <code>undefined</code>。</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; formatUnits &#125; <span class="keyword">from</span> <span class="string">&#x27;@ethersproject/units&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">BABY</span> = <span class="string">&#x27;0x53e562b9b7e5e94b81f10e96ee70ad06df3d2657&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">TokenBalance</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; account &#125; = <span class="title function_">useEthers</span>()</span><br><span class="line">  <span class="keyword">const</span> tokenBalance = <span class="title function_">useTokenBalance</span>(<span class="variable constant_">BABY</span>, account)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;tokenBalance &amp;&amp; <span class="tag">&lt;<span class="name">p</span>&gt;</span>BABY: &#123;formatUnits(tokenBalance, 18)&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span>&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h4><ul><li><a href="https://dev.to/jacobedawson/build-a-web3-dapp-in-react-login-with-metamask-4chp">Build a Web3 Dapp in React &amp; Login with MetaMask</a></li></ul><h2 id="部署"><a href="#部署" class="headerlink" title="部署"></a>部署</h2><h3 id="GitHub-Pages"><a href="#GitHub-Pages" class="headerlink" title="GitHub Pages"></a>GitHub Pages</h3><p>如果是 vercel 付费用户，推荐使用 vercel。GitHub Pages 只能部署静态内容，所以需要使用 <code>next export</code> 将静态内容导出部署。</p><p>首先配置 npm scripts：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;preexport&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn build&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;export&quot;</span><span class="punctuation">:</span> <span class="string">&quot;next export&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br></pre></td></tr></table></figure><p>然后添加 <code>.github/workflows/gh-pages.yml</code>：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">github</span> <span class="string">pages</span></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line">  <span class="attr">push:</span></span><br><span class="line">    <span class="attr">branches:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">main</span> <span class="comment"># default branch</span></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">deploy:</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-18.04</span></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">actions/checkout@v2</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">c-hive/gha-yarn-cache@v2</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Install</span> <span class="string">Dependencies</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">yarn</span> <span class="string">--non-interactive</span> <span class="string">--silent</span> <span class="string">--ignore-scripts</span> <span class="string">--production=false</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Build</span> <span class="string">Website</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">yarn</span> <span class="string">export</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Deploy</span> <span class="string">Website</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">peaceiris/actions-gh-pages@v3</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">personal_token:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.PERSONAL_TOKEN</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">external_repository:</span> <span class="string">crypto-meta/crypto-meta.github.io</span></span><br><span class="line">          <span class="attr">publish_dir:</span> <span class="string">./out</span></span><br><span class="line"></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/cba1320bcd71/</id>
    <link href="https://blog.zisheng.pro/posts/cba1320bcd71/"/>
    <published>2021-12-05T14:58:44.000Z</published>
    <summary>本文记录了基于 Next.js 开发 BSC DApp</summary>
    <title>基于 Next.js 开发 BSC DApp</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="微前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/%E5%BE%AE%E5%89%8D%E7%AB%AF/"/>
    <category term="微前端" scheme="https://blog.zisheng.pro/tags/%E5%BE%AE%E5%89%8D%E7%AB%AF/"/>
    <category term="single-spa" scheme="https://blog.zisheng.pro/tags/single-spa/"/>
    <category term="single-spa 原理解析" scheme="https://blog.zisheng.pro/tags/single-spa-%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90/"/>
    <category term="源码解析" scheme="https://blog.zisheng.pro/tags/%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/"/>
    <content>
      <![CDATA[<h2 id="single-spa-是什么？"><a href="#single-spa-是什么？" class="headerlink" title="single-spa 是什么？"></a>single-spa 是什么？</h2><p>Single-spa 是一个将多个单页面应用聚合为一个整体应用的 JavaScript 微前端框架。 使用 single-spa 进行前端架构设计可以带来很多好处，例如:</p><ul><li>在同一页面上使用多个前端框架而不用刷新页面 (React, AngularJS, Angular, Ember, 你正在使用的框架)</li><li>独立部署每一个单页面应用</li><li>新功能使用新框架，旧的单页应用不用重写可以共存</li><li>改善初始加载时间，延迟加载代码</li></ul><p>single-spa 可以说是微前端的鼻祖了，市面上纷杂的解决方案多少都受到了它的影响，以下两款流行的生产级框架则更是基于 single-spa 开发的。</p><ul><li><a href="https://qiankun.umijs.org/zh/guide">qiankun</a>：蚂蚁金服出品的易用的微前端框架。</li><li><a href="https://github.com/namecheap/ilc">Isomorphic Layout Composer（ILC）</a>：一个将微前端组成部分支持 SSR 的完整的解决方案。</li></ul><h2 id="single-spa-架构"><a href="#single-spa-架构" class="headerlink" title="single-spa 架构"></a>single-spa 架构</h2><p>single-spa 借鉴了组件生命周期的思想，它为应用设置了针对路由的生命周期。当应用匹配路由&#x2F;处于激活状态时，应用会把自身的内容挂载到页面上；反之则卸载。典型的 single-spa 由 html 页面、应用注册脚本、应用脚本自身构成。应用注册内容包含：</p><ul><li>appName：应用名</li><li>loadingFunction：加载应用程序的代码。</li><li>activityFunction 函数：确定应用程序何时处于活动状态&#x2F;非活动状态。</li><li>customProps：自定义 props，可以不填</li></ul><p>single-spa 又约定应用脚本包含以下生命周期：</p><ul><li><code>load</code>：当应用匹配路由时就会加载脚本（非函数，只是一种状态）</li><li><code>bootstrap</code>：引导函数（对接 html，应用内容首次挂载到页面前调用）</li><li><code>mount</code>：挂载函数</li><li><code>unmount</code>：卸载函数（须移除事件绑定等内容）</li><li><code>unload</code>：非必要（unload 之后会重新启动 bootstrap 流程；借助 unload 可实现热更新）。</li></ul><p>生命周期函数获得参数包含 name（应用名）、singleSpa（实例）、mountParcel（手动挂载函数）、customProps（自定义信息），生命周期函数必须返回 Promise 或其本身为 async 函数，<code>bootstrap</code>、mount、unmount 生命周期函数不可缺省，生命周期函数可以指定多个，它们会构成异步调用链，逐个调用。简要流程图如下：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images/202111221452146.png" alt="紫升"></p><h2 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h2><p>源码参考：<a href="https://github.com/youngjuning/simple-single-spa-webpack-example">youngjuning&#x2F;simple-single-spa-webpack-example</a></p><h3 id="1-创建一个-html-文件"><a href="#1-创建一个-html-文件" class="headerlink" title="1.创建一个 html 文件"></a>1.创建一个 html 文件</h3><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;single-spa-config.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="2-创建一个-single-spa-config。"><a href="#2-创建一个-single-spa-config。" class="headerlink" title="2.创建一个 single-spa-config。"></a>2.创建一个 single-spa-config。</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> singleSpa <span class="keyword">from</span> <span class="string">&#x27;single-spa&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* loading 是一个返回 promise 的函数，用于 加载/解析 应用代码。</span></span><br><span class="line"><span class="comment"> * 它的目的是为延迟加载提供便利 —— single-spa 只有在需要时才会下载应用程序的代码。</span></span><br><span class="line"><span class="comment"> * 在这个示例中，在 webpack 中支持 import ()并返回 Promise，但是 single-spa 可以使用任何返回 Promise 的加载函数。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">singleSpa.<span class="title function_">registerApplication</span>(<span class="string">&#x27;app-1&#x27;</span>, <span class="function">() =&gt;</span></span><br><span class="line">  <span class="keyword">import</span> (<span class="string">&#x27;../app1/app1.js&#x27;</span>), <span class="title function_">pathPrefix</span>(<span class="string">&#x27;/app1&#x27;</span>));</span><br><span class="line">singleSpa.<span class="title function_">registerApplication</span>(<span class="string">&#x27;app-2&#x27;</span>, <span class="function">() =&gt;</span></span><br><span class="line">  <span class="keyword">import</span> (<span class="string">&#x27;../app2/app2.js&#x27;</span>), <span class="title function_">pathPrefix</span>(<span class="string">&#x27;/app2&#x27;</span>));</span><br><span class="line"></span><br><span class="line">singleSpa.<span class="title function_">start</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">/* Single-spa 配置顶级路由，以确定哪个应用程序对于指定 url 是活动的。</span></span><br><span class="line"><span class="comment"> * 你可以以任何你喜欢的方式实现此路由。</span></span><br><span class="line"><span class="comment"> * 一种有用的约定是在 url 前面加上活动应用程序的名称，以使顶层路由保持简单。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">pathPrefix</span>(<span class="params">prefix</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">function</span>(<span class="params">location</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> location.<span class="property">pathname</span>.<span class="title function_">startsWith</span>(<span class="string">`<span class="subst">$&#123;prefix&#125;</span>`</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-创建一个应用程序。"><a href="#3-创建一个应用程序。" class="headerlink" title="3.创建一个应用程序。"></a>3.创建一个应用程序。</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">ReactDOM</span> <span class="keyword">from</span> <span class="string">&#x27;react-dom&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> singleSpaReact <span class="keyword">from</span> <span class="string">&#x27;single-spa-react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Root</span> <span class="keyword">from</span> <span class="string">&#x27;./root.component.tsx&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> reactLifecycles = <span class="title function_">singleSpaReact</span>(&#123;</span><br><span class="line">  <span class="title class_">React</span>,</span><br><span class="line">  <span class="title class_">ReactDOM</span>,</span><br><span class="line">  <span class="attr">rootComponent</span>: <span class="title class_">Root</span>,</span><br><span class="line">  domElementGetter,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">bootstrap</span>(<span class="params">props</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> reactLifecycles.<span class="title function_">bootstrap</span>(props);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">mount</span>(<span class="params">props</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> reactLifecycles.<span class="title function_">mount</span>(props);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">unmount</span>(<span class="params">props</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> reactLifecycles.<span class="title function_">unmount</span>(props);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">domElementGetter</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">// 确保这里有一个 div 供渲染用</span></span><br><span class="line">  <span class="keyword">let</span> el = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;app1&#x27;</span>);</span><br><span class="line">  <span class="keyword">if</span> (!el) &#123;</span><br><span class="line">    el = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;div&#x27;</span>);</span><br><span class="line">    el.<span class="property">id</span> = <span class="string">&#x27;app1&#x27;</span>;</span><br><span class="line">    <span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">appendChild</span>(el);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> el;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="源码解析"><a href="#源码解析" class="headerlink" title="源码解析"></a>源码解析</h2><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images/202111221420019.png" alt="紫升"></p><p>其中 avigation、lifecycles、applications 这三个文件夹即可，是整个源码的核心。</p><h3 id="核心方法分析"><a href="#核心方法分析" class="headerlink" title="核心方法分析"></a>核心方法分析</h3><p>绿底白字则是 single-spa 的三个核心方法 <a href="http://tny.im/T5UaE">registerApplication</a>、<a href="http://tny.im/tsub">reroute</a> 和 <a href="http://tny.im/9CvCH">start</a>，脑图描述了这三个方法具体做了什么：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images/202111221417558.png" alt="紫升"></p><h3 id="初始化加载流程"><a href="#初始化加载流程" class="headerlink" title="初始化加载流程"></a>初始化加载流程</h3><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images/202111221423159.png" alt="紫升"></p><h3 id="应用启动和挂载流程"><a href="#应用启动和挂载流程" class="headerlink" title="应用启动和挂载流程"></a>应用启动和挂载流程</h3><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images/202111221423521.png" alt="紫升"></p><h2 id="经验"><a href="#经验" class="headerlink" title="经验"></a>经验</h2><ol><li>加载操作一般都是异步的，比如加载插件、加载应用。所以原则上只要会使用 Promise，我们就能很快模仿一个自己的微前端架构出来。</li><li>本次分享没有涉及微前端的另个一个重要概念沙箱，有机会单独分享。</li><li>技术发展都是有迹可循的，搞清发展脉络对于理解应用一个技术也很有帮助。</li></ol><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://juejin.cn/post/6941402712890638367">微前端框架single-spa源码解析、项目实战、手写简版single-spa</a></li><li><a href="https://juejin.cn/post/6862661545592111111#heading-43">微前端框架 之 single-spa 从入门到精通</a></li><li><a href="https://zhuanlan.zhihu.com/p/107059106">single-spa 实现前端微服务</a></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/89f69cf72fa9/</id>
    <link href="https://blog.zisheng.pro/posts/89f69cf72fa9/"/>
    <published>2021-11-21T09:00:00.000Z</published>
    <summary>Single-spa 是一个将多个单页面应用聚合为一个整体应用的 JavaScript 微前端框架。 使用 single-spa 进行前端架构设计可以带来很多好处</summary>
    <title>single-spa 原理解析</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="VS Code" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/VS-Code/"/>
    <category term="vscode 插件" scheme="https://blog.zisheng.pro/tags/vscode-%E6%8F%92%E4%BB%B6/"/>
    <category term="vscode 插件开发" scheme="https://blog.zisheng.pro/tags/vscode-%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91/"/>
    <category term="vscode 插件发布" scheme="https://blog.zisheng.pro/tags/vscode-%E6%8F%92%E4%BB%B6%E5%8F%91%E5%B8%83/"/>
    <category term="vscode 发布插件" scheme="https://blog.zisheng.pro/tags/vscode-%E5%8F%91%E5%B8%83%E6%8F%92%E4%BB%B6/"/>
    <category term="vscode 插件市场" scheme="https://blog.zisheng.pro/tags/vscode-%E6%8F%92%E4%BB%B6%E5%B8%82%E5%9C%BA/"/>
    <category term="vsce" scheme="https://blog.zisheng.pro/tags/vsce/"/>
    <category term="Visual Studio Code" scheme="https://blog.zisheng.pro/tags/Visual-Studio-Code/"/>
    <category term="access token" scheme="https://blog.zisheng.pro/tags/access-token/"/>
    <category term="personal token" scheme="https://blog.zisheng.pro/tags/personal-token/"/>
    <content>
      <![CDATA[<h2 id="安装发布工具-vsce"><a href="#安装发布工具-vsce" class="headerlink" title="安装发布工具 vsce"></a>安装发布工具 vsce</h2><p><a href="https://github.com/Microsoft/vsce">vsce</a> 是一个用于将插件发布到<a href="https://marketplace.visualstudio.com/">vscode 插件市场</a>上的命令行工具。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ npm install vsce -g</span><br></pre></td></tr></table></figure><h2 id="注册-vscode-开发者信息"><a href="#注册-vscode-开发者信息" class="headerlink" title="注册 vscode 开发者信息"></a>注册 vscode 开发者信息</h2><h3 id="新建-Azure-DevPos-组织"><a href="#新建-Azure-DevPos-组织" class="headerlink" title="新建 Azure DevPos 组织"></a>新建 Azure DevPos 组织</h3><ol><li>登录 <a href="https://azure.microsoft.com/zh-cn/services/devops/">Azure DevOps</a>。</li><li>点击 <strong>New organization</strong>。</li></ol><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images/20210812201149.png" alt="vscode 新建 Azure DevPos 组织"></p><ol start="3"><li>确认信息，然后选择 <strong>Continue</strong>。</li></ol><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images/20210812201210.png" alt="vscode 新建 Azure DevPos 组织"></p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images/20210812201223.png" alt="vscode 新建 Azure DevPos 组织"></p><p>到这里，一个组织就创建成功了，之后随时登录到你的组织<a href="https://dev.azure.com/tuyaworks">https://dev.azure.com/tuyaworks</a>。</p><h3 id="获取-Personal-Access-Token"><a href="#获取-Personal-Access-Token" class="headerlink" title="获取 Personal Access Token"></a>获取 Personal Access Token</h3><p>下面的例子里，我们假设组织名为 vscode，从你的组织主页（例如：<a href="https://dev.azure.com/tuyaworks">https://dev.azure.com/tuyaworks</a> ） 进入 Personal access tokens 页面：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images/20210812201259.png" alt="vscode 获取 Personal Access Token"></p><p>点击 <strong>New Token</strong> 创建一个新的 Personal Access Token：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images/20210812202141.png" alt="vscode 获取 Personal Access Token"></p><p>给 Personal Access Token 添加描述，过期时间等等，你最好把过期时间设置为 1 年，这样你接下就能方便很多，选择 <strong>custom defined（用户自定义）</strong> 范围，然后点击 <strong>Show all scopes(显示全部)</strong> ，在这个列表中找到 Marketplace，并勾选 Acquire 和 Manage：</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images/20210812202337.png" alt="vscode 获取 Personal Access Token"></p><p>点击 <strong>Create</strong>，你就会看到新创建的 Personal Access Token 了，复制好，你接下来就会用到这个 token 来创建一个发行方了。</p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images/20210812202402.png" alt="vscode 获取 Personal Access Token"></p><h3 id="创建一个发行方"><a href="#创建一个发行方" class="headerlink" title="创建一个发行方"></a>创建一个发行方</h3><p><strong>发行方</strong>是 VS Code 市场有权发布插件的唯一标识，每个插件的 package.json 文件都包含着 publisher 字段。<br>现在我们已经有了 Personal Access Token，我们马上可以通过 <a href="https://aka.ms/vscode-create-publisher">vscode-create-publisher</a> 创建一个发行方。</p><h3 id="发行方登录"><a href="#发行方登录" class="headerlink" title="发行方登录"></a>发行方登录</h3><p>如果你已经有发行方账号了：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ vsce login (publisher name)</span><br></pre></td></tr></table></figure><p><code>vsce</code> 会记住这个 Personal Access Token，日后你需要再次使用的时候会自动带上。</p><p>你也可以用命令参数 <code>-p &lt;token&gt;</code> 直接登录然后立即发布插件：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ vsce publish -p &lt;token&gt;</span><br></pre></td></tr></table></figure><h2 id="vscode-插件版本维护"><a href="#vscode-插件版本维护" class="headerlink" title="vscode 插件版本维护"></a>vscode 插件版本维护</h2><h3 id="增量更新插件版本"><a href="#增量更新插件版本" class="headerlink" title="增量更新插件版本"></a>增量更新插件版本</h3><p>用 SemVer 语义标识符：major，minor，patch 增量更新插件版本号。</p><p>例如，你想把插件从 1.0.0 更新到 1.1.0，那么加上 minor：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ vsce publish minor</span><br></pre></td></tr></table></figure><p>插件 <em>package.json</em> 的 version 会先更新，然后才发布插件。</p><p>你也可以通过命令行指定版本号：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ vsce publish 2.0.1</span><br></pre></td></tr></table></figure><h3 id="下架插件"><a href="#下架插件" class="headerlink" title="下架插件"></a>下架插件</h3><p>通过指定插件 idpublisher.extension 下架插件：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ vsce unpublish (publisher name).(extension name)</span><br></pre></td></tr></table></figure><blockquote><p>当你下架插件的时候，市场会移除所有插件的历史统计数据，请在下架前再三考虑，最好还是更新插件吧。</p></blockquote><h3 id="VS-Code-版本兼容性"><a href="#VS-Code-版本兼容性" class="headerlink" title="VS Code 版本兼容性"></a>VS Code 版本兼容性</h3><p>当你制作插件的时候，你需要描述插件对 VS Code 的版本兼容性——修改 <em>package.json</em> 中的 engines.vscode：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;engines&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;vscode&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^1.8.0&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>1.8.0 表示你的插件只能兼容 1.8.0 版本的 VS Code，^1.8.0 则表示你的插件向上兼容，包括 1.8.1, 1.9.0 等等。</p><p>使用 <code>engines.vscode</code> 可以确保插件安装环境包含了插件依赖的 API。这个机制在稳定版和 Insider 版本都适用。</p><p>现在我们假设最新的稳定版 API 是 1.8.0，而 1.9.0 引入了新的 API，所以你可以用 1.9.0-insider 标识插件在 Insider 版中也可正常使用。 如果你想使用这些刚刚引入的 API，则将依赖版本设置为 ^1.9.0，你的插件则只能安装在 &gt;&#x3D;1.9.0 的 VS Code 上，也就意味着所有当前的 Insider 版本都可以用得上，而稳定版只有在更新到 1.9.0 才能使用你的插件。</p><h2 id="vscode-插件打包"><a href="#vscode-插件打包" class="headerlink" title="vscode 插件打包"></a>vscode 插件打包</h2><p>你也可能只是想打包一下插件，而不是发布到商店里。用下列命令将插件打包到 <code>.vsix</code> 文件中：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ vsce package</span><br></pre></td></tr></table></figure><p>这个命令会在当前目录生成一个 .vsix 文件，直接从 .vsix 安装插件是允许的，查看<a href="https://github.com/Microsoft/vscode-docs/blob/master/docs/editor/extension-gallery.md#install-from-a-vsix">从 VSIX 安装插件</a>了解更多内容。</p><h2 id="相关链接"><a href="#相关链接" class="headerlink" title="相关链接"></a>相关链接</h2><ul><li><a href="https://vscode-api-cn.js.org/">VS Code API 中文文档</a></li><li><a href="https://marketplace.visualstudio.com/">vscode 插件市场</a></li><li><a href="https://azure.microsoft.com/zh-cn/services/devops/">Azure Devops</a></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/4b349879ced6/</id>
    <link href="https://blog.zisheng.pro/posts/4b349879ced6/"/>
    <published>2021-08-12T12:46:38.000Z</published>
    <summary>VsCode 插件开发的最后一步也是最繁琐的就是插件发布，本文详细介绍了如何使用 vsce 将 vscode 插件发布到扩展市场。</summary>
    <title>2023 保姆级 VsCode 插件发布教程</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/tags/React-Native/"/>
    <category term="前端工程化" scheme="https://blog.zisheng.pro/tags/%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%8C%96/"/>
    <category term="掘金" scheme="https://blog.zisheng.pro/tags/%E6%8E%98%E9%87%91/"/>
    <category term="前端组件化" scheme="https://blog.zisheng.pro/tags/%E5%89%8D%E7%AB%AF%E7%BB%84%E4%BB%B6%E5%8C%96/"/>
    <category term="前端架构" scheme="https://blog.zisheng.pro/tags/%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84/"/>
    <content>
      <![CDATA[<blockquote><p>本文为掘金社区首发签约文章，未获授权禁止转载</p></blockquote><p>紫升有一个朋友小黑最近在面试时被问到如何设计一个前端组件库。没啥经验的小黑回答了业务提取封装成库以及基于 antd 结合业务二次封装。最后小黑被 HR 以灵力不够挂掉了。其实这个问题考察的并不是假大空的概念，而是有关开发者仓库管理、组件设计、单元测试、持续集成、协作管理等等能力。那么为了赋能小黑完美回答这个问题呢，我决定带领小黑一步一步建设一个 React Native 组件库。</p><p>这是一篇干货比较多的组件库搭建实战教程，不仅有通用的代码规范、提交规范、文档维护、单元测试、GitHub Action 配置的讲解，还涉及基于 lerna 的多包管理架构、React Native 图标库建设、React Native 组件库开发调试、按需加载原理及实现。工程化的思想是通用的，所以无论是你用的框架是什么，本文都值得一读。</p><blockquote><p>如果电脑前的掘友也对组件库开发感兴趣，不妨先给个点赞，再持续关注紫升和小黑的组件库开发之旅。PS：配合<a href="https://github.com/youngjuning/vant-react-native">仓库</a>和<a href="https://vant-react-native.js.org/">组件库文档</a>阅读本文效果更佳喲！</p></blockquote><h2 id="站在-Vant-Design-的肩膀上"><a href="#站在-Vant-Design-的肩膀上" class="headerlink" title="站在 Vant Design 的肩膀上"></a>站在 Vant Design 的肩膀上</h2><p>维护开发一个组件库无疑是需要投入很多时间和精力的，Flag 立了倒，倒了又立。可谓万事开头难，首先我们要有自知之明，在没有设计师和业余开发的情况下，我选择了给现有 UI Design 实现 React Native 版本的方式开启组件库开发之旅。在调研了 <a href="https://github.com/youzan/vant">vant</a>、<a href="https://github.com/NSFI/fishd-mobile/">fishd-mobile</a> 和 <a href="https://github.com/ant-design/ant-design-mobile">antd-mobile</a> 后我选择了 vant。这是几个仓库的现状对比：</p><table><thead><tr><th>组件库</th><th>团队</th><th>Github Star</th><th>Npm 周下载量</th><th>维护度</th></tr></thead><tbody><tr><td>vant</td><td>有赞</td><td>17.7K</td><td>27,789</td><td>维高度高，流行度也高</td></tr><tr><td>antd-mobile</td><td>Ant Design Team</td><td>8.9K</td><td>31,470</td><td>几乎不维护，据说蚂蚁内部也不用了</td></tr><tr><td>fishd-mobile</td><td>网易云商前端</td><td>29</td><td>22</td><td>看起来是个 KPI 项目无疑了</td></tr></tbody></table><p>确定了旅程的方向，就是给我们的组件库起一个合适的名字和口号，用前端工程师的方式表述就是 <code>package.json</code> 的 <code>name</code> 和 <code>description</code> 字段：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// package.json</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;vant-react-native&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Lightweight React Native UI Components inspired on Vant&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><blockquote><p>由于我们的组件库定位是 vant 的 RN 版，参照 lottie-react-native、styled-react-native、jpush-react-native 的命名方式我们将组件库命名为 vant-react-native，同时也是希望组件库完成时能获得 vant 官方的支持。</p></blockquote><h2 id="基于-Lerna-的多包管理架构"><a href="#基于-Lerna-的多包管理架构" class="headerlink" title="基于 Lerna 的多包管理架构"></a>基于 Lerna 的多包管理架构</h2><p>Lerna 是一个管理工具，用于管理包含多个软件包（package）的 JavaScript 项目。由 Lerna 管理的仓库我们一般称之为单体仓库（monorepo）。基于 Lerna 的多包管理架构的优点在于：</p><ul><li>组件级别解耦，独立版本控制，每个组件都有版本记录可追溯</li><li>组件单独发布，支持灰度、版本回滚以及平滑升降级</li><li>按需引用，用户安装具体某个组件包，无需配置即可实现按需加载的效果。</li><li>关注点分离，降低大型复杂度、组件之间依赖清晰且可控制</li><li>单一职责原则，降低开源基友的参与和贡献难度</li></ul><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">└── packages</span><br><span class="line">    ├── button <span class="comment"># @vant-react-native/button</span></span><br><span class="line">    └── icons <span class="comment"># @vant-react-native/icon</span></span><br></pre></td></tr></table></figure><h3 id="初始化-lerna-项目"><a href="#初始化-lerna-项目" class="headerlink" title="初始化 lerna 项目"></a>初始化 lerna 项目</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">mkdir</span> vant-react-native &amp;&amp; lerna init --independent</span><br></pre></td></tr></table></figure><h3 id="yarn-workspaces"><a href="#yarn-workspaces" class="headerlink" title="yarn workspaces"></a>yarn workspaces</h3><p>使用 <a href="https://yarnpkg.com/lang/zh-Hans/docs/workspaces/">yarn workspaces</a> 结合 Lerna <code>useWorkspaces</code> 可以实现 <a href="https://github.com/lerna/lerna/blob/main/doc/hoist.md">Lerna Hoisting</a>。这并不是多此一举，这可以让你在统一的地方（根目录）管理依赖，这即节省时间又节省空间。</p><p>配置 <code>lerna.json</code>:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  ...</span><br><span class="line">  <span class="attr">&quot;npmClient&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;useWorkspaces&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>托管给 yarn wrokspace 之后，lerna 的 <code>packages</code> 将会被顶级 <code>package.json</code> 的 <code>workspaces</code> 覆盖:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;private&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  ...</span><br><span class="line">  <span class="attr">&quot;workspaces&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;packages/*&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="lerna-publish-config"><a href="#lerna-publish-config" class="headerlink" title="lerna publish config"></a>lerna publish config</h3><p>如果你不想在所有 <code>package.json</code> 文件中单独明确设置你的注册表配置，例如使用私有注册表时，设置 <code>command.publish.registry</code> 很有用。配置 <code>ignoreChanges</code> 则是为了避免不必要的版本升级。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;ignoreChanges&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">  <span class="string">&quot;ignored-file&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="string">&quot;**/__tests__/**&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="string">&quot;**/*.md&quot;</span></span><br><span class="line"><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;command&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;publish&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;registry&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://registry.npmjs.org&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><blockquote><p>除此之外，如果你的包名是带 scope 的，需要在那个包的 <code>package.json</code> 中设置 <code>publishConfig.access</code> 为 <code>&quot;public&quot;</code>。</p></blockquote><h3 id="lerna-version-config"><a href="#lerna-version-config" class="headerlink" title="lerna version config"></a>lerna version config</h3><p>当配置 <code>conventionalCommits</code> 为 <code>true</code> 后，lerna 版本将使用 <a href="https://conventionalcommits.org/">Conventional Commits Specification</a> 来确定版本升级并 <a href="https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-cli">生成 CHANGELOG.md 文件</a>。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;command&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;conventionalCommits&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;chore(release): publish&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="规范化提交"><a href="#规范化提交" class="headerlink" title="规范化提交"></a>规范化提交</h2><p>规范化 <code>git commit</code> 对于提高 <code>git log</code> 可读性、可控的版本控制和 changelog 生成都有着重要的作用。紫升之前在 <a href="https://juejin.cn/post/6877462747631026190">一文搞定规范化Git Commit</a> 中详细讲述了 Conventional Commits 的概念以及 commitizen、cz-customizable、@commitlint&#x2F;cli、yorkie 和 commitlint-config-cz 等工具的配置。</p><p>由于配置繁琐，我在 <a href="https://github.com/youngjuning/cli#%E4%B8%80%E9%94%AE%E9%85%8D%E7%BD%AE-conventional-commit">@youngjuning&#x2F;cli</a> 中添加了 <code>init-commit</code> 命令一键配置 conventional commit。可以打开这个 <a href="https://github.com/youngjuning/vant-react-native/commit/d49f4203eacd37d3908306d309783ed5c11fb565">commit</a> 查看配置信息。</p><blockquote><p>注意：husky 高版本用法不向后兼容，我在这个 <a href="https://github.com/youngjuning/vant-react-native/commit/174c7fd883dd5dc7a0c4f5d8fcb25ed605680b12">commit</a> 中用尤大的 yorkie 代替了 husky。</p></blockquote><h2 id="代码规范化"><a href="#代码规范化" class="headerlink" title="代码规范化"></a>代码规范化</h2><p>代码规范化的重要性不言而喻，代码规范化涉及的工具有 editorconfig、eslint、prettier 等，在 <a href="https://juejin.cn/post/6854573217320075278">装它|再也不用操心ESLint配置</a> 一文中我介绍了如何一步一步建设属于自己的 eslint config 插件并产出了 <a href="https://github.com/youngjuning/eslint-config">@youngjuning&#x2F;eslint-config</a> 和 <a href="https://github.com/youngjuning/prettier-config">@youngjuning&#x2F;prettier-config</a>。</p><p>vant-react-native 暂时使用 @youngjuning&#x2F;eslint-config、@youngjuning&#x2F;prettier-config 约束项目代码规范。相关配置如下文。</p><h3 id="eslint"><a href="#eslint" class="headerlink" title="eslint"></a>eslint</h3><p>首先安装 react-native 所需的插件。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">yarn add -D eslint-plugin-react \</span><br><span class="line">  eslint-plugin-react-hooks \</span><br><span class="line">  eslint-plugin-jsx-a11y \</span><br><span class="line">  eslint-plugin-import \</span><br><span class="line">  eslint-plugin-react-native</span><br></pre></td></tr></table></figure><p>然后配置 <code>.eslintrc.js</code></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// .eslintrc.js</span></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">extends</span>: [<span class="string">&#x27;@youngjuning/eslint-config/react-native&#x27;</span>]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="prettier"><a href="#prettier" class="headerlink" title="prettier"></a>prettier</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// .prettierrc.js</span></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = <span class="built_in">require</span>(<span class="string">&#x27;@youngjuning/prettier-config&#x27;</span>);</span><br></pre></td></tr></table></figure><blockquote><p>@youngjuning&#x2F;eslint-config 计划也用 lerna 管理，产出 @youngjuning&#x2F;eslint-config-react、@youngjuning&#x2F;eslint-config-react-native、@youngjuning&#x2F;eslint-config-vue 让开发者无需过多配置开箱即用。</p></blockquote><h3 id="editorconfig"><a href="#editorconfig" class="headerlink" title="editorconfig"></a>editorconfig</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"># .editorconfig</span><br><span class="line"># EditorConfig is awesome: http://EditorConfig.org</span><br><span class="line"></span><br><span class="line"># top-most EditorConfig file</span><br><span class="line">root = true</span><br><span class="line"></span><br><span class="line"># Unix-style newlines with a newline ending every file</span><br><span class="line">[*]</span><br><span class="line">indent_style = space</span><br><span class="line">indent_size = 2</span><br><span class="line">end_of_line = lf</span><br><span class="line">charset = utf-8</span><br><span class="line">trim_trailing_whitespace = true</span><br><span class="line">insert_final_newline = true</span><br><span class="line"></span><br><span class="line">[*.md]</span><br><span class="line">trim_trailing_whitespace = false</span><br><span class="line"></span><br><span class="line">[*.gradle]</span><br><span class="line">indent_size = 4</span><br><span class="line"></span><br><span class="line">[BUCK]</span><br><span class="line">indent_size = 4</span><br></pre></td></tr></table></figure><h3 id="yorkie-lint-staged"><a href="#yorkie-lint-staged" class="headerlink" title="yorkie &amp; lint-staged"></a>yorkie &amp; lint-staged</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add -D yorkie lint-staged</span><br></pre></td></tr></table></figure><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;gitHooks&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;commit-msg&quot;</span><span class="punctuation">:</span> <span class="string">&quot;commitlint -e -V&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;pre-commit&quot;</span><span class="punctuation">:</span> <span class="string">&quot;lint-staged&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;lint-staged&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;**/*.&#123;js,jsx,ts,tsx&#125;&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;eslint --fix&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;git add .&quot;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="第一个组件从-Icon-开始"><a href="#第一个组件从-Icon-开始" class="headerlink" title="第一个组件从 Icon 开始"></a>第一个组件从 Icon 开始</h2><p>一个成熟的组件库都会拥有自己的一套 Icon，Icon 一般由设计师通过 Sketch 设计，然后导出 svg 文件。</p><p>ant-design-icons 的 svg 文件是 <a href="https://github.com/ant-design/ant-design-icons/tree/master/packages/icons-svg/svg/filled">保存在本地</a>，然后通过脚本生成 <a href="https://github.com/ant-design/ant-design-icons/tree/master/packages/icons-react/src/icons">react 组件</a>、<a href="https://github.com/ant-design/ant-design-icons/tree/master/packages/icons-vue">vue 组件</a> 和 <a href="https://github.com/ant-design/ant-design-icons/tree/master/packages/icons-react-native">icons-react-native</a> 等组件，由于支持的框架比较完备我们无需自己实现，RN 我们直接使用 <a href="https://github.com/ant-design/ant-design-icons/tree/master/packages/icons-react-native">icons-react-native</a>。</p><p>vant 以及 fishd-mobile 则是通过 Iconfont 维护 svg 文件，然后通过设置 <code>@font-face</code> 的方式实现 Icon 组件，如图所示：</p><p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9b3f6212f0124f4fab8b70dfeeb40c25~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p>有了 ttf 文件，我们可以像 @ant-design&#x2F;icons-react-native 一样基于 ttf 文件使用脚本生成 Icon 组件，但是使用 ttf 字体有一个弊端，就是每次更新图标，都要相应的更新 ttf 文件，然后再次打包发布 APP。而且 ttf 不支持多种色彩的图标，导致所有图标都是单色。如果你是借助 react-native-vector-icons，该库内置了 10 多套 ttf 文件，合起来有 2M 左右；你可能用不到它们，但是它们仍然会被打包进你的 APP 里，这也是我认为 react-native-elements 这个库外强中干的一大原因。</p><p>那么只有 Iconfont 链接我们如何实现 vant-icons 的 React Native 版本呢？这里紫升没有自己写脚本，而是使用了一款叫 react-native-iconfont-cli 的工具，<a href="https://github.com/fwh1990">fwh1990</a> 大佬针对以上痛点用纯 Javascript 实现 iconfont 到 React 组件的转换操作，不需要依赖 ttf 字体文件，不需要手动下载图标到本地。</p><h3 id="创建-lerna-子包"><a href="#创建-lerna-子包" class="headerlink" title="创建 lerna 子包"></a>创建 lerna 子包</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 创建主包，主包用来统一导出所有的组件</span></span><br><span class="line">$ lerna create vant-react-native -y</span><br><span class="line"><span class="comment"># 创建 icons 包，我们的第一个组件！</span></span><br><span class="line">$ lerna create @vant-react-native/icons -y</span><br></pre></td></tr></table></figure><p>我们的目录结构看起来是这样的：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">└── packages</span><br><span class="line">    ├── icons</span><br><span class="line">    │   ├── README.md</span><br><span class="line">    │   └── package.json</span><br><span class="line">    └── vant-react-native</span><br><span class="line">        ├── README.md</span><br><span class="line">        └── package.json</span><br></pre></td></tr></table></figure><h3 id="生成-icons"><a href="#生成-icons" class="headerlink" title="生成 icons"></a>生成 icons</h3><h4 id="安装插件"><a href="#安装插件" class="headerlink" title="安装插件"></a>安装插件</h4><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn workspace @vant-react-native/icons add -D react-native-svg react-native-iconfont-cli</span><br></pre></td></tr></table></figure><h4 id="生成配置文件"><a href="#生成配置文件" class="headerlink" title="生成配置文件"></a>生成配置文件</h4><p>我们在 <code>packages/icons</code> 目录下使用 <code>npx iconfont-init</code> 命令会生成 <code>iconfont.json</code> 文件，自定义后内容如下：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;symbol_url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://at.alicdn.com/t/font_2553510_7cds497uxwn.js&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;use_typescript&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;save_dir&quot;</span><span class="punctuation">:</span> <span class="string">&quot;./lib&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;trim_icon_prefix&quot;</span><span class="punctuation">:</span> <span class="string">&quot;van-icon&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;default_icon_size&quot;</span><span class="punctuation">:</span> <span class="number">18</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h4 id="生成-React-Native-标准组件"><a href="#生成-React-Native-标准组件" class="headerlink" title="生成 React Native 标准组件"></a>生成 React Native 标准组件</h4><p>执行 <code>npx iconfont-rn</code> 命令即可生成标准 React Native 组件。由于图标文件比较多，我们不将图标产物加入 git 管理。所以我们需要在 npm 发布前执行构建命令：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;build&quot;</span><span class="punctuation">:</span> <span class="string">&quot;npx iconfont-rn&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;prepublishOnly&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn build&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="配置-react-native-vant"><a href="#配置-react-native-vant" class="headerlink" title="配置 react-native-vant"></a>配置 react-native-vant</h3><p>我们前面提到 <code>packages/vant-react-native</code> 是主包的目录，我们需要将 <code>@vant-react-native/icons</code> 包添加到主包的依赖中并导出。</p><h4 id="添加依赖"><a href="#添加依赖" class="headerlink" title="添加依赖"></a>添加依赖</h4><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ lerna add @vant-react-native/icons --scope vant-react-native</span><br></pre></td></tr></table></figure><h4 id="导出-Icon-组件"><a href="#导出-Icon-组件" class="headerlink" title="导出 Icon 组件"></a>导出 Icon 组件</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// packages/vant-react-native/src/index.ts</span></span><br><span class="line"><span class="keyword">export</span> &#123; <span class="keyword">default</span> <span class="keyword">as</span> <span class="title class_">Icon</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@vant-react-native/icons&#x27;</span>;</span><br><span class="line"><span class="keyword">export</span> * <span class="keyword">from</span> <span class="string">&#x27;@vant-react-native/icons&#x27;</span>;</span><br></pre></td></tr></table></figure><h4 id="tsconfig-配置"><a href="#tsconfig-配置" class="headerlink" title="tsconfig 配置"></a>tsconfig 配置</h4><p>对与每个子包我们期望使用一样的配置，所以我们会先在整个项目的根目录新建 <a href="https://github.com/youngjuning/vant-react-native/blob/main/tsconfig.base.json">tsconfig. base.json</a>，在子包继承即可。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;extends&quot;</span><span class="punctuation">:</span> <span class="string">&quot;../../tsconfig.base.json&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;compilerOptions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;outDir&quot;</span><span class="punctuation">:</span> <span class="string">&quot;lib&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;include&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;src/**/*&quot;</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h4 id="配置发布脚本"><a href="#配置发布脚本" class="headerlink" title="配置发布脚本"></a>配置发布脚本</h4><p>和 <code>@vant-react-native/icons</code> 子包一样，我们需要添加 <code>build</code> 和 <code>prepublishOnly</code> 脚本：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;build&quot;</span><span class="punctuation">:</span> <span class="string">&quot;tsc&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;prepublishOnly&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn build&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="发布包"><a href="#发布包" class="headerlink" title="发布包"></a>发布包</h3><p>第一次发布的话，注意使用的是 <code>lerna publish 0.0.1</code>，因为 lerna 的发布命令没有第一次发布这个参数，所以需要显示指定初始版本。或者可以将初始版本设置为 <code>0.0.0</code> 然后执行 <code>lerna publish</code>。</p><blockquote><p>小技巧：如果发布后想查看包内容，可以通过 <a href="https://www.jsdelivr.com/">jsdelivr</a> 查看。比如刚发布的 <a href="https://cdn.jsdelivr.net/npm/vant-react-native/">vant-react-native</a> 和 <a href="https://cdn.jsdelivr.net/npm/@vant-react-native/icons/">@vant-react-native&#x2F;icons</a></p></blockquote><h2 id="开发调试"><a href="#开发调试" class="headerlink" title="开发调试"></a>开发调试</h2><p>一个完善且体验良好的调试流程不仅能够满足在开发阶段验证组件是否符合预期，还可以降低开源社区基友的参与难度。React Native 组件库的调试和其他技术栈流程大体没有区别，只不过因为 <a href="https://github.com/facebook/metro/issues/1">Metro 不支持软连接</a> 以及 vant-react-native 是基于 lerna 的单体仓库项目，我们的配置会有不同。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f3856a691a73409c8533fa9dd1966357~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><h3 id="初始化-React-Native-App"><a href="#初始化-React-Native-App" class="headerlink" title="初始化 React Native App"></a>初始化 React Native App</h3><p>由于是 React Native 项目，我们需要初始化一个 React Native 项目。首先找一个地方使用 <code>react-native init vantapp --template react-native-template-typescript</code> 创建一个新的 React Native App。然后将生成的 App 与我们的主项目合并。合并后的项目结构如下：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">├── App.tsx</span><br><span class="line">├── __tests__</span><br><span class="line">│   └── App-test.tsx</span><br><span class="line">├── android</span><br><span class="line">│   ├── app</span><br><span class="line">│   ├── build.gradle</span><br><span class="line">│   ├── gradle</span><br><span class="line">│   ├── gradle.properties</span><br><span class="line">│   ├── gradlew</span><br><span class="line">│   ├── gradlew.bat</span><br><span class="line">│   └── settings.gradle</span><br><span class="line">├── app.json</span><br><span class="line">├── babel.config.js</span><br><span class="line">├── commitlint.config.js</span><br><span class="line">├── index.js</span><br><span class="line">├── ios</span><br><span class="line">│   ├── Podfile</span><br><span class="line">│   ├── Podfile.lock</span><br><span class="line">│   ├── Pods</span><br><span class="line">│   ├── vantapp</span><br><span class="line">│   ├── vantapp.xcodeproj</span><br><span class="line">│   ├── vantapp.xcworkspace</span><br><span class="line">│   └── vantappTests</span><br><span class="line">├── lerna.json</span><br><span class="line">├── metro.config.js</span><br><span class="line">├── package.json</span><br><span class="line">├── packages</span><br><span class="line">│   ├── icons</span><br><span class="line">│   └── vant-react-native</span><br><span class="line">├── tsconfig.base.json</span><br><span class="line">├── tsconfig.json</span><br><span class="line">└── yarn.lock</span><br></pre></td></tr></table></figure><p>主要冲突的是 Prettier、eslint 等工具的配置，合并没那么难。在运行项目之前，我们一般需要编译项目。我们可以借助 <code>lerna run build</code> 命令批量运行子包里的 <code>build</code> npm script。</p><blockquote><p>注意📢：由于子包之间有依赖关系，不要使用 <code>--parallel</code> 参数并行执行打包脚本。</p></blockquote><p>现在我们编写一个九宫格 Demo 验证一下：</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// App.tsx</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">React</span>, &#123; <span class="title class_">Component</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">View</span>, <span class="title class_">Text</span>, <span class="title class_">SafeAreaView</span>, <span class="title class_">ScrollView</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;react-native&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Icon</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;vant-react-native&#x27;</span>;</span><br><span class="line"><span class="comment">// 我们也可以只安装 @vant-react-native/icons 包</span></span><br><span class="line"><span class="comment">// import &#123; VanIconAdd &#125; from &#x27;@vant-react-native/icons&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">IconNameType</span> = <span class="title class_">React</span>.<span class="property">ComponentProps</span>&lt;<span class="keyword">typeof</span> <span class="title class_">Icon</span>&gt;[<span class="string">&#x27;name&#x27;</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">class</span> <span class="title class_">App</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Component</span> &#123;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">SafeAreaView</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">ScrollView</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Text</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">textAlign:</span> &#x27;<span class="attr">center</span>&#x27;, <span class="attr">paddingVertical:</span> <span class="attr">20</span>, <span class="attr">fontSize:</span> <span class="attr">25</span>, <span class="attr">color:</span> &#x27;#<span class="attr">007fff</span>&#x27; &#125;&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          &gt;</span></span></span><br><span class="line"><span class="language-xml">            vant-react-native</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;/<span class="name">Text</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">View</span> <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">flexWrap:</span> &#x27;<span class="attr">wrap</span>&#x27;, <span class="attr">flexDirection:</span> &#x27;<span class="attr">row</span>&#x27; &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">            &#123;data.map((item, index) =&gt; &#123;</span></span><br><span class="line"><span class="language-xml">              const lastLineLength = data.length % 4 || 4;</span></span><br><span class="line"><span class="language-xml">              return (</span></span><br><span class="line"><span class="language-xml">                <span class="tag">&lt;<span class="name">View</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">                  <span class="attr">key</span>=<span class="string">&#123;item&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">                  <span class="attr">style</span>=<span class="string">&#123;&#123;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">                    <span class="attr">width:</span> &#x27;<span class="attr">25</span>%&#x27;,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">                    <span class="attr">marginBottom:</span> <span class="attr">index</span> &lt; <span class="attr">data.length</span> <span class="attr">-</span> <span class="attr">lastLineLength</span> ? <span class="attr">40</span> <span class="attr">:</span> <span class="attr">0</span>,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">                    <span class="attr">alignItems:</span> &#x27;<span class="attr">center</span>&#x27;,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">                  &#125;&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">                &gt;</span></span></span><br><span class="line"><span class="language-xml">                  <span class="tag">&lt;<span class="name">Icon</span> <span class="attr">name</span>=<span class="string">&#123;item&#125;</span> <span class="attr">size</span>=<span class="string">&#123;40&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">                  <span class="tag">&lt;<span class="name">Text</span> <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">color:</span> &#x27;#<span class="attr">646566</span>&#x27;, <span class="attr">marginTop:</span> <span class="attr">10</span> &#125;&#125;&gt;</span>&#123;item&#125;<span class="tag">&lt;/<span class="name">Text</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">                <span class="tag">&lt;/<span class="name">View</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">              );</span></span><br><span class="line"><span class="language-xml">            &#125;)&#125;</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;/<span class="name">View</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">ScrollView</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">SafeAreaView</span>&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="attr">data</span>: <span class="title class_">IconNameType</span>[] = [<span class="string">&#x27;location-o&#x27;</span>, <span class="string">&#x27;like-o&#x27;</span>, <span class="string">&#x27;star-o&#x27;</span>, <span class="string">&#x27;phone-o&#x27;</span>, <span class="string">&#x27;setting-o&#x27;</span>, <span class="string">&#x27;fire-o&#x27;</span>, <span class="string">&#x27;coupon-o&#x27;</span>, <span class="string">&#x27;cart-o&#x27;</span>, <span class="string">&#x27;shopping-cart-o&#x27;</span>, <span class="string">&#x27;cart-circle-o&#x27;</span>, <span class="string">&#x27;friends-o&#x27;</span>, <span class="string">&#x27;comment-o&#x27;</span>, <span class="string">&#x27;gem-o&#x27;</span>, <span class="string">&#x27;gift-o&#x27;</span>, <span class="string">&#x27;point-gift-o&#x27;</span>, <span class="string">&#x27;send-gift-o&#x27;</span>, <span class="string">&#x27;service-o&#x27;</span>, <span class="string">&#x27;bag-o&#x27;</span>, <span class="string">&#x27;todo-list-o&#x27;</span>, <span class="string">&#x27;balance-list-o&#x27;</span>, <span class="string">&#x27;close&#x27;</span>, <span class="string">&#x27;clock-o&#x27;</span>, <span class="string">&#x27;question-o&#x27;</span>, <span class="string">&#x27;passed&#x27;</span>];</span><br></pre></td></tr></table></figure><p>然后执行 <code>yarn ios</code> 查看实际效果（之后我们就可以执行 <code>yarn start --reset-cache</code> 快速开始调试）：</p><p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3d2828ea83804d3bb53a0563eaf429bd~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p>上面的示例代码中我们可以看到我们直接使用了 <code>import { Icon } from &#39;vant-react-native&#39;;</code> 而不是相对路径引用 packages 下的模块。可是我们的项目并没与安装这个依赖，编译器是怎么找到的呢？这里也没有什么银弹，这是因为 lerna 会把子包软链接到 node_modules 中，我们可以使用 <code>ls -al</code> 发现看到包的实际指向：</p><p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7857ecd166a74331befe791dc677257b~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p>我们也可以在类型提示中看到实际指向的是 packages 下的文件：</p><p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ba82260550904406b4224734761c2f5e~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><blockquote><p>注意📢：<a href="https://github.com/facebook/metro/issues/1">Metro 不支持符号链接</a> 指的是软连接的目录不在项目根目录下，这里我们软连接指向的位置还在根目录下，所以可以正确工作✅。这个特性保证了调试与生产开发的一致性和便利性。</p></blockquote><h3 id="实时编译"><a href="#实时编译" class="headerlink" title="实时编译"></a>实时编译</h3><p>现在我们的调试流程是：</p><ol><li>修改代码</li><li>执行 <code>lerna run build</code> 编译每个子包</li><li>执行 <code>yarn ios</code> 调试项目</li><li>修改代码</li><li>执行 <code>lerna run build</code> 重新编译</li><li>执行 <code>yarn start --reset-cache</code> 运行项目</li><li>循环 4、5、6。</li></ol><p>尽管 React Native 有 Fast Refresh 功能，但是由于我们的代码是需要编译的，所以我们需要重复编译运行的动作。</p><p>任何重复的工作都可以用脚本代替。首先我们需要给每个子包添加实时编译的 script，像 rollup、babel、webpack、typescript 都有参数可以实现实时编译：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;dev&quot;</span><span class="punctuation">:</span> <span class="string">&quot;tsc -w&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;build&quot;</span><span class="punctuation">:</span> <span class="string">&quot;tsc&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;prepublishOnly&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn build&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>而我们的 @vant-react-native&#x2F;icons 包使用的 <code>npx iconfont</code> 没有实时编译选项，经过调研，我引入了 <a href="https://www.npmjs.com/package/onchange">onchange</a> 这个库可以基于 glob 模式监听文件改动后执行一个命令：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;dev&quot;</span><span class="punctuation">:</span> <span class="string">&quot;onchange -i &#x27;iconfont.json&#x27; -- yarn build&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>然后我们需要使用 <code>lerna run dev --parallel</code> 批量执行实时编译脚本，这里加 <code>--parallel</code> 是因为子包如果是实时编译，进程会卡住。为了补救，我们不得不预先编译 <code>@vant-react-native/icons</code> 包，然后因为同样的原因我引入了 <code>npm-run-all</code> 来并行执行 <code>lerna run dev</code> 和 <code>react-native start</code>，完整脚本如下：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;predev&quot;</span><span class="punctuation">:</span> <span class="string">&quot;lerna run build --scope @vant-react-native/icons&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;dev&quot;</span><span class="punctuation">:</span> <span class="string">&quot;lerna run dev --parallel&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;start&quot;</span><span class="punctuation">:</span> <span class="string">&quot;react-native start&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;debug&quot;</span><span class="punctuation">:</span> <span class="string">&quot;run-p dev start&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="按需加载"><a href="#按需加载" class="headerlink" title="按需加载"></a>按需加载</h2><blockquote><p>小黑：“紫升哥哥，我之前为了使用 react-native-elements 的其中几个组件而引入了整个组件库。因为这个组件库依赖了 react-native-vector-icons 导致 bundle 包变大。如果我就是想用整套 vant-react-native，如何解决这个问题呢？”</p></blockquote><p>众所周知，React Native 的打包工具 Metro <a href="https://github.com/facebook/metro/issues/227#issuecomment-583358386">不支持 tree-shaking</a>。解决这个问题的方式其实很简单，机智的你可能知道配合 <a href="https://www.npmjs.com/package/babel-plugin-import">babel-plugin-import</a> 是可以实现按需加载的需求的。但由于我们是多包管理架构，需要针对多包的架构设计一个方案。</p><h3 id="react-naitve-bundle-包"><a href="#react-naitve-bundle-包" class="headerlink" title="react-naitve bundle 包"></a>react-naitve bundle 包</h3><p>为了比对优化前后包大小，我们需要使用 <code>react-native bundle</code> 命令看一下纯 JS 包的大小，我们来简单看下这个命令：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">react-native bundle --platform ios --entry-file index.js --bundle-output ./bundle/ios/index.ios.jsbundle --assets-dest ./bundle/ios --dev <span class="literal">false</span> --reset-cache</span><br></pre></td></tr></table></figure><ul><li><code>--entry</code>：入口 js 文件</li><li><code>--bundle-output</code>：生成的 bundle 文件路径</li><li><code>--platform</code>：平台</li><li><code>--assets-dest</code>：图片资源的输出目录</li><li><code>--dev</code>：是否为开发版本，打正式版的安装包时我们将其赋值为 false</li><li><code>--reset-cache</code>：重置缓存，避免打包使用旧的缓存</li></ul><h3 id="按需加载原理"><a href="#按需加载原理" class="headerlink" title="按需加载原理"></a>按需加载原理</h3><p>前面我们提到 <code>packages/vant-react-native</code> 只有一个文件 <code>src/index.ts</code> 用来导出所有子包，现在我们添加一个新的包 Button，看上去就是这样：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> &#123; <span class="keyword">default</span> <span class="keyword">as</span> <span class="title class_">Icon</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@vant-react-native/icons&#x27;</span>;</span><br><span class="line"><span class="keyword">export</span> * <span class="keyword">from</span> <span class="string">&#x27;@vant-react-native/icons&#x27;</span>;</span><br><span class="line"><span class="keyword">export</span> &#123; <span class="keyword">default</span> <span class="keyword">as</span> <span class="title class_">Button</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@vant-react-native/icons&#x27;</span>;</span><br></pre></td></tr></table></figure><p>这种导出方式，用户只能通过 <code>import Button from &#39;@vant-react-native/button&#39;;</code> 或 <code>import Button from &#39;vant-react-native/lib/button&#39;;</code> 的方式手动实现按需加载，这不仅不方便开发者使用，从打包产物来说也增加了很多字节。那么问题来了，怎么样的组织形式才能满足按需加载呢？答案就在 babel-plugin-import 插件的文档中：</p><p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3d8bd23d28b0413b8b3da8119d32dd35~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p>从图中我们看出 babel-plugin-import 插件是在编译阶段将引用指向了模块所在文件夹。用户使用时安装插件并做如下配置就完成了按需加载。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;plugins&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">  <span class="punctuation">[</span><span class="string">&quot;import&quot;</span><span class="punctuation">,</span> <span class="punctuation">&#123;</span> libraryName<span class="punctuation">:</span> <span class="string">&quot;antd&quot;</span><span class="punctuation">,</span> style<span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span> <span class="punctuation">&#125;</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">]</span></span><br></pre></td></tr></table></figure><p>依然没有银弹，插件做的工作只是代替了你的右手。知道了原理我们就可以按照文档要求的格式重新组织我们的 vant-react-native 包：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">├── CHANGELOG.md</span><br><span class="line">├── lib                    <span class="comment"># 上传到 NPM 的编译产物</span></span><br><span class="line">│   ├── button             <span class="comment"># 符合 babel-plugin-import 的默认配置要求</span></span><br><span class="line">│   │   ├── index.d.ts</span><br><span class="line">│   │   └── index.js</span><br><span class="line">│   ├── icon</span><br><span class="line">│   │   ├── index.d.ts</span><br><span class="line">│   │   └── index.js</span><br><span class="line">│   ├── index.d.ts</span><br><span class="line">│   └── index.js          <span class="comment"># export * from &#x27;./button&#x27;;</span></span><br><span class="line">├── package.json</span><br><span class="line">├── src                   <span class="comment"># 源码目录</span></span><br><span class="line">│   ├── button</span><br><span class="line">│   │   └── index.ts</span><br><span class="line">│   ├── icon</span><br><span class="line">│   │   └── index.ts</span><br><span class="line">│   └── index.ts</span><br><span class="line">└── tsconfig.json         <span class="comment"># 编译配置，将 ts 文件编译到 lib 文件夹下</span></span><br></pre></td></tr></table></figure><p><strong>vant-react-native&#x2F;src&#x2F;button&#x2F;index.ts：</strong></p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">Button</span> <span class="keyword">from</span> <span class="string">&#x27;@vant-react-native/button&#x27;</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Button</span>;</span><br><span class="line"><span class="keyword">export</span> &#123; <span class="title class_">Button</span> &#125;;</span><br></pre></td></tr></table></figure><p><strong>vant-react-native&#x2F;src&#x2F;icon&#x2F;index.ts：</strong></p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">Icon</span> <span class="keyword">from</span> <span class="string">&#x27;@vant-react-native/icons&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Icon</span>;</span><br><span class="line"><span class="keyword">export</span> &#123; <span class="title class_">Icon</span> &#125;;</span><br><span class="line"><span class="keyword">export</span> * <span class="keyword">from</span> <span class="string">&#x27;@vant-react-native/icons&#x27;</span>;</span><br></pre></td></tr></table></figure><p><strong>vant-react-native&#x2F;src&#x2F;index.ts：</strong></p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> * <span class="keyword">from</span> <span class="string">&#x27;./icon&#x27;</span>;</span><br><span class="line"><span class="keyword">export</span> * <span class="keyword">from</span> <span class="string">&#x27;./button&#x27;</span>;</span><br></pre></td></tr></table></figure><p>然后项目中修改 babel.config.js：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">presets</span>: [<span class="string">&#x27;module:metro-react-native-babel-preset&#x27;</span>],</span><br><span class="line">  <span class="attr">plugins</span>: [</span><br><span class="line">    [<span class="string">&quot;import&quot;</span>, &#123;<span class="attr">libraryName</span>: <span class="string">&#x27;vant-react-native&#x27;</span>&#125;]</span><br><span class="line">  ],</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="编写-Babel-插件？"><a href="#编写-Babel-插件？" class="headerlink" title="编写 Babel 插件？"></a>编写 Babel 插件？</h3><p>虽然通过修改主包的导出方式可以完成需求，但是却极大地增加了项目本身的复杂度。前面我们已经知道 babel-plugin-import 的原理是转换引用路径。那么我们是不是可以通过插件动态把 <code>import {Button} from &#39;vant-react-native&#39;</code> 转成 <code>import Button from &#39;@vant-react-native/button&#39;</code> 呢？答案是肯定的，下面是我基于 babel-plugin-import 的 <code>customName</code> 配置编写了一套配置并封装在 babel-plugin-import-vant 包中：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> camelCase <span class="keyword">from</span> <span class="string">&#x27;camelcase&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> (): <span class="built_in">any</span>[] =&gt; [</span><br><span class="line">  [</span><br><span class="line">    <span class="string">&#x27;import&#x27;</span>,</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">libraryName</span>: <span class="string">&#x27;vant-react-native&#x27;</span>,</span><br><span class="line">      <span class="attr">customName</span>: <span class="function">(<span class="params">name: <span class="built_in">string</span></span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (name === <span class="string">&#x27;icon&#x27;</span>) &#123;</span><br><span class="line">          <span class="keyword">return</span> <span class="string">&#x27;@vant-react-native/icons&#x27;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (name.<span class="title function_">match</span>(<span class="regexp">/^van-icon-/</span>)) &#123;</span><br><span class="line">          <span class="keyword">return</span> <span class="string">`@vant-react-native/icons/lib/<span class="subst">$&#123;camelCase(name, &#123; pascalCase: <span class="literal">true</span> &#125;)&#125;</span>`</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">`@vant-react-native/<span class="subst">$&#123;name&#125;</span>`</span>;</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="string">&#x27;vant-react-native&#x27;</span>,</span><br><span class="line">  ],</span><br><span class="line">  [</span><br><span class="line">    <span class="string">&#x27;import&#x27;</span>,</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">libraryName</span>: <span class="string">&#x27;@vant-react-native/icons&#x27;</span>,</span><br><span class="line">      <span class="attr">customName</span>: <span class="function">(<span class="params">name: <span class="built_in">string</span></span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">`@vant-react-native/icons/lib/<span class="subst">$&#123;camelCase(name, &#123; pascalCase: <span class="literal">true</span> &#125;)&#125;</span>`</span>;</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="string">&#x27;@vant-react-native/icons&#x27;</span>,</span><br><span class="line">  ],</span><br><span class="line">];</span><br></pre></td></tr></table></figure><p>在项目的 <code>babel.config.js</code> 配置中添加 <code>plugins: [...require(&#39;babel-plugin-import-vant&#39;).default()]</code> 即可实现按需加载。</p><p>还有可以优化的地方吗？机智的你可能又发现我只是通过函数导出了一个配置而已，并不是真正的插件，所以未来我会定制一个 vant-react-native 自己的按需加载 babel 插件。</p><blockquote><p><code>name.match(/^van-icon-/)</code> 这个判断条件是因为 <code>@vant-react-native/icons</code> 包除了包含一个默认导出的 Icon 组件，还导出了很多单个图标组件，为了进一步减小打包体积，我们对这个子包也进行了按需加载处理。</p><p><strong>我们已经知道按需加载的原理是没有中间商赚差价直接和卖家谈</strong>，所以后面我们遇见类似的需求通过转换返回卖家地址即可。不需要破坏性地改项目结构。</p></blockquote><h3 id="成果展示"><a href="#成果展示" class="headerlink" title="成果展示"></a>成果展示</h3><table><thead><tr><th>初始包大小</th><th>未配置按需加载（引入 Button）</th><th>按需加载（引入 Button）</th><th>按需加载（引入 Icon）</th><th>按需加载（引入 VanIconAdd）</th></tr></thead><tbody><tr><td>723KB</td><td>1.8M</td><td>725KB</td><td>1.8M</td><td>1.22M</td></tr></tbody></table><p>之所以 Icon 包会大，是因为 react-native-svg 这个库大，所以不建议直接使用 Icon 组件，而是使用 VanIconAdd、VanIconEye 这种单独的图标组件，少了 593KB 还是挺香的。</p><h2 id="组件库文档"><a href="#组件库文档" class="headerlink" title="组件库文档"></a>组件库文档</h2><p>组件库文档比较重要的是有可以交互的 Demo 演示，我是 Dumi 的资深用户，借助 dumi-theme-mobile 和 <a href="https://github.com/youngjuning/umi-plugin-react-native">umi-plugin-react-native</a> 我们可以很好地满足 React Native 组件库文档的搭建。</p><h3 id="集成-Dumi-到项目中"><a href="#集成-Dumi-到项目中" class="headerlink" title="集成 Dumi 到项目中"></a>集成 Dumi 到项目中</h3><p><strong>安装依赖：</strong></p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add dumi dumi-theme-mobile umi-plugin-react-native -D</span><br></pre></td></tr></table></figure><p><strong>配置文件：</strong></p><p>在项目根目录添加 <code>.umirc.ts</code></p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; defineConfig, <span class="title class_">IConfig</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;dumi&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title function_">defineConfig</span>(&#123;</span><br><span class="line">  <span class="attr">title</span>: <span class="string">&#x27;vant-react-native&#x27;</span>,</span><br><span class="line">  <span class="attr">mode</span>: <span class="string">&#x27;site&#x27;</span>,</span><br><span class="line">  <span class="attr">logo</span>: <span class="string">&#x27;https://img01.yzcdn.cn/vant/logo.png&#x27;</span>,</span><br><span class="line">  <span class="attr">favicon</span>: <span class="string">&#x27;https://img01.yzcdn.cn/vant/logo.png&#x27;</span>,</span><br><span class="line">  <span class="attr">resolve</span>: &#123;</span><br><span class="line">    <span class="attr">includes</span>: [<span class="string">&#x27;docs&#x27;</span>, <span class="string">&#x27;packages/button&#x27;</span>, <span class="string">&#x27;packages/icons&#x27;</span>],</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="comment">// more config: https://d.umijs.org/config</span></span><br><span class="line">&#125; <span class="keyword">as</span> <span class="title class_">IConfig</span>);</span><br></pre></td></tr></table></figure><p>值得一提的是，Dumi 是支持 Lerna 仓库的，它默认会以 <code>packages/[包名]/src</code> 为基础路径搜寻所有子包的 Markdown 文档并生成路由。通过 <code>resolve.includes</code> 可以配置 dumi 嗅探的文档目录，dumi 会尝试在配置的目录中递归寻找 markdown 文件。</p><p><strong>添加 NPM 脚本：</strong></p><blockquote><p>注意📢：由于实际依赖的是 packages 下的包，我们必须先编译所有的包，否则部署的时候会报 <code>This dependency was not found:</code> 的错误。</p></blockquote><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;start:dumi&quot;</span><span class="punctuation">:</span> <span class="string">&quot;dumi dev&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;build:dumi&quot;</span><span class="punctuation">:</span> <span class="string">&quot;lerna run build &amp;&amp; dumi build&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><strong>忽略文件（.gitignore）：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># umi</span><br><span class="line">.umi</span><br><span class="line">.umi-production</span><br><span class="line">.env.local</span><br><span class="line">dist/</span><br></pre></td></tr></table></figure><h3 id="部署到-GitHub-Pages"><a href="#部署到-GitHub-Pages" class="headerlink" title="部署到 GitHub Pages"></a>部署到 GitHub Pages</h3><p>在根目录新建 <code>.github/workflows/gh-pages</code>：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">github</span> <span class="string">pages</span></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line">  <span class="attr">push:</span></span><br><span class="line">    <span class="attr">branches:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">main</span> <span class="comment"># default branch</span></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">deploy:</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-18.04</span></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">actions/checkout@v2</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">run:</span> <span class="string">yarn</span> <span class="string">install</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">run:</span> <span class="string">yarn</span> <span class="string">build:dumi</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Deploy</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">peaceiris/actions-gh-pages@v3</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">github_token:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.GITHUB_TOKEN</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">publish_dir:</span> <span class="string">./dist</span></span><br></pre></td></tr></table></figure><h3 id="预览"><a href="#预览" class="headerlink" title="预览"></a>预览</h3><p>现在我们可以访问 <a href="https://blog.zisheng.pro/vant-react-native/">https://blog.zisheng.pro/vant-react-native/</a> 查看效果了：</p><p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9faf0889b6d64238922bb02c51cf02ff~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a11e9ce535c54f8bb038599533f0ef9a~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8bed86245d8f485894b0c5e3d9767e9c~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><h3 id="配置优化"><a href="#配置优化" class="headerlink" title="配置优化"></a>配置优化</h3><p>现在基于 dumi 的文档站点只是初始化，很多配置（.umirc.ts）可以优化，比如：</p><ol><li>基于 jsdelivr 配置 CDN 加速</li></ol><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> isProd = process.<span class="property">env</span>.<span class="property">NODE_ENV</span> === <span class="string">&#x27;production&#x27;</span>;</span><br><span class="line">...</span><br><span class="line"><span class="attr">publicPath</span>: isProd ? <span class="string">&#x27;https://cdn.jsdelivr.net/gh/youngjuning/vant-react-native@gh-pages/&#x27;</span>: <span class="string">&#x27;/&#x27;</span>,</span><br></pre></td></tr></table></figure><ol start="2"><li>增量发布和避免浏览器加载缓存</li></ol><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">hash</span>: <span class="literal">true</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="3"><li><a href="https://www.umeng.com/web">友盟网站统计</a></li></ol><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">scripts</span>: [<span class="string">&#x27;https://s9.cnzz.com/z_stat.php?id=1280093214&amp;web_id=1280093214&#x27;</span>],</span><br><span class="line">  <span class="attr">styles</span>: [<span class="string">&#x27;a[title=站长统计] &#123; display: none; &#125;&#x27;</span>],</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="4"><li>配置 <code>exportStatic: {}</code> 将所有路由输出为 HTML 目录结构，以免刷新页面时 404。</li></ol><h3 id="Pull-Request-预发预览"><a href="#Pull-Request-预发预览" class="headerlink" title="Pull Request 预发预览"></a>Pull Request 预发预览</h3><p>考虑到后期社区会贡献代码和文档。在 pr 合进主分支之前，我们需要预览文档或组件。满足这一需求的是一个叫 surge.sh 的静态托管服务，surge 支持在命令行通过简单的命令免费发布 HTML、CSS 和 JS 文件到 web。</p><h4 id="申请-Surge-Token"><a href="#申请-Surge-Token" class="headerlink" title="申请 Surge Token"></a>申请 Surge Token</h4><p><strong>安装 surge cli：</strong></p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install --global surge</span><br></pre></td></tr></table></figure><p><strong>注册 surge 账号：</strong></p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">suerge login</span><br></pre></td></tr></table></figure><p><strong>获取 token：</strong></p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">suerge token</span><br></pre></td></tr></table></figure><h4 id="配置-CI"><a href="#配置-CI" class="headerlink" title="配置 CI"></a>配置 CI</h4><p>由于 GitHub 的安全问题，surge-preview Action 插件无法使用，我们参考 dumi 官方的配置自定义了 CI，首先我们拷贝下图中的三个文件到项目中。</p><p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9e3d3e61ac1c4019ac393b2d1b2c5719~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p>然后修改 <code>preview-build.yml</code> 中的 <code>build step</code>：</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="deletion">- NODE_OPTIONS=&#x27;--max-old-space-size=4096&#x27; yarn build</span></span><br><span class="line"><span class="addition">+ NODE_OPTIONS=&#x27;--max-old-space-size=4096&#x27; PREVIEW_PR=true yarn build:dumi</span></span><br></pre></td></tr></table></figure><p>添加环境变量 <code>PREVIEW_PR=true</code> 是为了让 dumi 打包时识别出不是生产环境打包，<code>.umirc.ts</code> 需要相应修改为：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> isProd =</span><br><span class="line">  process.<span class="property">env</span>.<span class="property">NODE_ENV</span> === <span class="string">&#x27;production&#x27;</span> &amp;&amp; process.<span class="property">env</span>.<span class="property">PREVIEW_PR</span> !== <span class="string">&quot;true&quot;</span>;</span><br><span class="line">...</span><br><span class="line"><span class="attr">publicPath</span>: isProd ? <span class="string">&#x27;https://cdn.jsdelivr.net/gh/youngjuning/vant-react-native@gh-pages/&#x27;</span>: <span class="string">&#x27;/&#x27;</span>,</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>再然后，修改 <code>preview-deploy.yml</code> 文件中的部署域名 <code>dumi-preview</code> 为 <code>vant-react-native-preview</code>。</p><p>最后我们把前面获取的 Surge Token 添加到仓库的 Secrets 即可。</p><h4 id="成果展示-1"><a href="#成果展示-1" class="headerlink" title="成果展示"></a>成果展示</h4><p><strong>正在部署 PR 预览状态：</strong></p><p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/193ac9e16cca408383ca7ae2ec503613~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p><strong>部署成功状态：</strong></p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1659de97aafd4f87a93993c35ef93195~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p>访问 <a href="https://vant-react-native-preview-pr-1.surge.sh/">https://vant-react-native-preview-pr-1.surge.sh/</a> 即可验证文档的正确性✅。</p><h2 id="单元测试"><a href="#单元测试" class="headerlink" title="单元测试"></a>单元测试</h2><p>我在 <a href="https://juejin.cn/post/6937983077209604127">使用 Jest 和 Enzyme 进行 React Native 单元测试|技术点评</a> 一文中曾提交单元测试和文档一样，是保障程序最小单元质量的重要一环。诚然一个成熟的组件库是必然有单元测试的身影。本章就不展开讲单元测试了，主要讲 vant-react-native 是如何配置单元测试的。</p><h3 id="安装依赖"><a href="#安装依赖" class="headerlink" title="安装依赖"></a>安装依赖</h3><p>jest、babel-jest、@types&#x2F;jest 这些依赖都已经安装了，我们需要安装的是 enzyme 这个基于 jest 的单元测试框架。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add enzyme jest-enzyme enzyme-adapter-react-16 enzyme-to-json @types/enzyme react-native-mock-render -DW</span><br></pre></td></tr></table></figure><blockquote><p>Enzyme 是用于 React 的 JavaScript 测试实用程序，可以更轻松地测试 React 组件的输出。您还可以根据给定的输出进行操作，遍历并以某种方式模拟运行时。</p></blockquote><h3 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h3><p><strong>jest.config.js：</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">preset</span>: <span class="string">&#x27;react-native&#x27;</span>,</span><br><span class="line">  <span class="attr">verbose</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="attr">collectCoverage</span>: <span class="literal">true</span>, <span class="comment">// 生成测试覆盖率报告</span></span><br><span class="line">  <span class="attr">moduleNameMapper</span>: &#123;</span><br><span class="line">    <span class="comment">// for https://github.com/facebook/jest/issues/919</span></span><br><span class="line">    <span class="string">&#x27;^image![a-zA-Z0-9$_-]+$&#x27;</span>: <span class="string">&#x27;GlobalImageStub&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;^[@./a-zA-Z0-9$_-]+\\.(png|gif)$&#x27;</span>: <span class="string">&#x27;RelativeImageStub&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">setupFilesAfterEnv</span>: [<span class="string">&#x27;&lt;rootDir&gt;/jest.setup.js&#x27;</span>], <span class="comment">// 使用 Jest 运行安装文件以配置 Enzyme 和适配器（如下文jest.setup.js中所示），之前是setupTestFrameworkScriptFile，也可以使用setupFiles</span></span><br><span class="line">  <span class="attr">snapshotSerializers</span>: [<span class="string">&#x27;enzyme-to-json/serializer&#x27;</span>], <span class="comment">// 推荐使用序列化程序使用 enzyme-to-json，它的安装和使用非常简单，并允许您编写简洁的快照测试。</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>jest.setup.js：</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">&#x27;react-native&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Enzyme</span> <span class="keyword">from</span> <span class="string">&#x27;enzyme&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Adapter</span> <span class="keyword">from</span> <span class="string">&#x27;enzyme-adapter-react-16&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Enzyme</span>.<span class="title function_">configure</span>(&#123; <span class="attr">adapter</span>: <span class="keyword">new</span> <span class="title class_">Adapter</span>() &#125;);</span><br></pre></td></tr></table></figure><p><strong>一个简单的示例：</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// packages/button/__test__/index.tsx</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; shallow &#125; <span class="keyword">from</span> <span class="string">&#x27;enzyme&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Button</span> <span class="keyword">from</span> <span class="string">&#x27;../src/index&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">setup</span>(<span class="params">props = &#123;&#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> wrapper = <span class="title function_">shallow</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Button</span> /&gt;</span></span>);</span><br><span class="line">  <span class="keyword">const</span> instance = wrapper.<span class="title function_">instance</span>();</span><br><span class="line">  <span class="keyword">return</span> &#123; wrapper, instance &#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">describe</span>(<span class="string">&#x27;Button Component&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;renders correctly&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; wrapper &#125; = <span class="title function_">setup</span>();</span><br><span class="line">    <span class="title function_">expect</span>(wrapper).<span class="title function_">toMatchSnapshot</span>();</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>执行 <code>jest</code> 命令后可以查看覆盖率如下：</p><p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/358737fa32f549f4a9b06a9bcf8903d4~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><h2 id="写给勇士"><a href="#写给勇士" class="headerlink" title="写给勇士"></a>写给勇士</h2><p>能写长文的不算勇士，能坚持看到这里的才是勇士。紫升在此感谢您的阅读。然而组件库工程化这只是一个起点，如果本文反响好，组件库具体组件的设计实现、完整的 React Native 单元测试教程等等紫升会在后续的文章中展开讲。</p><h2 id="推荐的-UI-库"><a href="#推荐的-UI-库" class="headerlink" title="推荐的 UI 库"></a>推荐的 UI 库</h2><p>当然了，vant-react-native 并不是你唯一的选择，下面的几个 UI 库都是很优秀的项目。在实现 vant-react-native 时我也多少借鉴了前人优秀的设计。</p><ul><li><a href="https://github.com/ant-design/ant-design-mobile-rn">antd-mobile-rn</a></li><li><a href="https://github.com/react-native-elements/react-native-elements">react-native-elements</a></li><li><a href="https://github.com/akveo/react-native-ui-kitten">react-native-ui-kitten</a></li><li><a href="https://github.com/wix/react-native-ui-lib">react-native-ui-lib</a></li><li><a href="https://zarm.design/">Zarm</a></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/eae5334982a6/</id>
    <link href="https://blog.zisheng.pro/posts/eae5334982a6/"/>
    <published>2021-07-11T17:01:01.000Z</published>
    <summary>这是一篇干货比较多的组件库搭建实战教程，不仅有通用的代码规范、提交规范、文档维护、单元测试、GitHub Action 配置的讲解，还涉及基于 lerna 的多包管理架构、React Native 图标库建设、React Native 组件库开发调试、按需加载原理及实现。</summary>
    <title>每个前端都值得拥有自己的组件库，就像每个夏天都拥有西瓜🍉</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="掘金翻译计划" scheme="https://blog.zisheng.pro/categories/%E6%8E%98%E9%87%91%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="JavaScript" scheme="https://blog.zisheng.pro/tags/JavaScript/"/>
    <category term="掘金翻译计划" scheme="https://blog.zisheng.pro/tags/%E6%8E%98%E9%87%91%E7%BF%BB%E8%AF%91%E8%AE%A1%E5%88%92/"/>
    <category term="TC39" scheme="https://blog.zisheng.pro/tags/TC39/"/>
    <content>
      <![CDATA[<blockquote><ul><li>原文地址：<a href="https://medium.com/@jorydotcom/revised-patterns-for-participation-in-standards-committees-dae82d93954e">Revised Patterns for Participation in Standards Committees</a></li><li>原文作者：<a href="https://medium.com/@jorydotcom">Jory Burson</a></li><li>译文出自：<a href="https://github.com/xitu/gold-miner">掘金翻译计划</a></li><li>本文永久链接：<a href="https://github.com/xitu/gold-miner/blob/master/article/ECMA-TC39/Revised-Patterns-for-Participation-in-Standards-Committees.md">https://github.com/xitu/gold-miner/blob/master/article/ECMA-TC39/Revised-Patterns-for-Participation-in-Standards-Committees.md</a></li><li>译者：<a href="https://github.com/youngjuning">紫升</a></li><li>校对者：<a href="https://github.com/KimYangOfCat">KimYangOfCat</a>，<a href="https://github.com/qq1120637483">qq1120637483</a></li></ul></blockquote><p><img src="https://miro.medium.com/max/1020/1*EnBeLnKvRHmiHvbivS4jzg.jpeg" alt="紫升"></p><p><small><em>图片来自民族学局提交给史密森学会秘书的年度报告（1881）</em></small></p><p>我最近重新阅读了 <a href="https://twitter.com/awbjs">艾伦·维尔福斯·布洛克</a> 的论文 <a href="http://pl.csie.ntut.edu.tw/asianplop2016/proceedings/A14.pdf">编程语言的标准化和参与模式</a>，同时为即将到来的讲座做准备。艾伦在编程语言标准开发、研究和文档方面有20多年的经验，这对 JavaScript 社区来说是非常有价值且不能被低估的。这篇论文为那些刚开始从事标准制定工作的人提供了学术性的指导方针和模式；为了使其更具有可操作性，并为我们的社区提供现代化的语言，我在下面更新了他的指导方针，以及一些改善大家参与标准委员会体验的有用建议。</p><p>技术标准委员会的性质决定了用户、实施者和供应商都在挤在同一个房间里，他们的需求或利益往往是相互冲突的。<a href="https://twitter.com/slightlylate">亚历克斯·罗素</a> 在他的 <a href="https://infrequently.org/2018/06/effective-standards-work-part-1-the-lay-of-the-land/">有效的标准工作</a> 系列文章中对网络驱动的标准组织中的这些紧张关系做了很好的说明。重要的是我们要明白，最终大家都在努力做正确的事——挑战在于就什么是正确的事达成共识，并以一种包容和尊重的方式去执行。这些指导方针是关于鼓励社会活动，促进专业（和个人回报）的标准制定过程。</p><h1 id="首先寻求理解…"><a href="#首先寻求理解…" class="headerlink" title="首先寻求理解…"></a>首先寻求理解…</h1><p><strong>做一个积极的倾听者</strong>：艾伦的指导意见是，只有当你确定你有有价值的东西需要说的时候才可以发言。我完全同意，对于一个新人来说，在第一次会议上应该专注于倾听和观察。然而，我认为有必要对这一准则进行修订，如果你有不明白的地方，绝对可以提出问题并得到解释（当然是在适当的时候）。为了提出问题，你必须真正倾听别人说的话——无论是技术上的还是背景上的，这也是快速融入小组的好方法。</p><p><strong>做好研究工作</strong>：这不仅适用于你可能有兴趣提出的具体建议和想法，而且也适用于整个团体的组织、个人和历史。但有时候知易行难，反馈或电子邮件中的文档很难再找到，或者对某件事情有真正了解的人可能已经离开了。Ecma TC39（以及大多数 W3C 工作组）已经通过 GitHub 将大量的背景资料放在网上，并且正在努力为社区呈现更多的组织历史。如果你偶然发现某个领域的历史不为人知，这可能是一个很好的机会，你可以通过填补空白做出有意义的贡献。</p><p><strong>认识你的团队</strong>：艾伦建议了解其他玩家，但我更愿意把它看作是认识和了解你的新团队的其他人。所有的技术都是其社会环境的产物，所以你越是了解这个社会环境并能对其做出积极的贡献，就越能帮助你的事业。如果你不认识小组中的其他人，请发送电子邮件介绍自己并分享你的想法。邀请某个组员吃顿烧烤或喝杯星巴克。和你的新伙伴一起参加或组织会后活动。了解他们来自哪里，哪些问题对他们来说是最重要的。当面建立这些联系更容易——我强烈建议，如果条件允许，你应该每年都参加一次或多次会议。</p><h1 id="…然后被理解"><a href="#…然后被理解" class="headerlink" title="…然后被理解"></a>…然后被理解</h1><p><strong>明确你的目标</strong>：如果你想实现一个新的功能，请准备好你要解决的明确的问题和任何相关的用例。如果你的解决方案的形式足够好，也要记得为它写测试。请记住，根据语言在其生命周期中的位置，实际问题要比理论问题更有说服力。</p><p>正如艾伦所指出的，“语言标准的制定不是一种绿地开发活动……语言标准委员会的存在是为了解决语言现状所产生的问题”。因此，如果你的目标是以某种方式发展一种语言——比如说，这样它就可以在区块链上实现——你就必须要有耐心。反之，如果你已经达到了你的目标，就可以继续做其他事情了。要有目的性。</p><blockquote><p>译者注：绿地项目在软件开发领域，一项绿地工程可能是在全新的环境内开发一个新系统，不需要考虑与其他系统，尤其是与老旧系统（Legacy system）的集成问题。这里作者指的是语言标准的制定是有历史包袱的。</p></blockquote><p><strong>对变化持开放的态度</strong>：标准工作尽管其名声在外，但这并不是关于竞争性的争论（我喜欢说，TC39 不是关于“竞争性的 JavaScripting”）。小组中的任何两个人都可能以不同的方式解决同一个问题，但是并不会自动抉择哪个人是正确的。在制定让你的功能得到实施的策略时，艾伦建议寻找盟友、选择你的战斗并有一个备用方案。与他人合作交流想法，可以提升你对问题和语言的理解，并帮助你发展对其他组织关心的问题的敏感性（从而可能影响你的提案的成功机会）。这也有助于你避免在特定背景下不重要的争论和辩论。另一个重要的策略是：对自己的错误持开放态度，并愿意说出来。毕竟，大多数想法和设计都不好，而我们最终接受的大多数想法和设计在开始时也是不好的。<sup>1</sup>营造一个可以安全地提出、讨论、推进或撤回想法的环境，对于健康的技术对话至关重要——这需要所有各方都愿意改变他们的想法。</p><p><strong>成为贡献者</strong>：艾伦和亚历克斯都认为，从历史上看，团体越老，新人和新想法就越难进入该团体。幸运的是，在这些团体中，有一个明确的向新的想法和工作方式开放的文化转变。</p><blockquote><p>有很多方法可以开始为标准工作做出贡献，而不需要写技术提案。事实上，这往往是最重要也是最缺乏服务的领域!</p></blockquote><p>你如何为一个标准工作做出贡献，最终应该是你的优势、目标和团体的需求的中一个因素。这里有一些方法可以让你成为一个伟大的贡献者，提高你的可信度，而不需要写新的提案。</p><ul><li>会议笔记记录志愿者。</li><li>组织或共同组织社会活动。</li><li>帮助策划会议。</li><li>为委员会的其他工作做出贡献，如编写文件。</li><li>阅读提案并提供关键的反馈或使用案例。</li><li>帮助编辑或支持其他提案，或为功能和提案编写测试。</li><li>帮助识别现有技术和被忽略的声音——是否有可以借鉴的例子，或者可以咨询的人，以帮助推动一些事情的发展？</li></ul><p><strong>力求达成共识</strong>：大多数网络标准委员会使用共识模式进行决策，如何衡量共识，各小组会有所不同，但一般意味着小组的大多数<sup>2</sup>支持该决定，其余人愿意接受该决定。分歧和辩论是可以的，也是需要的，但必须是专业和尊重的。在某些时候，作为代表，你必须决定你是否应该积极反对一项决定，但这应该是一种罕见的情况。并非每项提案都会被纳入最终规范——事实上，大多数都不会，这是件好事。</p><p>无论你是第一次参与标准制定并希望找到自己的立足点，还是你是现有的参与者并希望提高你目前工作的成功率，上述准则都适用。任何组织、委员会或运营联合开源软件项目的团队都可以直接从这些指南中受益，或者进行修改，使其更适合你的团队。我们的目标是快乐地制定标准!</p><ul><li><sup>1</sup>： 亚历克斯·罗素的 <a href="https://infrequently.org/2018/06/effective-standards-work-part-2-threading-the-needle/">穿针引线</a></li><li><sup>2</sup>：大多数的定义因组织而异。它通常需要三分之二或更多的投票来通过一个问题。</li></ul><blockquote><p>如果发现译文存在错误或其他需要改进的地方，欢迎到 <a href="https://github.com/xitu/gold-miner">掘金翻译计划</a> 对译文进行修改并 PR，也可获得相应奖励积分。文章开头的 <strong>本文永久链接</strong> 即为本文在 GitHub 上的 MarkDown 链接。</p></blockquote><hr><blockquote><p><a href="https://github.com/xitu/gold-miner">掘金翻译计划</a> 是一个翻译优质互联网技术文章的社区，文章来源为 <a href="https://juejin.im/">掘金</a> 上的英文分享文章。内容覆盖 <a href="https://github.com/xitu/gold-miner#android">Android</a>、<a href="https://github.com/xitu/gold-miner#ios">iOS</a>、<a href="https://github.com/xitu/gold-miner#%E5%89%8D%E7%AB%AF">前端</a>、<a href="https://github.com/xitu/gold-miner#%E5%90%8E%E7%AB%AF">后端</a>、<a href="https://github.com/xitu/gold-miner#%E5%8C%BA%E5%9D%97%E9%93%BE">区块链</a>、<a href="https://github.com/xitu/gold-miner#%E4%BA%A7%E5%93%81">产品</a>、<a href="https://github.com/xitu/gold-miner#%E8%AE%BE%E8%AE%A1">设计</a>、<a href="https://github.com/xitu/gold-miner#%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD">人工智能</a>等领域，想要查看更多优质译文请持续关注 <a href="https://github.com/xitu/gold-miner">掘金翻译计划</a>、<a href="http://weibo.com/juejinfanyi">官方微博</a>、<a href="https://zhuanlan.zhihu.com/juejinfanyi">知乎专栏</a>。</p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/b64419077480/</id>
    <link href="https://blog.zisheng.pro/posts/b64419077480/"/>
    <published>2021-06-08T08:35:10.000Z</published>
    <summary>这篇论文为那些刚开始从事标准制定工作的人提供了学术性的指导方针和模式；为了使其更具有可操作性，并为我们的社区提供现代化的语言，我在下面更新了他的指导方针，以及一些改善大家参与标准委员会体验的有用建议。</summary>
    <title>修订后的参与 TC39 标准委员会的模式</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="漫谈" scheme="https://blog.zisheng.pro/categories/%E6%BC%AB%E8%B0%88/"/>
    <category term="掘金" scheme="https://blog.zisheng.pro/tags/%E6%8E%98%E9%87%91/"/>
    <category term="写作" scheme="https://blog.zisheng.pro/tags/%E5%86%99%E4%BD%9C/"/>
    <category term="个人成长" scheme="https://blog.zisheng.pro/tags/%E4%B8%AA%E4%BA%BA%E6%88%90%E9%95%BF/"/>
    <category term="个人博客" scheme="https://blog.zisheng.pro/tags/%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/"/>
    <content>
      <![CDATA[<p>小时候长大想做的职业，我的小作文写的是作家。但是很可惜被迫选择了理科。然后专业更是选择了计算机。这一切看似和写作一点关系都没有。实际上是，大学的时候经理了手撸博客、用 wordpress 搭博客、用 hexo 搭静态博客的历程。慢慢地将自己的业务兴趣和专业联系在了一起，假装自己没有背弃理想。</p><p>第一次在社区发文并获得影响是为了推广 wx-promise-pro：</p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2109e0efc60e4ccda679b599b0298235~tplv-k3u1fbpfcp-zoom-1.image" style="zoom: 67%;" /><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e34344c7ef6847c2a3597ac75df1220e~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4718dc1b6e574a9e9e92f3c8e91037b6~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><h3 id="大道至简"><a href="#大道至简" class="headerlink" title="大道至简"></a>大道至简</h3><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a95ad345f27a4a4b8fb446670ad006e3~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><h3 id="巧用-issues"><a href="#巧用-issues" class="headerlink" title="巧用 issues"></a>巧用 issues</h3><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0b26b3a1f44b4e50a6e3ac9b53d39606~tplv-k3u1fbpfcp-zoom-1.image" style="zoom: 33%;" /><h2 id="如何坚持写作"><a href="#如何坚持写作" class="headerlink" title="如何坚持写作"></a>如何坚持写作</h2><h3 id="输入决定产出"><a href="#输入决定产出" class="headerlink" title="输入决定产出"></a>输入决定产出</h3><p>长期的输出是伴随着长期的输入的，一般有两个方向可以选择，一是横向地学习并记录 📝 学习过程，二是纵向对自己擅长的领域进行深挖，最终形成系列。</p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/952e21273503469ea3e72fd65389a204~tplv-k3u1fbpfcp-zoom-1.image" style="zoom:50%;" /><h3 id="参与活动"><a href="#参与活动" class="headerlink" title="参与活动"></a>参与活动</h3><p>其实各个内容平台都有推广活动，而且对入门和大佬都有相应的活动可以参与，我便是在参与了几次掘金的活动之后获得了了流量扶持以及结识平台运营。</p><p>一年的积累下来，集齐了两套完整的周边，以及各种单品。并在今年三月、四月获得了月榜的证书。</p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8251ea3c43f74027a3c215c3f321db3f~tplv-k3u1fbpfcp-zoom-1.image" style="zoom: 50%;" /><h2 id="收益"><a href="#收益" class="headerlink" title="收益"></a>收益</h2><h3 id="影响力："><a href="#影响力：" class="headerlink" title="影响力："></a>影响力：</h3><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8c7dd5af95d74bba8381bef755a77527~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><h3 id="与行业大佬交流的机会："><a href="#与行业大佬交流的机会：" class="headerlink" title="与行业大佬交流的机会："></a>与行业大佬交流的机会：</h3><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8907918504174d9094119d0f707886a4~tplv-k3u1fbpfcp-watermark.image" style="zoom: 50%;" /><ul><li><a href="https://juejin.cn/post/6946764691725254669">闲鱼宗心大佬聊跨端职业发展</a></li></ul><h3 id="认识一帮前端大佬："><a href="#认识一帮前端大佬：" class="headerlink" title="认识一帮前端大佬："></a>认识一帮前端大佬：</h3><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a78af0e6d7554e04ae35f5e3c5b1d8db~tplv-k3u1fbpfcp-zoom-1.image" style="zoom:50%;" /><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1f95c125c85e438ba8654d1d9b947228~tplv-k3u1fbpfcp-zoom-1.image" style="zoom: 20%;" /><ul><li><a href="https://blog.zisheng.pro/friends">紫升友人帐</a></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/5d19fd0218f8/</id>
    <link href="https://blog.zisheng.pro/posts/5d19fd0218f8/"/>
    <published>2021-06-04T08:31:11.000Z</published>
    <summary>
      <![CDATA[<p>小时候长大想做的职业，我的小作文写的是作家。但是很可惜被迫选择了理科。然后专业更是选择了计算机。这一切看似和写作一点关系都没有。实际上是，大学的时候经理了手撸博客、用 wordpress 搭博客、用 hexo 搭静态博客的历程。慢慢地将自己的业务兴趣和专业联系在了一起，假装]]>
    </summary>
    <title>少壮不努力，老大写博客</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React-Native/"/>
    <category term="Flutter" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/Flutter/"/>
    <category term="Flutter" scheme="https://blog.zisheng.pro/tags/Flutter/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/tags/React-Native/"/>
    <category term="跨端" scheme="https://blog.zisheng.pro/tags/%E8%B7%A8%E7%AB%AF/"/>
    <category term="跨平台" scheme="https://blog.zisheng.pro/tags/%E8%B7%A8%E5%B9%B3%E5%8F%B0/"/>
    <content>
      <![CDATA[<blockquote><p>原文链接：<a href="https://www.thedroidsonroids.com/blog/flutter-vs-react-native-what-to-choose-in-2021">https://www.thedroidsonroids.com/blog/flutter-vs-react-native-what-to-choose-in-2021</a><br>原作者：the droids on roids<br>校对：<a href="https://juejin.cn/user/606586150596360">Alex</a></p></blockquote><h2 id="译者序"><a href="#译者序" class="headerlink" title="译者序"></a>译者序</h2><p>从 Flutter 诞生之日起，React Native 就一直被拿来与之作比较，从谷歌超过 700 万的相关搜索结果中可以一瞥这场跨端王位之争的激烈程度。本文不像我翻译的标题一样在捧一踩一，而是较客观地对两者进行评价并给出实际需求中如何抉择。</p><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>你想更迅速地开发一款移动应用吗？你想通过跨平台技术减少开发成本吗？你还在为不知道选择哪一种技术焦虑吗？</p><p>本文正是解决你的焦虑的，我们比较了跨端方案的佼佼者：Flutter 和 React Native，并从 App 所有者的角度出发帮助你判断哪一款框架更适合你的 App 的需求。</p><h2 id="为什么我们写这篇文章"><a href="#为什么我们写这篇文章" class="headerlink" title="为什么我们写这篇文章"></a>为什么我们写这篇文章</h2><p>跨平台解决方案越来越受欢迎。Flutter 和 React Native 是两个行业领先的参与者，但是在 <a href="https://www.thedroidsonroids.com/about-us">Droids On Roids</a>，在为合作伙伴构建商业应用时，我们会根据具体需求来选择最合适的方案：可能是原生、Flutter 或者 React Native。</p><p>尽管如此，企业主总是希望知道几个关键问题的答案:</p><ul><li>哪一个框架最适合我的项目？</li><li>哪一种方案可以让我的应用以最快的速度面市？</li><li>能保证我的 App 稳定且有很好地用户体验吗？</li><li>如果 <a href="https://www.thedroidsonroids.com/services/product-design-ux-and-ui-for-mobile-and-web-app">完美设计</a> 是我的第一原则我该原则哪一种解决方案？</li></ul><p><strong>我们写这篇文章是为了总结与合作伙伴分享的答案，以便你也可以从中受益。</strong></p><p>我们希望这篇文章能帮助你在为你的 App 选择跨端方案时做出明智且合理的决策。</p><h3 id="作者介绍"><a href="#作者介绍" class="headerlink" title="作者介绍"></a>作者介绍</h3><p>深度对比这两种技术，需要对 Flutter 和 React Native 都有一定水平和经验的大佬才能胜任。</p><p>因此，我们要求两名 Droids On Roids 开发人员来执行此任务：</p><ul><li>Bartosz – Web 和 React Native 开发者</li><li>Damian – iOS 和 Flutter 开发者 (<strong><a href="https://github.com/DroidsOnRoids/flutter-roadmap">Roadmap for Flutter Developers in 2021</a></strong> 的作者—欢迎 PR)</li></ul><p>这两位大佬日常工作便是使用 Flutter 和 React Native 来为客户构建商业 App。因此，他们的分享不仅包括理论，还包括多年的开发经验。</p><p>我们还针对社区中的热门文章验证了我们的理解，并参考了这些资源来解答问题。 你可以在本文的末尾找到完整的参考文献列表。</p><h2 id="Flutter-vs-React-Native-概览"><a href="#Flutter-vs-React-Native-概览" class="headerlink" title="Flutter vs React Native: 概览"></a>Flutter vs React Native: 概览</h2><table><thead><tr><th align="left"></th><th align="left">Flutter</th><th>React Native</th></tr></thead><tbody><tr><td align="left">简介</td><td align="left">Flutter 是一个由谷歌开发的开源移动应用软件开发工具包，用于为 Android、iOS、Windows、Mac、Linux、Google Fuchsia 开发应用。</td><td>React Native 基于 React.js，目的是让开发者可以利用 JavaScript 和 React.js 的声明式编程模式开发出在多平台上运作的程式</td></tr><tr><td align="left">正式发布</td><td align="left">2018 年 12 月，Google I&#x2F;O 大会</td><td>2015 年 3 月，F8 大会</td></tr><tr><td align="left">厂商</td><td align="left">谷歌</td><td>脸书</td></tr><tr><td align="left">是否免费开源</td><td align="left">是</td><td>是</td></tr><tr><td align="left">编程语言</td><td align="left">Dart</td><td>JavaScript</td></tr><tr><td align="left">人气</td><td align="left"><a href="https://github.com/flutter/flutter">Github</a> 11.5 万 Star (2021 年 3 月)</td><td><a href="https://github.com/facebook/react-native">Github</a> 9.3 万 Star (2021 年 3 月)</td></tr><tr><td align="left">热加载（Hot Reload）</td><td align="left">是</td><td>是</td></tr><tr><td align="left">原生性能</td><td align="left">优秀</td><td>优秀</td></tr><tr><td align="left">UI</td><td align="left">Flutter 应用程序在最新的操作系统上看起来与旧版本一样好。由于它们只有一个代码库，因此这些应用在 <a href="https://www.thedroidsonroids.com/services/ios-mobile-app-development">iOS</a> 和 <a href="https%EF%BC%9A//www.thedroidsonroids">Android</a> 上的外观和行为类似，但是由于 Material Design 和 Cupertino widgets 的帮助，他们还可以模仿平台本身的设计。这是怎么做到的呢？Flutter 包含两组符合特定设计语言的 Widgets：Material Design Widgets 实现了同名 Google 的设计语言；Cupertino widgets 模拟了苹果公司 iOS 的设计。这意味着你的 Flutter 应用在每个平台上的外观和行为都将自然地模仿它们的原生组件。</td><td>应用组件看起来和原生组件一样（比如，一个 Button 在 iOS 设备上看起来和 iOS 原生 Button 一样，Android 设备上也是如此）。React Native 在后台使用了原生组件这一事实印证了这一点，在任何操作系统 UI 更新之后，你的应用程序组件也将相应地立即升级。也就是说，这可能会破坏应用的用户界面，但这种情况很少发生。如果你希望你的应用在各个平台上看起来几乎相同，在较旧版本的操作系统下也相同（正如 Flutter 实现的那样），你需要使用第三方组件库（比如 <a href="https://reactnativepaper.com/">react-native-paper</a>）。它们将使你能够使用 Material Design 组件代替原生组件。</td></tr><tr><td align="left">一码多端</td><td align="left">最近 Flutter 2 正式版发布了，作为 Flutter 的重大升级，使用 Flutter 2 开发者可以用相同的代码，把使用 Flutter 开发的应用发布到五个操作系统：<code>iOS</code>，<code>Android</code>，<code>Windows</code>，<code>macOS</code> 和 <code>Linux</code>； 以及运行到 <code>Chrome</code> 、 <code>Firefox</code>， <code>Safari</code> 或 <code>Edge</code>等浏览器的 Web 版本上，Flutter 甚至还可以嵌入到车机、电视和智能家电中。</td><td>iOS、Android — 但是有第三方库可以让你使用一套代码构建 iOS、Android、web 和 Windows10 应用。你还可以在移动端、桌面端和 Web 应用中提取共享代码到单独的存储库； 将其视为一个单独的项目； 然后以与另一个依赖项相同的方式注入它。这使开发人员可以专注于为特定平台编写代码，而不必考虑与另一个依赖项的兼容性。</td></tr><tr><td align="left">知名案例</td><td align="left">阿里巴巴的闲鱼、Hamilton Musical 的 Hamilton App、Google Ads App</td><td>Instagram, Facebook, Facebook Ads, Skype, Tesla</td></tr><tr><td align="left">开发周期</td><td align="left">通常比原生开发要快得多。</td><td>可能与 Flutter 的开发速度一样快。但是 React Native 使用桥接和原生组件，因此可能需要对每个平台进行单独的优化-基于 widget 的 Flutter 则没有这个问题。 它可能会使使用 React Native 进行 App 开发周期更长</td></tr><tr><td align="left">竞争优势</td><td align="left">得益于丰富的 widgets 让 Flutter 开发的界面很漂亮；快速增长的社区和知名度；Flutter 团队的精心提供的出色文档（这使得使用 Flutter 进行开发变得容易）;改进 Flutter for Web，为一码多端的跨平台开发提供了潜力；难以缩短开发周期</td><td>稳定性（已面市 5 年以上）；许多使用 React Native 的成功案例以及著名的市场参与者；成熟、庞大的社区；易于学习的技术；大量的教程和库，使你可以快速轻松地进行开发；可以轻松地在 Web 应用程序和桌面应用程序之间重用代码。</td></tr><tr><td align="left">什么时候不是好的选择</td><td align="left">如果你的应用程序需要支持 3D Touch（目前，Flutter 不支持 3D，但在 Flutter 团队的长期路线图中有此功能）应用程序的设计是特定于平台的；你的应用需要与操作系统进行多次交互或需要稀有的鲜为人知的原生库；你需要一个简约的 UI，但需要大量使用手机硬件（例如，播放音乐或仅拍照的应用程序）；你想要创建一个即时应用程序（小型应用程序），那么最好选择原生应用程序开发。有关这些情况的更多信息，请点击 <a href="https://www.thedroidsonroids.com/blog/flutter-in-mobile-app-development-pros-and-cons-for-app-owners#notgoodchoice">此处&gt;&gt;</a></td><td>如果你的应用程序需要在后台处理不太常见的任务或超特定任务（例如计算）；你需要通过蓝牙进行自定义通信（使用 React Native 实施可能会很棘手）； 如果你想要同时开发 iOS 和 Android 应用并且掌握 JavaScript 那么你可以选择 React Native，但是如果你只想开发 Android 应用，则最好让原生团队去构建。如果符合以上的则更适合选择原生应用</td></tr></tbody></table><h2 id="介绍-Flutter-和-React-Native"><a href="#介绍-Flutter-和-React-Native" class="headerlink" title="介绍 Flutter 和 React Native"></a>介绍 Flutter 和 React Native</h2><p>首先，让我们介绍 Flutter 和 React Native 的基本（但必不可少的）细节。</p><h3 id="Flutter-是什么？"><a href="#Flutter-是什么？" class="headerlink" title="Flutter 是什么？"></a>Flutter 是什么？</h3><p>Flutter 是一个可移植的 UI 工具包。 换句话说，这是一个全面的应用软件开发套件（SDK），其中包含 widgets 和工具。</p><h3 id="Flutter-应用场景"><a href="#Flutter-应用场景" class="headerlink" title="Flutter 应用场景"></a>Flutter 应用场景</h3><p><strong>Flutter 为跨平台开发赋能。</strong></p><p>它为开发人员提供了一种简便的方法来为移动应用（iOS、Android）、<a href="https://flutter.cn/web">Web</a> 和桌面端构建和部署具有视觉吸引力的原生编译的应用程序：一套代码运行在多端，即所谓的一次编写处处运行。</p><p><strong>请注意:</strong></p><ul><li><p><strong>在使用 Flutter 开发桌面应用程序时，API 仍处于开发阶段</strong>。Google 正在努力扩展 Flutter，以支持桌面端作为目标环境，从而允许开发人员使用该技术创建 macOS，Windows 和 Linux 应用程序。</p><blockquote><p>Flutter 2 已经全平台 SDK 稳定，详情查看 <a href="https://juejin.cn/post/6935520179262586917">Flutter 2.0 发布 | 针对 Web，移动端和桌面端构建的下一代 Flutter</a></p></blockquote></li><li><p>Flutter for Web 是 Flutter 的代码兼容实现，允许您将用 Dart 编写的现有 Flutter 代码编译为可嵌入到浏览器中并部署到任何 Web 服务器的客户端。 您可以使用 Flutter 的所有功能，并且不需要浏览器插件</p></li></ul><p>关于 Flutter 的更多信息：</p><ul><li>免费且开源</li><li>它基于 <a href="https://dart.cn/">Dart</a>——一种快速的，面向对象的编程语言。<br>Dart 相对较新，并且易于学习-特别是对于经验丰富的熟悉 Java 和 C# 的开发人员</li><li>该架构基于非常流行的反应式编程（它遵循与 React 相同的风格）</li><li>它提供了自己的控件，这些控件是从自己的高性能渲染引擎中提取的，这些控件快速，有吸引力且可自定义</li><li>多亏了控件的使用经验，Flutter 应用程序的外观和感觉都很棒（尽管你仍然可以创建自己的 <a href="https://www.thedroidsonroids.com/services/product-design-ux-and-ui-for-mobile-and-web-app">自定义应用程序设计</a>，使用遵循特定平台准则的易于使用的 UI 组件）</li></ul><h3 id="谁创建了-Flutter-项目"><a href="#谁创建了-Flutter-项目" class="headerlink" title="谁创建了 Flutter 项目"></a>谁创建了 Flutter 项目</h3><p>一个来自 <strong>Google</strong> 的团队创建了 Flutter 项目。</p><p>但是作为一个开源项目，Google 和 Flutter 社区都在为 Flutter 的开发贡献力量。</p><h3 id="Flutter-有多成熟？"><a href="#Flutter-有多成熟？" class="headerlink" title="Flutter 有多成熟？"></a>Flutter 有多成熟？</h3><p>Flutter 简明历史：</p><ul><li><strong>2018 年 2 月，世界移动通信大会 – Flutter 发布了第一个 beta 版本</strong></li><li>2018 年 4 月，Google I&#x2F;O 大会 – Flutter beta 2 发布</li><li>2018 年 5 月，Google I&#x2F;O – Flutter beta 3 发布<ul><li>Flutter 进入 <strong>GitHub Top 100</strong></li></ul></li><li>2018 年 6 月 – Flutter Preview 1 发布</li><li>2018 年 9 月 – Flutter Preview 2 发布</li><li>2018 年 11 月 - Dart 2.1 发布</li><li>2018 年 12 月，Google I&#x2F;O – Flutter 1.0 发布<ul><li>自 2018 年 12 月以来，Flutter 被认为是稳定且可投入生产的技术里程碑</li></ul></li><li>2019 年 2 月，移动世界大会– Flutter 1.2 发布</li><li>2019 年 5 月，世界移动通信大会<ul><li>Flutter 1.5 发布</li><li><a href="https://flutter.dev/web">Flutter for Web</a> 预览版发布</li><li>Dart 2.3 发布</li></ul></li><li>2019 年 7 月, <a href="https://medium.com/flutter/announcing-flutter-1-7-9cab4f34eacf">Flutter 1.7 发布</a></li><li>2019 年九月, <a href="https://medium.com/flutterpub/whats-new-in-flutter-1-9-26b3c518dd0">Flutter 1.9 发布</a></li></ul><p>如你所见，Google 以快速的步伐从 Flutter beta 版本过渡到 Flutter 1.0 稳定版。</p><p>更妙的是，自 Flutter 1.0 发布以来，Flutter 团队并没有因此而感到骄傲。他们积极致力于使工具包更强大，更灵活，并优先考虑增强稳定性、性能和质量。</p><p>此外，Flutter 1.5 包含数百项响应开发人员反馈的更改。</p><p>Flutter 现在是 GitHub 上排名前 20 位的活跃仓库之一（截至 2019 年 12 月 11 日排名第 16 位），这证明了开发人员社区正在使用它，并继续为其改进做出贡献。</p><p>总而言之，Flutter 仍然是一个新兴的技术。</p><p>但是，考虑到 Flutter 不断改进的步伐以及它的爆炸性普及，我们可以充满信心地说，它随着年龄的增长已经非常稳定和成熟。Fluter 2.0 发布之后更是如此。</p><p>从发展速度来看，我们预计 Flutter for Web 也将在 12 个月内稳定下来。（截止发稿已经稳定了）</p><h3 id="哪些流行的应用在使用-Flutter？"><a href="#哪些流行的应用在使用-Flutter？" class="headerlink" title="哪些流行的应用在使用 Flutter？"></a>哪些流行的应用在使用 Flutter？</h3><ul><li>阿里巴巴的闲鱼 App – 阿里巴巴是全球最大的电子商务公司之一</li><li>Hamilton App – 百老汇音乐剧的官方应用程序</li><li>Google Ads app</li><li>Reflectly</li><li>京东金融 App – 奶茶妹妹老公的公司</li><li>Topline app by Abbey Road Studios</li></ul><h3 id="React-Native-是什么"><a href="#React-Native-是什么" class="headerlink" title="React Native 是什么?"></a>React Native 是什么?</h3><p>React Native 是使用 JavaScript 的开源移动应用程序框架。</p><h3 id="React-Native-应用场景"><a href="#React-Native-应用场景" class="headerlink" title="React Native 应用场景?"></a>React Native 应用场景?</h3><p>React Native 适用于以下场景：</p><ul><li><strong>跨平台开发</strong></li><li>使用 JavaScript 开发移动应用</li><li>一套代码开发 Android 和 iOS 应用</li><li>使用和 React 相同的设计</li></ul><p><strong>请注意：</strong></p><ul><li>使用 React Native <strong>创建的应用程序不是移动 Web 应用程序</strong>。 React Native 使用与常规 iOS 和 Android 应用相同的基本 UI 构建块：这意味着，你不需要使用 Java、Kotlin 或者 Swift 构建应用，而是使用 JavaScript 和 React 将相同的代码放在一起。</li><li>React Native 使用类似于 Flutter 中的控件的组件进行开发。</li></ul><p>要使用 React Native 开发<strong>Web 和桌面应用程序</strong>，最好使用外部库。</p><h3 id="谁创建了-React-Native-项目"><a href="#谁创建了-React-Native-项目" class="headerlink" title="谁创建了 React Native 项目?"></a>谁创建了 React Native 项目?</h3><p><strong>Facebook</strong> 创建了 React Native</p><h3 id="React-Native-成熟度"><a href="#React-Native-成熟度" class="headerlink" title="React Native 成熟度?"></a>React Native 成熟度?</h3><p>React Native 简明历史:</p><ul><li>2013 年夏季，Facebook 黑客马拉松 – React Native 作为 Facebook 内部项目开始</li><li>2015 年 1 月，React.js 会议– React Native 1 发布</li><li>2015 年 3 月，F8 大会 – React Native 正式启动<ul><li>Facebook 宣布 React Native 为 “开放使用并在 Github 上可用”</li></ul></li></ul><p>综上所述，可以肯定有两件事：React Native 比 Flutte r 年长，并且拥有更大的社区。 更不用说 Facebook 团队有足够的时间稳定 API 并专注于解决所有潜在问题：</p><ul><li><a href="https://github.com/react-native-community/discussions-and-proposals/issues/6">Lean Core </a>– 通过将可选组件&#x2F;功能移动到单独的存储库中来减小应用程序的大小（以在需要时添加到应用程序中）</li><li><a href="https://github.com/react-native-community/discussions-and-proposals/issues/40">TurboModules</a> – 用于改进原生模块的处理</li><li><a href="https://github.com/react-native-community/discussions-and-proposals/issues/4">React Native Fabric</a> – 重构 UI 层</li></ul><h3 id="哪些流行的应用在使用-React-Native"><a href="#哪些流行的应用在使用-React-Native" class="headerlink" title="哪些流行的应用在使用 React Native?"></a>哪些流行的应用在使用 React Native?</h3><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fe8a0e49580c488e96dedfa7b6cf484c~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>如果你想查看其他使用 React Native 制作的应用示例，请查看 <a href="https://facebook.github.io/react-native/showcase.html">official React Native showcase</a>.</p><h2 id="Flutter-–-优势和劣势"><a href="#Flutter-–-优势和劣势" class="headerlink" title="Flutter – 优势和劣势"></a>Flutter – 优势和劣势</h2><p>在本节中，我们简要讨论 Flutter 的主要优缺点。</p><p>如果您想了解更多信息，请查看 <a href="https://www.thedroidsonroids.com/blog/flutter-in-mobile-app-development-pros-and-cons-for-app-owners">这篇文章</a> Flutter 对应用程序所有者的利弊。</p><h3 id="✅-Flutter-的优势"><a href="#✅-Flutter-的优势" class="headerlink" title="✅ Flutter 的优势"></a>✅ Flutter 的优势</h3><h4 id="1-热重载-快速开发"><a href="#1-热重载-快速开发" class="headerlink" title="1. 热重载 &#x3D; 快速开发"></a>1. 热重载 &#x3D; 快速开发</h4><p>从开发人员的角度来看，Flutter 提供了更多动态的，更快的应用程序开发。 这是 Flutter 最为人称道的事情之一，每一家顶级移动应用开发公司都对此表示赞赏</p><p>开发人员可以即时更改代码库，并立即将其反映在应用程序中。 这就是所谓的 <a href="https://flutter.cn/docs/development/tools/hot-reload">热重载</a> 功能，响应更改通常是秒级甚至毫秒级。</p><p>该功能可帮助团队立即添加功能，修复错误并尝试新的想法。 另外，在开发人员与设计人员的协作中，<strong>热加载非常方便</strong>。</p><h4 id="2-一次编写-多处运行"><a href="#2-一次编写-多处运行" class="headerlink" title="2. 一次编写, 多处运行"></a>2. 一次编写, 多处运行</h4><p>使用 Flutter，开发人员仅可以为两个应用程序编写一个代码库——覆盖 iOS 和 Android 平台。</p><p>Flutter 具有平台无关性，因为它具有自己的 widgets 和设计，这意味着你可以在两个平台上拥有完全相同的应用程序（如果你希望区分自己的应用程序，这同样容易实现）。</p><p>Google 目前正在开发 Flutter for Web，您可以将其作为预览版进行查看。 一旦上线，单个代码库将覆盖 Android，iOS 和 Web 平台。</p><h4 id="3-最多减少-50％-的测试"><a href="#3-最多减少-50％-的测试" class="headerlink" title="3. 最多减少 50％ 的测试"></a>3. 最多减少 50％ 的测试</h4><p>假设你在两个平台上都有相同的应用程序，则质量保证流程会更快更少的测试。</p><p>我们编写的自动化测试大约减少 50％，因为我们可以创建相同的测试以在两个平台上运行，从而减少了对 QA 团队的需求。</p><p>也就是说，你仍然必须在与原生编程类似的级别上运行手动测试，因为你的质量检查专家必须手动检查每个平台上的两个应用程序。</p><h4 id="4-更快的应用"><a href="#4-更快的应用" class="headerlink" title="4. 更快的应用"></a>4. 更快的应用</h4><p><strong>Flutter 应用程序运行流畅，快速滚动时不会卡顿。</strong> 为什么？</p><p>Flutter 使用 <a href="https://skia.org/">Skia 图形库</a>。 因此，每次视图更改时，UI 都会重新绘制。</p><p>大部分工作在 GPU 上完成； 这就是 Flutter UI 流畅且可提供 60fps（每秒帧数）的原因。</p><p>但是在开发过程中必须小心，以免重绘视图中数据未更改的那些元素。</p><p>重绘整个视图而不是仅重绘那些已更改的元素会影响应用程序的性能和速度，尤其是在需要经常重载视图的情况下，例如在秒表应用程序中。</p><h4 id="5-用户会喜欢的设计"><a href="#5-用户会喜欢的设计" class="headerlink" title="5. 用户会喜欢的设计"></a>5. 用户会喜欢的设计</h4><p>Flutter 不依赖原生系统组件。 相反，Flutter 拥有自己的一组自定义 widgets，这些 widgets 由框架的图形引擎呈现和管理。 用户将看到与典型的原生应用程序不同的 UI 组件，但这不一定是不利的。</p><p>Flutter 应用程序具有特别友好的用户界面：Flutter 与 React Native 相比具有至关重要的优势，源于对视觉细节的超级专注。 Flutter 的创建是为了使你可以轻松创建自己的窗口 widgets，或简单地自定义现有窗口 widgets。</p><p>随意浏览 <a href="https://flutter.cn/widgets/">核心 Widget 目录</a>; 或者，单击链接以查看 <a href="https://flutter.cn/docs/development/ui/widgets/material">Material Design widgets</a> 和 <a href="https://flutter.cn/docs/development/ui/widgets/cupertino">Cupertino widgets</a> 的示例。</p><h4 id="6-一样的-UI-甚至在旧机型上"><a href="#6-一样的-UI-甚至在旧机型上" class="headerlink" title="6. 一样的 UI, 甚至在旧机型上"></a>6. 一样的 UI, 甚至在旧机型上</h4><p>甚至新的应用在旧的 iOS 或 Android 系统上看起来也一样，因此你不必担心支持旧的设备。</p><table><thead><tr><th align="center">Android 5.1.1</th><th align="center">Android 8.1.0</th></tr></thead><tbody><tr><td align="center"><img src="https://media.giphy.com/media/8mdR9KZHBVue6J8ItN/giphy.gif" alt="紫升"></td><td align="center"><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/53ae3b6533ea4548bd447cbb59feaa72~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></td></tr></tbody></table><h4 id="7-完美适用于-MVP"><a href="#7-完美适用于-MVP" class="headerlink" title="7. 完美适用于 MVP"></a>7. 完美适用于 MVP</h4><p>如果你需要为应用程序构建 MVP（Minimum Viable Product，最小可行产品）（例如，作为潜在投资者的展示），则 Flutter 是理想的选择，尤其是在时间短的情况下。 另请阅读：</p><ul><li><a href="https://www.thedroidsonroids.com/blog/how-to-build-minimum-viable-product-mvp-business-plan">How to Build a Minimum Viable Product – MVP Guide for App Owners</a></li><li><a href="https://www.thedroidsonroids.com/blog/mobile-app-development-cost-in-2021">App development cost in 2021</a></li></ul><p>查看 <a href="https://play.google.com/store/apps/details?id=io.flutter.demo.gallery">Flutter Gallery 应用程序</a>，其中包含 Flutter 的核心功能，小部件和小插图的演示。</p><table><thead><tr><th align="center">Flutter Galary</th><th align="center">Flutter Galary</th><th align="center">Flutter Galary</th></tr></thead><tbody><tr><td align="center"><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7dc504630daa48f99d68f68a33d40ea0~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></td><td align="center"><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c80f1894916144fdb8a3cdbd15131ed9~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></td><td align="center"><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2b2171b61cee4213bed667635194a633~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></td></tr></tbody></table><h3 id="🔻-Flutter-的劣势"><a href="#🔻-Flutter-的劣势" class="headerlink" title="🔻 Flutter 的劣势"></a>🔻 Flutter 的劣势</h3><h4 id="1-开发者社区的规模"><a href="#1-开发者社区的规模" class="headerlink" title="1. 开发者社区的规模"></a>1. 开发者社区的规模</h4><p>大多数人会告诉你：React Native 相对 Flutter 的一个关键优势是其更加成熟，经验更丰富的开发者社区。<br>此外，就编程语言而言，至少在目前，Dart 还没有像 JavaScript 那样得到广泛使用。</p><p>实际上，要想以 “哥哥” 相称，Flutter 有很多工作要做，这是可以理解的。<br>社区需要时间来教育其受众并获得更多的经验，这对于任何新的年轻的工具都是必经之路。</p><p>但是，我们并不认为这是一个明显的劣势。<br>同样值得注意的是，Flutter 社区正在成倍增长。</p><p>另外，开发套件包也是令人兴奋的。</p><p><strong>请注意：</strong></p><ul><li><p><a href="https://insights.stackoverflow.com/trends?tags=flutter">Stack Overflow Trends</a> 标签趋势表明开发人员对 Flutter 的兴趣大大增加</p></li><li><p>在 Udemy 上，有 50 多门 <a href="https://www.udemy.com/topic/google-flutter/">Flutter 课程</a>（约 213,000 个课程的参与者）</p><p>还有 35 项 <a href="https://www.udemy.com/topic/dart-programming-language/">Dart 课程</a>（约有 187,500 门课程的参与者（2019 年 6 月 25 日））</p><p><a href="https://www.udemy.com/topic/react-native/">React Native</a> 的数字大致相同——共有 597,000 门课程的 287,000 名学生</p></li><li><p>Flutter 有 11.5 万 Github stars；React Native 有 9.3 万</p></li></ul><p><strong>综上所述，Flutter 目前确实有一个较小的，经验不足的社区，而 Dart 比 JavaScript 更具优势。</strong></p><p><strong>尽管如此，目前的趋势表明，Flutter 将在这方面赶上竞争对手。</strong></p><h4 id="2-库和支持-令人印象深刻，但仍然没有像原生开发一样丰富"><a href="#2-库和支持-令人印象深刻，但仍然没有像原生开发一样丰富" class="headerlink" title="2. 库和支持 - 令人印象深刻，但仍然没有像原生开发一样丰富"></a>2. 库和支持 - 令人印象深刻，但仍然没有像原生开发一样丰富</h4><p>Google 对 Flutter 的支持令人印象深刻，但 Flutter 还是一个相当新的事物。<br>这意味着你无法始终在现有库中找到所需的功能，因此开发人员可能需要自己构建自定义功能，这需要时间。</p><h4 id="3-持续集成支持"><a href="#3-持续集成支持" class="headerlink" title="3. 持续集成支持"></a>3. 持续集成支持</h4><p>在撰写本文时，Flutter 缺少对 Travis 或 Jenkins 等 CI 平台的支持。<br>因此，要实现自动构建，测试和部署，你的开发人员需要使用和维护自定义脚本，<br>例如 <a href="https://github.com/yjbanov/flutter_travis_sample/blob/master/.travis.yml">这个</a>。</p><p>值得注意的是：</p><ul><li>Flutter Live 2018 宣布了适用于 Flutter 应用程序的新 CI&#x2F;CD 系统——<a href="https://codemagic.io/start/">Codemagic</a></li><li>2019 年 1 月，<a href="https://blog.bitrise.io/flutter-ci-on-bitrise-v1">Bitrise 发布了功能齐全的 Flutter CI</a></li></ul><h4 id="4-平台风险"><a href="#4-平台风险" class="headerlink" title="4. 平台风险"></a>4. 平台风险</h4><p>即使 Flutter 是开源的，但如果 Google 决定取消对该项目的支持，那将是一场灾难。</p><p>也就是说，自 Google 团队发布 Flutter 的 Beta 版以来，它一直在加大对 Flutter 的支持，<br>正如 Flutter 在 19 届 Google IO 期间的杰出作用以及最近的 Flutter Live 活动所表明的那样。</p><p>很难想像没有 Google 的 Flutter 会走向何方。</p><h4 id="5-应用大小"><a href="#5-应用大小" class="headerlink" title="5. 应用大小"></a>5. 应用大小</h4><p>用 Flutter 编写的应用程序比原生应用程序大。<br>请查看文章 <a href="https://android.jlelse.eu/comparing-apk-sizes-a0eb37bb36f">比较 APK 大小</a>。<br>但是，Flutter 团队正在研究 <a href="https://github.com/flutter/flutter/issues/16833#issuecomment-410103493">减小应用程序的大小</a>。</p><h2 id="React-Native-–-优势和劣势"><a href="#React-Native-–-优势和劣势" class="headerlink" title="React Native – 优势和劣势"></a>React Native – 优势和劣势</h2><h3 id="✅-React-Native-优势"><a href="#✅-React-Native-优势" class="headerlink" title="✅ React Native 优势"></a>✅ React Native 优势</h3><h4 id="1-热刷新-快速开发"><a href="#1-热刷新-快速开发" class="headerlink" title="1. 热刷新 &#x3D; 快速开发"></a>1. 热刷新 &#x3D; 快速开发</h4><p>本质上与 Flutter 具有相同的功能。</p><p>热重载允许开发人员将新代码直接注入正在运行的应用程序中，从而加快了开发过程。<br>因此，开发人员可以立即看到更改，而无需重建应用程序。</p><p>热重装还保留了应用程序的状态，避免了在完全重装期间丢失它的风险（在基于状态的框架中是一项重要优势）—— 加快了移动应用程序的开发过程。</p><p>为了改善热重载的开发人员体验，React Native 团队在 0.61 版本中包括一项称为快速刷新的新功能，<br>该功能将实时重载和热重载相结合。<br>与以前的版本相比，它对错别字和错误的处理更具弹性。<br>您可以在 <a href="https://facebook.github.io/react-native/blog/2019/09/18/version-0.61#fast-refresh">此处</a> 阅读更多有关快速刷新的信息。</p><h4 id="2-一处编写-处处运行"><a href="#2-一处编写-处处运行" class="headerlink" title="2. 一处编写, 处处运行"></a>2. 一处编写, 处处运行</h4><ul><li><p>与 Flutter 完全相同：编写单个代码库为 2 个应用程序提供动力 —— 涵盖 Android 和 iOS 平台。</p><p>更好的是，JavaScript 通过与 <a href="https://www.thedroidsonroids.com/services/web-app-development">Web 应用程序</a><br>共享代码来编写跨平台应用程序时，你也可以一臂之力。这是通过创建可编译为目标平台的抽象组件来完成的。</p></li></ul><p>参见下面的示例库，这些库可让你在 iOS 和 Android 以外的平台（包括 Web 和桌面应用程序）上同时创建代码：</p><ul><li><a href="https://github.com/necolas/react-native-web">React Native for Web</a> – 支持 Android，iOS 和 Web（Twitter 使用它来创建 Twitter Lite）</li><li><a href="https://github.com/microsoft/reactxp">ReactXp</a> – 由 Skype 团队开发，以支持 Android，iOS 和 Web；另外，可在 Windows 10（UWP）上使用</li><li><a href="https://github.com/microsoft/react-native-windows">react-native-windows</a> – 由 Microsoft 团队开发，支持 Windows 10 支持的所有设备（PC，平板电脑，2 合 1 电脑），Xbox，混合现实设备等）</li></ul><p><strong>顺便一提</strong></p><p>Bartosz（我们的 React Native 开发人员）对事物的看法与大多数事物有所不同，因此更喜欢使用替代方法。</p><p>如果将 Web 桌面应用程序，移动 Web 应用程序和原生应用程序进行比较，则可以假定它们共享相同的业务逻辑，但它们可能需要满足不同用户需求的特定 UI。</p><p>所以，为什么不呢？</p><ol><li>将共享代码提取到单独的存储库中；</li><li>将其视为一个单独的项目；</li><li>是否以与其他依赖项相同的方式注入代码？</li></ol><p>通过这种方式，开发人员可以专注于将应用程序编写到实际平台上，而不必考虑跨平台兼容性。</p><p>观看 <a href="https://www.youtube.com/watch?v=IA_M2ESWoXw">Ben Ellerby 的演示文稿</a> 了解该方法，如果你喜欢在平台上创建抽象的想法，请查看 <a href="https://www.youtube.com/watch?v=ryMvNklnDjU">Radek Pietruszewski 的演示文稿</a>。</p><h4 id="3-它使用广泛流行的语言-——-JavaScript"><a href="#3-它使用广泛流行的语言-——-JavaScript" class="headerlink" title="3. 它使用广泛流行的语言 —— JavaScript"></a>3. 它使用广泛流行的语言 —— JavaScript</h4><p>React Native 使用 JavaScript：许多开发人员都熟悉的一种编程语言（而 Dart 仍然不那么广为人知或使用）。而且，如果你是喜欢静态类型的编程语言的开发人员，则甚至可以使用 <a href="https://www.typescriptlang.org/">TypeScript</a> —— 一个 JavaScript 超集。</p><h4 id="4-开发人员的选择自由"><a href="#4-开发人员的选择自由" class="headerlink" title="4. 开发人员的选择自由"></a>4. 开发人员的选择自由</h4><p>React Native 使开发人员可以构建跨平台应用程序。不做更多事情。</p><p>优点是 React Native 允许开发人员精确地决定他们要使用什么解决方案； 两者都是根据项目的要求以及开发人员的偏好进行的。</p><p>假设，如果开发人员需要决定如何处理全局状态（如何存储和管理应用程序中许多组件中使用的数据），请选择路由器库，或者在 JavaScript 和 TypeScript 之间进行选择——他们可以决定是否使用自定义 UI 库，或者自己编写。</p><h4 id="5-相对成熟度"><a href="#5-相对成熟度" class="headerlink" title="5. 相对成熟度"></a>5. 相对成熟度</h4><p>官方的 React Native 版本是 5 年前发布的，因此 Facebook 团队有足够的时间来稳定 API，以及专注于解决问题和解决问题。</p><p>现在，他们正在努力进行一些令人兴奋的改进——例如减小应用程序的大小。</p><h4 id="6-一个活跃而广阔的社区"><a href="#6-一个活跃而广阔的社区" class="headerlink" title="6. 一个活跃而广阔的社区"></a>6. 一个活跃而广阔的社区</h4><p>React Native 有一个庞大的开发者社区。不仅如此，还有 <strong>无数的教程，库和 UI 框架</strong>，它们使学习该技术变得容易，并且可以快速，轻松地进行开发。</p><p>而且，如果你比较专注于收集有关特定技术的文章，工具和材料的存储库，则 React Native 的位置要比 Xamarin，Flutter 或 Ionic 更好（来源：<a href="https://github.com/Solido/awesome-flutter">Awesome-Flutter</a>，<a href="https://github.com/jondot/awesome-react-native">Awesome-ReactNative</a>，<a href="https://github.com/Alexintosh/Awesome-Ionic">Awesome-Ionic</a>，<a href="https://github.com/XamSome/awesome-xamarin">Awesome-Xamarin</a>。</p><p>而且，React Native 是 **“ React 家族” ** 的一部分。</p><ul><li>还值得引起你注意还有 <a href="https://expo.io/">Expo</a> —— 一种简化了对原生 API 的访问的 React 构造框架，该框架具有针对典型移动功能（例如，推送通知）的现成解决方案。</li><li>值得一提的第二个库是 <a href="https://aws-amplify.github.io/">AWS Amplify</a>：该解决方案通过涵盖身份验证，存储，推送通知和分析来简化与 AWS 功能的集成。</li></ul><h4 id="7-易于学习"><a href="#7-易于学习" class="headerlink" title="7. 易于学习"></a>7. 易于学习</h4><p>我们列表中的这一优势是针对 React 开发人员的。<br><strong>如果您具有 Web 开发背景并且已经使用了流行的 React 解决方案，则可以轻松使用 React Native</strong> ，<br>而无需学习新的库。 您可以使用相同的库，工具和模式。</p><h4 id="8-最多减少-50％-的测试"><a href="#8-最多减少-50％-的测试" class="headerlink" title="8. 最多减少 50％ 的测试"></a>8. 最多减少 50％ 的测试</h4><p>设你在两个平台上都有相同的应用程序，则质量保证流程会更快更少的测试。</p><p>我们编写的自动化测试大约减少 50％，因为我们可以创建相同的测试以在两个平台上运行，从而减少了对 QA 团队的需求。</p><p>也就是说，你仍然必须在与原生编程类似的级别上运行手动测试，因为你的质量检查专家必须手动检查每个平台上的两个应用程序。</p><h3 id="🔻-React-Native-劣势"><a href="#🔻-React-Native-劣势" class="headerlink" title="🔻 React Native 劣势"></a>🔻 React Native 劣势</h3><h5 id="1-只是接近原生而已"><a href="#1-只是接近原生而已" class="headerlink" title="1. 只是接近原生而已"></a>1. 只是接近原生而已</h5><p>和其他跨平台解决方案一样，不管是性能还是用户体验都只是接近原生而已。</p><p>但是，与 Flutter 相比，使用 React Native 更容易获得自然的感觉。 如果你希望 Flutter 应用程序具有原生组件，则需要进行其他工作。</p><h4 id="2-开箱即用的组件少"><a href="#2-开箱即用的组件少" class="headerlink" title="2. 开箱即用的组件少"></a>2. 开箱即用的组件少</h4><p><strong>React Native 开箱只支持基础组件</strong> (许多都是针对特定平台开箱即用，例如按钮，加载指示器或滑块).</p><p>外部存储库包含许多其他 React Native 组件。 开发人员可以在项目中使用它们，但这需要额外的精力和时间。</p><p>另一方面，Flutter 旨在支持开箱即用的 Material Design，因此该框架支持更多的 widget 来节省开发者时间。<br>使用 Flutter 的开发人员可以使用易于定制且跨平台一致的预制窗口 widget 来创建大多数视图。</p><h4 id="3-开发人员的选择自由"><a href="#3-开发人员的选择自由" class="headerlink" title="3. 开发人员的选择自由"></a>3. 开发人员的选择自由</h4><p>既是诅咒又是祝福。</p><p>开发人员创建新项目后，他们需要确定要使用哪个导航包以及哪个全局状态管理。</p><p>了解每个解决方案的细微差别，最终决定要用于项目的最佳解决方案，可能需要花费大量时间。</p><h4 id="4-很多被遗弃的包"><a href="#4-很多被遗弃的包" class="headerlink" title="4. 很多被遗弃的包"></a>4. 很多被遗弃的包</h4><p>React Native 拥有大量的库。不幸的是，它们太多要么质量低下，要么完全被遗弃。</p><p>Dan Abramov 建议检查存储库中的问题数量和提交频率，以防止使用废弃的程序包。</p><p>更多信息请查看 <a href="https://github.com/react-native-community/discussions-and-proposals/issues/104">summary of his discussion of “What do you dislike about React Native?”</a></p><h4 id="5-脆弱的用户界面"><a href="#5-脆弱的用户界面" class="headerlink" title="5. 脆弱的用户界面"></a>5. 脆弱的用户界面</h4><p>React Native 在后台使用原生组件这一事实应该使你充满信心，<strong>每次 OS UI 更新后，你的应用程序组件也将立即升级</strong>。</p><p>也就是说 <strong>这可能会破坏应用的用户界面，但这种情况很少发生。</strong></p><p>更糟糕的是，如果更新会导致 Native Components API 发生某些更改，则更新会变得更加危险。 好消息是这种事件很少发生。</p><p>而就 Flutter 而言（由于框架自行重新创建原生组件的方式），应用程序 UI 更加稳定。</p><h4 id="6-应用尺寸大于原生"><a href="#6-应用尺寸大于原生" class="headerlink" title="6. 应用尺寸大于原生"></a>6. 应用尺寸大于原生</h4><p>用 React Native 编写的应用程序必须能够运行 Javascript 代码（JavaScript 虚拟机）。<br>Android 默认情况下不具有此功能，这意味着应用程序需要包含一个支持 JavaScript 代码的库，从而导致应用程序比其原生 Android 同类产品更大。</p><p>使用 React Native 制作的 <a href="https://www.thedroidsonroids.com/services/ios-mobile-app-development">iOS 应用</a> 并没有这个问题，但是它们通常比原生的更大。 好消息是正如我们之前提到的，React Native 团队正在 <a href="https://github.com/react-native-community/discussions-and-proposals/issues/6">减小应用程序的大小</a>。</p><p>阅读有关 <a href="https://www.thedroidsonroids.com/blog/react-native-pros-and-cons">2021 年为应用程序所有者进行 React Native 开发的利弊</a><br>的更多信息。</p><h2 id="预测未来：Flutter＆React-Native"><a href="#预测未来：Flutter＆React-Native" class="headerlink" title="预测未来：Flutter＆React Native"></a>预测未来：Flutter＆React Native</h2><p><strong>越来越多的公司被 Flutter 所吸引。</strong> 毕竟，随着 Google 不断完善其工具，我们正在见证 Flutter SDK 的每月改进。 <strong>另外，社区始终是乐于助人和热情的</strong>。 而且，我们可以期待 Flutter 很快将使我们不仅可以创建移动应用程序，还可以创建用于 Web 和桌面的应用程序。</p><p>鉴于像阿里巴巴这样的领先公司已经在使用 Flutter，它的未来看起来很有希望。</p><p>至于 <strong>React Native</strong>，不错，Facebook 当前正在致力于 <strong>该技术的大规模重新架构</strong>。</p><p>团队正在尽最大努力改善对 React Native 用户和更广泛社区的支持。<br>有了这个，社区现在可以通过使用 <a href="https://github.com/react-native-community/discussions-and-proposals/issues/">专用 GitHub 存储库</a> 关注进度。</p><p>此类体系结构改进的实际结果是：</p><ul><li><p><a href="https://engineering.fb.com/android/hermes/">Hermes</a> ：针对移动应用程序优化的开源 JavaScript 引擎，可缩短交互时间，并降低应用程序大小和内存利用率</p></li><li><p>0.60 版本中的重大更改（<a href="https://facebook.github.io/react-native/blog/2019/07/03/version-60%EF%BC%89">https://facebook.github.io/react-native/blog/2019/07/03/version-60）</a> - 默认情况下，使用最受欢迎的依赖项管理器 CocoaPods 可以更轻松地管理 iOS 依赖项， - 你可以将 React Native 迁移到 AndroidX， - 你可以在精益核心流程中提取可选功能。</p></li></ul><p><strong>创建一个开放的环境来讨论 React Native 是重要的一步。</strong> 这既是不断改进的标志，又是该技术光明前景的信号。</p><p>由于 React Native 在市场上具有如此稳定的地位并处于持续发展的轨道上，我们不太可能很快看到该工具包被遗弃。 阅读有关 React Native 的 <a href="https://facebook.github.io/react-native/blog/2018/11/01/oss-roadmap">长期愿景</a>。</p><p>尽管如此，Flutter 还是 React Native 的强大竞争对手。</p><p>另请阅读：<a href="https://www.thedroidsonroids.com/blog/benefits-outsourcing-software-development">外包软件开发 - 应用程序所有者的收益和风险</a></p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/eee38519bec9/</id>
    <link href="https://blog.zisheng.pro/posts/eee38519bec9/"/>
    <published>2021-04-19T09:50:36.000Z</published>
    <summary>从 Flutter 诞生之日起，React Native 就一直被拿来与之作比较，从谷歌超过 700 万的相关搜索结果中可以一瞥这场跨端王位之争的激烈程度。本文不像我翻译的标题一样在捧一踩一，而是较客观地对两者进行评价并给出实际需求中如何抉择。</summary>
    <title>2021 Flutter 和 React Native 如何选</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="加密货币" scheme="https://blog.zisheng.pro/categories/%E5%8A%A0%E5%AF%86%E8%B4%A7%E5%B8%81/"/>
    <category term="区块链" scheme="https://blog.zisheng.pro/categories/%E5%8A%A0%E5%AF%86%E8%B4%A7%E5%B8%81/%E5%8C%BA%E5%9D%97%E9%93%BE/"/>
    <category term="MetaMask" scheme="https://blog.zisheng.pro/tags/MetaMask/"/>
    <category term="BSC" scheme="https://blog.zisheng.pro/tags/BSC/"/>
    <category term="区块链" scheme="https://blog.zisheng.pro/tags/%E5%8C%BA%E5%9D%97%E9%93%BE/"/>
    <category term="钱包" scheme="https://blog.zisheng.pro/tags/%E9%92%B1%E5%8C%85/"/>
    <category term="小狐狸" scheme="https://blog.zisheng.pro/tags/%E5%B0%8F%E7%8B%90%E7%8B%B8/"/>
    <content>
      <![CDATA[<h2 id="下载"><a href="#下载" class="headerlink" title="下载"></a>下载</h2><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images/202111232204640.png" alt="小狐狸钱包（MetaMask）教程"></p><h2 id="开始"><a href="#开始" class="headerlink" title="开始"></a>开始</h2><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images/202111232202734.png" alt="小狐狸钱包（MetaMask）教程"></p><h2 id="创建钱包"><a href="#创建钱包" class="headerlink" title="创建钱包"></a>创建钱包</h2><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images/202111232204544.png" alt="创建小狐狸钱包"></p><h2 id="同意"><a href="#同意" class="headerlink" title="同意"></a>同意</h2><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images/202111232205121.png" alt="小狐狸钱包（MetaMask）教程"></p><h2 id="创建密码"><a href="#创建密码" class="headerlink" title="创建密码"></a>创建密码</h2><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images/202111232206560.png" alt="创建小狐狸钱包密码"></p><p><img src="https://cdn.jsdelivr.net/gh/youngjuning/images/202111232206713.png" alt="创建小狐狸钱包密码"></p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://metamask.io/">metamask</a></li><li><a href="https://academy.binance.com/zh/articles/an-introduction-to-binance-smart-chain-bsc">币安智能链 (Binance Smart Chain，简称 BSC) </a></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/77349c019445/</id>
    <link href="https://blog.zisheng.pro/posts/77349c019445/"/>
    <published>2020-11-23T13:58:00.000Z</published>
    <summary>本文主要讲解了小狐狸钱包教程，具有详细的操作步骤，对你有一定的参考价值，需要的朋友可以参考下。</summary>
    <title>小狐狸钱包（MetaMask）教程</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="工具" scheme="https://blog.zisheng.pro/categories/%E5%B7%A5%E5%85%B7/"/>
    <category term="git" scheme="https://blog.zisheng.pro/tags/git/"/>
    <category term="git-filter-repo" scheme="https://blog.zisheng.pro/tags/git-filter-repo/"/>
    <category term="git-filter-branch" scheme="https://blog.zisheng.pro/tags/git-filter-branch/"/>
    <category term="git-filter" scheme="https://blog.zisheng.pro/tags/git-filter/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>对于程序员来说，公司就像是工地，代码就是我们的钢筋水泥。那在工地什么最重要呢？没错就是安全生产。本文要讨论的是开发中的合规安全。GitHub 作为当代开源社区的基础设施，不仅是个人开发者在上面维护项目，很多企业也会将内部优秀项目选择开源反馈社区和扩大社区影响力。然后，就出现了公司拖欠工资，员工怒将内部代码开源、员工将敏感信息上传。有些信息虽然看起来不怎么重要，但是如果被不怀好意的黑客盯上，就没有一篇雪花是干净的。</p><p>那如果安全隐患已经存在，我们该如何补救呢？答案是使用 git-filter 能力对 git 记录进行合并、删除等操作。git-filter-repo 正是这类工具的集大成者。下面就让我们一起学习下这款强大的 git log 操作工具吧。</p><h2 id="git-filter-branch"><a href="#git-filter-branch" class="headerlink" title="git-filter-branch"></a>git-filter-branch</h2><p>git 自带的有一个 git-filter-branch 工具，我第一次使用是为了删除单个文件及其相关的 commit 记录。命令如下：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git filter-branch --force --index-filter <span class="string">&#x27;git rm --cached --ignore-unmatch CHANGELOG.md&#x27;</span> --prune-empty --tag-name-filter <span class="built_in">cat</span> -- --all</span><br></pre></td></tr></table></figure><p>执行后，会收到如下的警告：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">WARNING: git-filter-branch has a glut of gotchas generating mangled <span class="built_in">history</span></span><br><span class="line">         rewrites.  Hit Ctrl-C before proceeding to abort, <span class="keyword">then</span> use an</span><br><span class="line">         alternative filtering tool such as <span class="string">&#x27;git filter-repo&#x27;</span></span><br><span class="line">         (https://github.com/newren/git-filter-repo/) instead.  See the</span><br><span class="line">         filter-branch manual page <span class="keyword">for</span> more details; to squelch this warning,</span><br><span class="line">         <span class="built_in">set</span> FILTER_BRANCH_SQUELCH_WARNING=1.</span><br></pre></td></tr></table></figure><p>翻译过来如下：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">警告：翻译过来如下：git-filter-branch有大量的问题，会产生错误的历史记录重写。</span><br><span class="line">        在继续进行之前按 Ctrl-C 中止，然后用一个替代的过滤工具，</span><br><span class="line">        如 https://github.com/newren/git-filter-repo/ 来代替。</span><br><span class="line">        参见 filter-branch 手册页了解更多细节。</span><br><span class="line">        要消除这个警告。请设置 FILTER_BRANCH_SQUELCH_WARNING=1。</span><br></pre></td></tr></table></figure><ul><li><a href="https://github.com/newren/git-filter-repo">git-filter-repo</a></li><li><a href="https://blog.csdn.net/q258523454/article/details/83899911">Github: 单独删除某个文件的所有历史记录</a></li><li><a href="https://serverfault.com/questions/1018302/warning-git-filter-branch-has-a-glut-of-gotchas-generating-mangled-history-rewr">WARNING: git-filter-branch has a glut of gotchas generating mangled history rewrites</a></li><li><a href="https://nyakku.moe/posts/2020/06/12/use-git-filter-repo-clean-git-history.html">使用 git-filter-repo 清理 git 历史记录</a></li><li><a href="https://www.vicw.com/groups/code_monkey/topics/362">使用git filter-repo清理git commit历史中的大文件</a></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/6ead325700ca/</id>
    <link href="https://blog.zisheng.pro/posts/6ead325700ca/"/>
    <published>2020-11-15T11:55:00.000Z</published>
    <summary>对于程序员来说，公司就像是工地，代码就是我们的钢筋水泥。那在工地什么最重要呢？没错就是安全生产。本文要讨论的是开发中的合规安全。</summary>
    <title>使用 git-filter-branch 删除单个文件及其相关的 commit 信息</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="漫谈" scheme="https://blog.zisheng.pro/categories/%E6%BC%AB%E8%B0%88/"/>
    <category term="年中总结" scheme="https://blog.zisheng.pro/tags/%E5%B9%B4%E4%B8%AD%E6%80%BB%E7%BB%93/"/>
    <category term="程序员" scheme="https://blog.zisheng.pro/tags/%E7%A8%8B%E5%BA%8F%E5%91%98/"/>
    <category term="杭州" scheme="https://blog.zisheng.pro/tags/%E6%9D%AD%E5%B7%9E/"/>
    <category term="紫升的博客" scheme="https://blog.zisheng.pro/tags/%E7%B4%AB%E5%8D%87%E7%9A%84%E5%8D%9A%E5%AE%A2/"/>
    <category term="紫升" scheme="https://blog.zisheng.pro/tags/%E7%B4%AB%E5%8D%87/"/>
    <category term="前端开发" scheme="https://blog.zisheng.pro/tags/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    <category term="跨端开发" scheme="https://blog.zisheng.pro/tags/%E8%B7%A8%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    <content>
      <![CDATA[<h2 id="上半年总结"><a href="#上半年总结" class="headerlink" title="上半年总结"></a>上半年总结</h2><p>2020 的上半年注定是不平凡的一年，不管什么谈话基本都可以从疫情聊起：</p><ul><li>朋友A：你们公司复工了吗？还在家办公吗？</li><li>朋友B：你们工资及时发了吗？公司不会倒闭了吧？</li><li>某亲戚：今年这疫情弄得，办个婚礼都没人去，好多都推了。</li><li>面试官：你为什么从之前公司离职？<ul><li>我：教育行业，因为疫情拖欠了俩月工资</li></ul></li></ul><p>总的来说，疫情对我的直接影响还挺大。工作上来说，如果不是被拖欠工资，大概率不会这么快离职；生活上来说，订婚从2月份推迟到了4月底才办…</p><h2 id="工作"><a href="#工作" class="headerlink" title="工作"></a>工作</h2><p>上家公司是从 2018年3月干到2020年5月的，整整两年，写过小程序，负责过公众号、参与过基于element 和 vue 的后台程序开发、基于React Native开发过业务，搞过React Native 项目的架构。离职时，职位停留在了移动端开发负责人，实现了月薪过万的小目标。</p><p>离职原因一方面是因为薪资拖欠，另一方面是在公司也摸到了天花板。第二方面是最让我难受的，这天花板离我的心理预期太远。之前之所以一直拖着没跳槽是信了leader的核心人员以后不会亏待。长期的境况与心理预期不符不管是对公司还是对自己都是不负责任的，再加上疫情拖欠薪资，直接造成了房贷的还款困难（年前刚开始自己承担全部贷款），我最终下定决心离开工作了两年的公司。</p><p>再来说一下新工作吧，本人找工作的base是郑州，一个互联网三线城市。刚开始主动打招呼的和海投回应的都很多，9成都是 Vue 开发，甚至需要维护 jquery 项目。我心想还是主动投投 React Native 岗位吧，现实再次打了我的脸，郑州这边 RN 岗位两个手数的过来，并且范围都是 8-10K，甚至 4-8K。抱着快要放弃的心态，再次缩小范围投递了几家 React 技术栈的公司，最终实现了试用期工资高于上家的目标。</p><h2 id="生活"><a href="#生活" class="headerlink" title="生活"></a>生活</h2><p>在 4月初与小女友定亲啦，当时的沸点也得到了掘友的祝福，证明了掘金是一个有爱的有温度的技术社区。</p><h2 id="学习"><a href="#学习" class="headerlink" title="学习"></a>学习</h2><ol><li>看完了一个关于 Linux 的视频教程，并写下了<a href="https://juejin.im/post/5e81e2db518825737b4ad911">写给前端工程师的Linux实战教程【持续更新】</a>。</li><li>看完了《Docker从入门到实践》，并写下<a href="https://juejin.im/post/5e839f2851882573ab44f1b4">一个前端工程师的Docker学习笔记【持续更新】</a>，感谢掘金编写的推荐，获得了不少流量扶持。</li><li>工作需要，总结了React Native 开发经验并输出了<a href="https://juejin.im/post/5e535e0df265da576b566397">React Native 项目架构面面观【持续更新】</a>、<a href="https://juejin.im/post/5e50f3ace51d4526ef5f8d56">react-navigation 5.x 最佳实践</a> 等多篇相关博文，这几篇也成为了几十篇文章中阅读量和点赞最多的。果然我还是个只能靠实力的蓝孩子。这个系列还有更多的存货没有放出来。待我新工作转正继续输出。</li><li>新公司入职有两个月了，独立负责了两个不大的中台项目，技术栈是基于 umijs 的，而 umijs 基于 dva、antd、react-router，而 dva 又基于 redux。两个项目下来，我的水平停留在了熟练基于 umijs 开发业务。为后续研究源码和性能优化打下了基础。这方面的经验我写了一篇<a href="https://juejin.im/post/5ef1dd62f265da02981f0266">UmiJS开发技巧【持续更新】</a> 做了总结。</li></ol><h2 id="社区"><a href="#社区" class="headerlink" title="社区"></a>社区</h2><p>我现在混得社区比较少，一个是GitHub，一个是掘金。短期内也没计划再混其他的社区（不想精力分散太多）。</p><h3 id="掘金"><a href="#掘金" class="headerlink" title="掘金"></a>掘金</h3><p>从2月份到现在共发布了27篇博客，ps:之前分享的文章的流量看的我心疼（qaq）。</p><p>很多朋友都是通过<a href="https://juejin.im/post/5e535e0df265da576b566397">React Native 项目架构面面观【持续更新】</a>、<a href="https://juejin.im/post/5e50f3ace51d4526ef5f8d56">react-navigation 5.x 最佳实践</a> 这两篇文章认识我的，从推送来看，都是在点赞这两篇文章之后关注我。后期我也会继续输出跨平台相关的技术分享。</p><h3 id="GitHub"><a href="#GitHub" class="headerlink" title="GitHub"></a>GitHub</h3><p>假期的时候，我更新了<a href="https://github.com/youngjuning/wx-promise-pro">wx-promise-pro</a>，支持了TS，并优化了内部实现。更新之后到现在累计增加100star。这是一个微信小程序的增强插件，年前已被 <a href="https://hellogithub.com/periodical/volume/47/#wx-promise-pro">HelloGitHub 47期</a> 收录，有 20+ 的 github 库在使用，欢迎尝试。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/1e22179cfc15/</id>
    <link href="https://blog.zisheng.pro/posts/1e22179cfc15/"/>
    <published>2020-07-15T21:06:51.000Z</published>
    <summary>怕什么真理无穷，进一寸有一寸的欢喜 | 掘金征文 上半年总结 2020 的上半年注定是不平凡的一年，不管什么谈话基本都可以从疫情聊起：  朋友A：你们公司复工了吗？还在家办公吗？ 朋友B：你们工资及时发了吗？公司不会倒闭了吧？ 某亲戚：今年这疫情弄得，办个婚礼都没人去，好多都推了。 面试官：你为什么从之前公司离职？  我：教育行业，因为疫情拖欠了俩月工资    总的来说，疫情对我的直接影响还挺大。...</summary>
    <title>2020 年中总结</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="vant" scheme="https://blog.zisheng.pro/tags/vant/"/>
    <category term="微精通" scheme="https://blog.zisheng.pro/tags/%E5%BE%AE%E7%B2%BE%E9%80%9A/"/>
    <category term="vue" scheme="https://blog.zisheng.pro/tags/vue/"/>
    <content>
      <![CDATA[<p>精通任何技能都是需要时间的，但是往往工作中是不会给你这个时间的，尤其是开发工作。新技术层出不穷，每个项目都可能技术栈不一样。</p><p>无法改变环境，那么就只能改变我们自身。如果让公司等你精通再开发，那你只能被淘汰。但如果毫无准备进入开发，项目质量又无从谈起，而且项目也可能失控。而微精通就是框定一个最小范围，快速熟悉完成任务所涉及的内容。今天我就拿 Vue 移动端开发做一个实验。</p><h2 id="项目初始化"><a href="#项目初始化" class="headerlink" title="项目初始化"></a>项目初始化</h2><h3 id="创建项目"><a href="#创建项目" class="headerlink" title="创建项目"></a>创建项目</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 安装 Vue Cli</span></span><br><span class="line">npm install -g @vue/cli</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建一个项目</span></span><br><span class="line">vue create zhiliao-vant</span><br></pre></td></tr></table></figure><h3 id="配置Prettier"><a href="#配置Prettier" class="headerlink" title="配置Prettier"></a>配置Prettier</h3><p>1、安装依赖</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add prettier eslint-plugin-prettier eslint-config-prettier -D</span><br></pre></td></tr></table></figure><ul><li>prettier: Prettier CLI</li><li>eslint-plugin-prettier: 以 ESLint 插件的形式运行 prettier</li><li>eslint-config-prettier: 关闭所有不必要或可能与 prettier 的规则冲突的 ESLint 规则。一定要放到最后。</li></ul><p>2、配置  <code>.eslintrc.js</code>:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">root</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="attr">env</span>: &#123;</span><br><span class="line">    <span class="attr">node</span>: <span class="literal">true</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">extends</span>: [</span><br><span class="line">    <span class="string">&#x27;plugin:vue/essential&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;@vue/airbnb&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;plugin:prettier/recommended&#x27;</span>,</span><br><span class="line">    <span class="string">&quot;prettier/vue&quot;</span></span><br><span class="line">  ],</span><br><span class="line">  <span class="attr">parserOptions</span>: &#123;</span><br><span class="line">    <span class="attr">parser</span>: <span class="string">&#x27;babel-eslint&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">rules</span>: &#123;</span><br><span class="line">    <span class="string">&#x27;no-console&#x27;</span>: process.<span class="property">env</span>.<span class="property">NODE_ENV</span> === <span class="string">&#x27;production&#x27;</span> ? <span class="string">&#x27;warn&#x27;</span> : <span class="string">&#x27;off&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;no-debugger&#x27;</span>: process.<span class="property">env</span>.<span class="property">NODE_ENV</span> === <span class="string">&#x27;production&#x27;</span> ? <span class="string">&#x27;warn&#x27;</span> : <span class="string">&#x27;off&#x27;</span>,</span><br><span class="line">    <span class="comment">// add your rules</span></span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>3、新建 <code>/.prettierrc.js</code>，并写入如下配置</p><blockquote><p>注意: 要不要加分号的原则是<strong>领导为大，喜好为小</strong></p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">printWidth</span>: <span class="number">120</span>, <span class="comment">// 一行的字符数，如果超过会进行换行，默认为80</span></span><br><span class="line">  <span class="attr">tabWidth</span>: <span class="number">2</span>, <span class="comment">// tab缩进大小,默认为2</span></span><br><span class="line">  <span class="attr">useTabs</span>: <span class="literal">false</span>, <span class="comment">// 使用tab缩进，默认false</span></span><br><span class="line">  <span class="attr">semi</span>: <span class="literal">false</span>, <span class="comment">// 使用分号, 默认true</span></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 行尾逗号,默认none,可选 none|es5|all</span></span><br><span class="line"><span class="comment">   * es5 包括es5中的数组、对象</span></span><br><span class="line"><span class="comment">   * all 包括函数对象等所有可选</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="attr">trailingComma</span>: <span class="string">&#x27;es5&#x27;</span>,</span><br><span class="line">  <span class="attr">singleQuote</span>: <span class="literal">true</span>, <span class="comment">// 使用单引号, 默认false(在jsx中配置无效, 默认都是双引号)</span></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 对象中的空格 默认true</span></span><br><span class="line"><span class="comment">   * true: &#123; foo: bar &#125;</span></span><br><span class="line"><span class="comment">   * false: &#123;foo: bar&#125;</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="attr">bracketSpacing</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * JSX标签闭合位置 默认false</span></span><br><span class="line"><span class="comment">   * false:</span></span><br><span class="line"><span class="comment">   * &lt;div</span></span><br><span class="line"><span class="comment">   *  className=&quot;&quot;</span></span><br><span class="line"><span class="comment">   *  style=&#123;&#123;&#125;&#125;</span></span><br><span class="line"><span class="comment">   * &gt;</span></span><br><span class="line"><span class="comment">   * true:</span></span><br><span class="line"><span class="comment">   * &lt;div</span></span><br><span class="line"><span class="comment">   *  className=&quot;&quot;</span></span><br><span class="line"><span class="comment">   * style=&#123;&#123;&#125;&#125; &gt;</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="attr">jsxBracketSameLine</span>: <span class="literal">false</span>,</span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 箭头函数参数括号 默认avoid 可选 avoid| always</span></span><br><span class="line"><span class="comment">   * avoid 能省略括号的时候就省略 例如x =&gt; x</span></span><br><span class="line"><span class="comment">   * always 总是有括号</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="attr">arrowParens</span>: <span class="string">&#x27;always&#x27;</span>,</span><br><span class="line">  <span class="attr">vueIndentScriptAndStyle</span>: <span class="literal">false</span>, <span class="comment">// vue 文件 script 和 style 标签缩进，默认false</span></span><br><span class="line">  <span class="attr">endOfLine</span>: <span class="string">&#x27;lf&#x27;</span>, <span class="comment">// 强制使用 unix 风格的换行符</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="vscode-配置"><a href="#vscode-配置" class="headerlink" title="vscode 配置"></a>vscode 配置</h3><p>新建 <code>/.vscode/settings.json</code> 并写入以下配置：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;editor.formatOnSave&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span> <span class="comment">// 关闭保存时自动格式化，防止与 eslint 冲突</span></span><br><span class="line">  <span class="attr">&quot;files.eol&quot;</span><span class="punctuation">:</span> <span class="string">&quot;\n&quot;</span><span class="punctuation">,</span> <span class="comment">// 统一默认行尾字符为 LF</span></span><br><span class="line">  <span class="attr">&quot;editor.codeActionsOnSave&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;source.fixAll.eslint&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span> <span class="comment">// 保存时自动修复</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 关闭 vetur 验证功能</span></span><br><span class="line">  <span class="attr">&quot;vetur.validation.script&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;vetur.validation.style&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;vetur.validation.template&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;vetur.format.enable&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="comment">// 禁用 prettier 插件，避免与 eslint 冲突，建议删除 vscode-prettier</span></span><br><span class="line">  <span class="attr">&quot;prettier.disableLanguages&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;javascript&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;javascriptreact&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;typescript&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;typescriptreact&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;vue&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;json&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;jsonc&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><blockquote><p>注意：如果是团队协作的项目，请删除 <code>.gitignire</code> 中的 <code>.vscode</code> ，将配置加入到代码库。</p></blockquote><h3 id="Format初始代码"><a href="#Format初始代码" class="headerlink" title="Format初始代码"></a>Format初始代码</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn lint</span><br></pre></td></tr></table></figure><h2 id="vue-cli"><a href="#vue-cli" class="headerlink" title="@vue&#x2F;cli"></a>@vue&#x2F;cli</h2><h3 id="vuerc"><a href="#vuerc" class="headerlink" title="~&#x2F;.vuerc"></a>~&#x2F;.vuerc</h3><p>被保存的 preset 将会存在用户的 home 目录下一个名为 <code>.vuerc</code> 的 JSON 文件里。如果你想要修改被保存的 <code>preset/</code> 选项，可以编辑这个文件。</p><p>在项目创建的过程中，你也会被提示选择喜欢的包管理器或使用<a href="https://npm.taobao.org/">淘宝 npm 镜像源</a>以更快地安装依赖。这些选择也将会存入 <code>~/.vuerc</code>。下面是我的配置：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;useTaobaoRegistry&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;packageManager&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;presets&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;javascript&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;useConfigFiles&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;plugins&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;@vue/cli-plugin-babel&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;@vue/cli-plugin-router&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;historyMode&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;@vue/cli-plugin-vuex&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;@vue/cli-plugin-eslint&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;config&quot;</span><span class="punctuation">:</span> <span class="string">&quot;airbnb&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;lintOn&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">            <span class="string">&quot;save&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="string">&quot;commit&quot;</span></span><br><span class="line">          <span class="punctuation">]</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;cssPreprocessor&quot;</span><span class="punctuation">:</span> <span class="string">&quot;less&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="git-hooks"><a href="#git-hooks" class="headerlink" title="git hooks"></a>git hooks</h3><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;gitHooks&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;pre-commit&quot;</span><span class="punctuation">:</span> <span class="string">&quot;lint-staged&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;lint-staged&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;*.&#123;js,jsx,vue&#125;&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;vue-cli-service lint&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;git add&quot;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;*.&#123;md,json&#125;&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;prettier --write&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;git add&quot;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h3><h4 id="vue-cli3出现Invalid-Host-header的解决方案"><a href="#vue-cli3出现Invalid-Host-header的解决方案" class="headerlink" title="vue-cli3出现Invalid Host header的解决方案"></a>vue-cli3出现Invalid Host header的解决方案</h4><blockquote><p>参考: <a href="https://blog.csdn.net/guzhao593/article/details/85918869">vue-cli3出现Invalid Host header的解决方案</a></p></blockquote><p><strong>产生原因</strong></p><p>新版的 <code>webpack-dev-server</code> 增加了安全验证，默认检查<code>hostname</code>，如果<code>hostname</code>不是配置内的，将中断访问。</p><p><strong>解决方案</strong></p><p>对<code>vue.config.js</code>进行如下配置：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">devServer</span>: &#123;</span><br><span class="line">    <span class="attr">disableHostCheck</span>: <span class="literal">true</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Vant-UI"><a href="#Vant-UI" class="headerlink" title="Vant UI"></a>Vant UI</h2><h3 id="安装依赖"><a href="#安装依赖" class="headerlink" title="安装依赖"></a>安装依赖</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add vant</span><br></pre></td></tr></table></figure><h3 id="按需引入组件"><a href="#按需引入组件" class="headerlink" title="按需引入组件"></a>按需引入组件</h3><p>安装 <code>babel-import-plugin</code></p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add babel-plugin-import -D</span><br></pre></td></tr></table></figure><p>对于使用 babel7 的用户，可以在 <code>babel.config.js</code> 中配置</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">presets</span>: [<span class="string">&#x27;@vue/cli-plugin-babel/preset&#x27;</span>],</span><br><span class="line">  <span class="attr">plugins</span>: [</span><br><span class="line">    [</span><br><span class="line">      <span class="string">&#x27;import&#x27;</span>,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">libraryName</span>: <span class="string">&#x27;vant&#x27;</span>,</span><br><span class="line">        <span class="attr">libraryDirectory</span>: <span class="string">&#x27;es&#x27;</span>,</span><br><span class="line">        <span class="attr">style</span>: <span class="literal">true</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="string">&#x27;vant&#x27;</span>,</span><br><span class="line">    ],</span><br><span class="line">  ],</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>接着你可以在代码中直接引入 Vant 组件：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Button</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;vant&#x27;</span>;</span><br><span class="line"><span class="title class_">Vue</span>.<span class="title function_">use</span>(<span class="title class_">Button</span>);</span><br></pre></td></tr></table></figure><h3 id="配置基于-Rem-的适配方案"><a href="#配置基于-Rem-的适配方案" class="headerlink" title="配置基于 Rem 的适配方案"></a>配置基于 Rem 的适配方案</h3><p>Vant 中的样式默认使用<code>px</code>作为单位，如果需要使用<code>rem</code>单位，推荐使用以下两个工具：</p><ul><li><a href="https://github.com/cuth/postcss-pxtorem">postcss-pxtorem</a> 是一款 postcss 插件，用于将单位转化为 rem</li><li><a href="https://github.com/amfe/lib-flexible">lib-flexible</a> 用于设置 rem 基准值</li></ul><p>1、安装依赖：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add amfe-flexible</span><br><span class="line">$ yarn add postcss-pxtorem -D</span><br></pre></td></tr></table></figure><p>2、在根目录新建 <code>postcss.config.js</code>，并写入以下配置：</p><blockquote><p>参考: <a href="https://github.com/youzan/vant/issues/1181">设计稿是750px，根元素应该设置75，但是vant转换后好小，要改成35才行</a>、<a href="https://www.cnblogs.com/yimei/p/11319657.html">使用vue vantUi框架 根字体是37.5 和默认根字体75不一致，导致页面组件样式变小</a></p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = <span class="function">(<span class="params">&#123; file &#125;</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> designWidth = file.<span class="property">dirname</span>.<span class="title function_">includes</span>(<span class="string">&#x27;node_modules/vant&#x27;</span>) ? <span class="number">37.5</span> : <span class="number">75</span></span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="attr">plugins</span>: &#123;</span><br><span class="line">      <span class="attr">autoprefixer</span>: &#123;</span><br><span class="line">        <span class="attr">overrideBrowserslist</span>: [<span class="string">&#x27;Android 4.1&#x27;</span>, <span class="string">&#x27;iOS 7.1&#x27;</span>, <span class="string">&#x27;Chrome &gt; 31&#x27;</span>, <span class="string">&#x27;ff &gt; 31&#x27;</span>, <span class="string">&#x27;ie &gt;= 8&#x27;</span>],</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="string">&#x27;postcss-pxtorem&#x27;</span>: &#123;</span><br><span class="line">        <span class="attr">rootValue</span>: designWidth,</span><br><span class="line">        <span class="attr">propList</span>: [<span class="string">&#x27;*&#x27;</span>, <span class="string">&#x27;!border&#x27;</span>],</span><br><span class="line">        <span class="attr">selectorBlackList</span>: [<span class="string">&#x27;.ignore&#x27;</span>, <span class="string">&#x27;.hairlines&#x27;</span>],</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>注意: 你可以使用 <code>Px</code> 或 <code>PX</code> 来让 <code>postcss-pxtorem</code> 忽略转换，而且这样浏览器也能识别。</p></blockquote><p>3、在 <code>src/main.js</code> 中引入 <code>amfe-flexible</code>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">import &#x27;amfe-flexible&#x27;</span><br><span class="line">...</span><br></pre></td></tr></table></figure><h3 id="底部安全区适配"><a href="#底部安全区适配" class="headerlink" title="底部安全区适配"></a>底部安全区适配</h3><p>iPhone X 等机型底部存在底部指示条，指示条的操作区域与页面底部存在重合，容易导致用户误操作，因此我们需要针对这些机型进行底部安全区适配。Vant 中部分组件提供了<code>safe-area-inset-bottom</code>属性，设置该属性后，即可在对应的机型上开启适配，如下示例：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 在 head 标签中添加 meta 标签，并设置 viewport-fit=cover 值 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">meta</span></span></span><br><span class="line"><span class="tag">  <span class="attr">name</span>=<span class="string">&quot;viewport&quot;</span></span></span><br><span class="line"><span class="tag">  <span class="attr">content</span>=<span class="string">&quot;width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, viewport-fit=cover&quot;</span></span></span><br><span class="line"><span class="tag">/&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!-- 开启 safe-area-inset-bottom 属性 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">van-number-keyboard</span> <span class="attr">safe-area-inset-bottom</span> /&gt;</span></span><br></pre></td></tr></table></figure><h3 id="配置自定义主题色方案"><a href="#配置自定义主题色方案" class="headerlink" title="配置自定义主题色方案"></a>配置自定义主题色方案</h3><h4 id="1、按需引入样式"><a href="#1、按需引入样式" class="headerlink" title="1、按需引入样式"></a>1、按需引入样式</h4><p>在 <code>babel.config.js</code> 中配置按需引入样式源文件:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">plugins</span>: [</span><br><span class="line">    [</span><br><span class="line">      <span class="string">&#x27;import&#x27;</span>,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">libraryName</span>: <span class="string">&#x27;vant&#x27;</span>,</span><br><span class="line">        <span class="attr">libraryDirectory</span>: <span class="string">&#x27;es&#x27;</span>,</span><br><span class="line">        <span class="comment">// 指定样式路径</span></span><br><span class="line">        <span class="attr">style</span>: <span class="function">(<span class="params">name</span>) =&gt;</span> <span class="string">`<span class="subst">$&#123;name&#125;</span>/style/less`</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="string">&#x27;vant&#x27;</span>,</span><br><span class="line">    ],</span><br><span class="line">  ],</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h4 id="2、-修改样式变量"><a href="#2、-修改样式变量" class="headerlink" title="2、 修改样式变量"></a>2、 修改样式变量</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">yarn add less less-loader</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// vue.config.js</span></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">css</span>: &#123;</span><br><span class="line">    <span class="attr">loaderOptions</span>: &#123;</span><br><span class="line">      <span class="attr">less</span>: &#123;</span><br><span class="line">        <span class="attr">modifyVars</span>: &#123;</span><br><span class="line">          <span class="comment">// 直接覆盖变量</span></span><br><span class="line">          <span class="string">&#x27;text-color&#x27;</span>: <span class="string">&#x27;#111&#x27;</span>,</span><br><span class="line">          <span class="string">&#x27;border-color&#x27;</span>: <span class="string">&#x27;#eee&#x27;</span>,</span><br><span class="line">          <span class="comment">// 或者可以通过 less 文件覆盖（文件路径为绝对路径）</span></span><br><span class="line">          <span class="attr">hack</span>: <span class="string">&#x27;true; @import &quot;your-less-file-path.less&quot;;&#x27;</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><blockquote><p>Vant 使用了 <a href="http://lesscss.org/">Less</a> 对样式进行预处理，并内置了一些样式变量，下面是一些基本的样式变量，所有可用的颜色变量请参考 <a href="https://github.com/youzan/vant/blob/dev/src/style/var.less">配置文件</a>。</p></blockquote><h3 id="配置基于-Viewport-的适配方案（推荐）"><a href="#配置基于-Viewport-的适配方案（推荐）" class="headerlink" title="配置基于 Viewport 的适配方案（推荐）"></a>配置基于 Viewport 的适配方案（推荐）</h3><p>该方案和<strong>配置基于 Rem 的适配方案</strong>是互斥的，请二选一。</p><blockquote><p>参考: <a href="https://my.oschina.net/u/4382386/blog/4290707">移动端布局之postcss-px-to-viewport（兼容vant）</a>、<a href="https://www.cnblogs.com/cnloop/p/9697229.html">vue —— 利用 viewport 进行适配</a></p></blockquote><p>1、安装 postcss-px-to-viewport</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">yarn add postcss-px-to-viewport -D</span></span><br></pre></td></tr></table></figure><p>2、配置<code> postcss.config.js</code> 文件</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = <span class="function">(<span class="params">&#123; file &#125;</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> designWidth = file.<span class="property">dirname</span>.<span class="title function_">includes</span>(<span class="string">&#x27;node_modules/vant&#x27;</span>) ? <span class="number">375</span> : <span class="number">750</span>;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="attr">plugins</span>: &#123;</span><br><span class="line">      <span class="attr">autoprefixer</span>: &#123;</span><br><span class="line">        <span class="comment">// 用来给不同的浏览器自动添加相应前缀，如-webkit-，-moz-等等</span></span><br><span class="line">        <span class="attr">overrideBrowserslist</span>: [</span><br><span class="line">          <span class="string">&#x27;Android 4.1&#x27;</span>,</span><br><span class="line">          <span class="string">&#x27;iOS 7.1&#x27;</span>,</span><br><span class="line">          <span class="string">&#x27;Chrome &gt; 31&#x27;</span>,</span><br><span class="line">          <span class="string">&#x27;ff &gt; 31&#x27;</span>,</span><br><span class="line">          <span class="string">&#x27;ie &gt;= 8&#x27;</span>,</span><br><span class="line">        ],</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="string">&quot;postcss-px-to-viewport&quot;</span>: &#123;</span><br><span class="line">      <span class="attr">unitToConvert</span>: <span class="string">&quot;px&quot;</span>, <span class="comment">// 要转化的单位</span></span><br><span class="line">        <span class="attr">viewportWidth</span>: designWidth, <span class="comment">// UI设计稿的宽度</span></span><br><span class="line">        <span class="attr">unitPrecision</span>: <span class="number">6</span>, <span class="comment">// 转换后的精度，即小数点位数</span></span><br><span class="line">        <span class="attr">propList</span>: [<span class="string">&quot;*&quot;</span>,<span class="string">&quot;!border&quot;</span>], <span class="comment">// 指定转换的css属性的单位，*代表全部css属性的单位都进行转换</span></span><br><span class="line">        <span class="attr">viewportUnit</span>: <span class="string">&quot;vw&quot;</span>, <span class="comment">// 指定需要转换成的视窗单位，默认vw</span></span><br><span class="line">        <span class="attr">fontViewportUnit</span>: <span class="string">&quot;vw&quot;</span>, <span class="comment">// 指定字体需要转换成的视窗单位，默认vw</span></span><br><span class="line">        <span class="attr">selectorBlackList</span>: [<span class="string">&#x27;.ignore&#x27;</span>, <span class="string">&#x27;.hairlines&#x27;</span>], <span class="comment">// 指定不转换为视窗单位的类名</span></span><br><span class="line">        <span class="attr">minPixelValue</span>: <span class="number">1</span>, <span class="comment">// 默认值1，小于或等于1px则不进行转换</span></span><br><span class="line">        <span class="attr">landscape</span>: <span class="literal">false</span> <span class="comment">// 是否处理横屏情况</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><code>propList</code>: 当有些属性的单位我们不希望转换的时候，可以添加在数组后面，并在前面加上<code>!</code>号，如<code>propList: [&quot;*&quot;,&quot;!border&quot;]</code>,这表示：所有css属性的属性的单位都进行转化，除了<code>border</code>的</li><li><code>selectorBlackList</code>：转换的黑名单，在黑名单里面的我们可以写入字符串，只要类名包含有这个字符串，就不会被匹配。比如<code>selectorBlackList: [&#39;wrap&#39;]</code>,它表示形如<code>wrap</code>,<code>my-wrap</code>,<code>wrapper</code>这样的类名的单位，都不会被转换</li></ul><h2 id="vue"><a href="#vue" class="headerlink" title="vue"></a>vue</h2><h3 id="vue中style-scope深度访问新方式-v-deep"><a href="#vue中style-scope深度访问新方式-v-deep" class="headerlink" title="vue中style scope深度访问新方式(::v-deep)"></a>vue中style scope深度访问新方式(<code>::v-deep</code>)</h3><blockquote><p>参考： <a href="https://segmentfault.com/a/1190000021576348">vue中style scope深度访问新方式(::v-deep)</a></p></blockquote><p>由于使用 scoped 后，父组件的样式将不会渗透到子组件中。官方引入了 <a href="https://vue-loader.vuejs.org/zh/guide/scoped-css.html#%E6%B7%B1%E5%BA%A6%E4%BD%9C%E7%94%A8%E9%80%89%E6%8B%A9%E5%99%A8">深度作用选择器</a>，来解决这个问题。记得之前使用的是 <code>/deep/</code>，据说这个属性有兼容问题，现在引入了新方式：<code>::v-deep</code>:</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-id">#editDoctorAdvice</span> &#123;</span><br><span class="line">  <span class="selector-class">.topSearch</span> &#123;</span><br><span class="line">    <span class="attribute">float</span>: left;</span><br><span class="line">    <span class="attribute">margin-right</span>: <span class="number">10px</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  &amp;::v-deep .el-input__inner &#123;</span><br><span class="line">    <span class="attribute">padding-right</span>: <span class="number">6px</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="selector-class">.dateTimeClass</span> &#123;</span><br><span class="line">    <span class="attribute">width</span>: <span class="number">150px</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><ul><li><a href="https://juejin.im/post/5c9f22876fb9a05e425556ed">Vue入门指南(快速上手vue)</a></li><li><a href="https://juejin.im/post/5a0c191f6fb9a04514639419">vue快速入门的三个小实例</a></li><li><a href="https://github.com/chimurai/http-proxy-middleware">http-proxy-middleware</a></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/4617061a3c2c/</id>
    <link href="https://blog.zisheng.pro/posts/4617061a3c2c/"/>
    <published>2020-07-13T07:37:03.000Z</published>
    <summary>精通任何技能都是需要时间的，但是往往工作中是不会给你这个时间的，尤其是开发工作。新技术层出不穷，每个项目都可能技术栈不一样。</summary>
    <title>微精通之 Vue 移动端开发</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="vant" scheme="https://blog.zisheng.pro/tags/vant/"/>
    <category term="wml" scheme="https://blog.zisheng.pro/tags/wml/"/>
    <category term="模块调试" scheme="https://blog.zisheng.pro/tags/%E6%A8%A1%E5%9D%97%E8%B0%83%E8%AF%95/"/>
    <category term="组件调试" scheme="https://blog.zisheng.pro/tags/%E7%BB%84%E4%BB%B6%E8%B0%83%E8%AF%95/"/>
    <category term="前端组件化" scheme="https://blog.zisheng.pro/tags/%E5%89%8D%E7%AB%AF%E7%BB%84%E4%BB%B6%E5%8C%96/"/>
    <content>
      <![CDATA[<p>前端模块开发已经成为了开发标准。模块开发带来的一大便利就是让我们可以跨页面甚至跨应用复用代码。当我们跨应用复用模块时，就需要借助 npm 把模块发布出去供各个系统调用。今天介绍的 wml 是一个进行模块调试的利器。</p><h2 id="wml-是什么"><a href="#wml-是什么" class="headerlink" title="wml 是什么"></a>wml 是什么</h2><p>wml 侦听某个文件夹中的更改（使用<a href="https://facebook.github.io/watchman/">Watchman</a>），然后将更改的文件复制到另一个文件夹中。</p><p>wml 是一个 CLI 工具，其工作方式与ln -s非常相似。首先，使用 <code>wml add</code> 命令设置链接，然后运行wml服务（<code>wml start</code>）开始监听。仅此而已！</p><h2 id="为什么使用-wml"><a href="#为什么使用-wml" class="headerlink" title="为什么使用 wml"></a>为什么使用 wml</h2><p>让我们面对现实吧，有时符号链接还不够。Github中有超过 <a href="https://github.com/search?utf8=%E2%9C%93&q=support+for+symlinks&type=Issues">1万个</a> 关于 <strong>“support for symlinks”</strong>。</p><p>到目前为止，我遇到的两个例子是: React Native 的包管理器<a href="https://github.com/facebook/react-native/issues/637">缺失对 symlinks 的支持</a> 和Webpack的 <a href="http://webpack.github.io/docs/troubleshooting.html#npm-linked-modules-doesn-t-find-their-dependencies">inability to find linked modules dependencies</a>。很多人的办法就是直接修改 <strong>node_modules</strong> 下的文件夹，但是这会造成很多问题：</p><ol><li>当同时有两个项目依赖你的模块时，就搞砸了</li><li>当不小心执行了 <code>npm install</code>，就又搞砸了</li><li>总之就是感觉这样不对</li></ol><p>wml 使用 Facebook 的超快 Watchman 来监视源文件夹中的更改，并将它们（并且仅复制）复制到目标文件夹中。</p><h2 id="安装-watchman"><a href="#安装-watchman" class="headerlink" title="安装 watchman"></a>安装 watchman</h2><ul><li><a href="https://facebook.github.io/watchman/docs/install">watchmamn Installation</a></li><li><a href="https://stackoverflow.com/questions/38451764/how-to-install-watchman-on-windows-win10">How to install Watchman on Windows (win10)?</a></li></ul><p>windows 下为了让 <code>wml start</code> 能够工作需要执行以下命令：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># npmGlobalPrefix 可以通过 `npm prefix -g` 获取</span></span><br><span class="line">$ watchman watch <span class="variable">$&#123;npmGlobalPrefix&#125;</span>\node_modules\wml\src</span><br><span class="line"><span class="comment"># 一定要重启电脑让配置生效！</span></span><br></pre></td></tr></table></figure><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ npm install -g wml</span><br></pre></td></tr></table></figure><h3 id="wml-命令"><a href="#wml-命令" class="headerlink" title="wml 命令"></a>wml 命令</h3><ul><li><code>wml add|a &lt;src&gt; &lt;dest&gt;</code>: 添加链接</li><li><code>wml start|s</code>: 开启 wml</li><li><code>wml list|ls</code>: 显示所有的链接</li><li><code>wml rm &lt;linkId&gt;</code>: 删除一个链接，传递 <code>all</code> 可以删除所有链接</li><li><code>wml enable|e</code>: 启用一个链接，传递 <code>all</code> 一键启用所有链接</li><li><code>wml disable|d</code>: 禁用一个链接，传递 <code>all</code> 一键禁用所有链接</li><li><code>watchman watch-del-all</code>: 修改 <code>.watchmanconfig</code> 之后需要清空 watchman 缓存</li></ul><h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><h3 id="npm-scripts"><a href="#npm-scripts" class="headerlink" title="npm scripts"></a>npm scripts</h3><p>这是我的习惯，写脚本能提高工作效率：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;dev&quot;</span><span class="punctuation">:</span> <span class="string">&quot;wml start&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="comment">// yarn wml:add &lt;dest&gt;</span></span><br><span class="line">    <span class="attr">&quot;wml:add&quot;</span><span class="punctuation">:</span> <span class="string">&quot;wml add ./&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="comment">// 修改 `.watchmanconfig` 之后需要清空 watchman 缓存</span></span><br><span class="line">    <span class="attr">&quot;wml:clean&quot;</span><span class="punctuation">:</span><span class="string">&quot;watchman watch-del-all&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="demo-在模块中"><a href="#demo-在模块中" class="headerlink" title="demo 在模块中"></a>demo 在模块中</h3><p>你可能为了演示你的模块，而把演示项目放在模块项目中，这也很常见的。我们需要做一下配置：</p><p>1、修改 package.json</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;zhiliao&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;dev&quot;</span><span class="punctuation">:</span> <span class="string">&quot;wml start&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;wml:add&quot;</span><span class="punctuation">:</span> <span class="string">&quot;wml add ./ ./Example/node_modules/zhiliao/&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="comment">// 修改 `.watchmanconfig` 之后需要清空 watchman 缓存</span></span><br><span class="line">    <span class="attr">&quot;wml:clean&quot;</span><span class="punctuation">:</span><span class="string">&quot;watchman watch-del-all&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>2、配置 .watchmanconfig 文件把 Example 文件夹忽略掉</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;ignore_dirs&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;.git&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;node_modules&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;yarn-error.log&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;yarn.lock&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;README.md&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;Example&quot;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/c31611afec6b/</id>
    <link href="https://blog.zisheng.pro/posts/c31611afec6b/"/>
    <published>2020-07-10T08:15:07.000Z</published>
    <summary>前端模块开发已经成为了开发标准。模块开发带来的一大便利就是让我们可以跨页面甚至跨应用复用代码。当我们跨应用复用模块时，就需要借助 npm 把模块发布出去供各个系统调用。今天介绍的 wml 是一个进行模块调试的利器。</summary>
    <title>使用 wml 进行模块调试</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React" scheme="https://blog.zisheng.pro/tags/React/"/>
    <category term="UmiJS" scheme="https://blog.zisheng.pro/tags/UmiJS/"/>
    <content>
      <![CDATA[<p>鉴于 UmiJS 是重度的封装了很多工具和功能，对于刚接触的人形成了一层技术壁垒（大佬请忽略这句话）。所以我总结了使用UmiJS 开发中遇到的坑和技巧。</p><h2 id="本地开发-umi-dev-时关闭-mock"><a href="#本地开发-umi-dev-时关闭-mock" class="headerlink" title="本地开发 umi dev 时关闭 mock"></a>本地开发 umi dev 时关闭 mock</h2><blockquote><p>参考: <a href="https://github.com/umijs/umi/issues/486">希望本地开发 umi dev 时可以关闭 mock</a></p></blockquote><p>方案一（推荐）：在 npm scripts 中加入以下指令：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;dev&quot;</span><span class="punctuation">:</span> <span class="string">&quot;MOCK=none umi dev&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>方案二：在 <code>.env</code> 文件里 设置 <code>MOCK=none</code> 也可以关闭</p><h2 id="jsx无法转到定义处"><a href="#jsx无法转到定义处" class="headerlink" title="jsx无法转到定义处"></a>jsx无法转到定义处</h2><p>在 <code>jsconfig.json</code> 文件中进行如下配置：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;compilerOptions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;jsx&quot;</span><span class="punctuation">:</span> <span class="string">&quot;react&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="在umi中如何访问静态资源"><a href="#在umi中如何访问静态资源" class="headerlink" title="在umi中如何访问静态资源"></a>在umi中如何访问静态资源</h2><p>在umi框架中，图片等静态资源主要放到三个地方：</p><ol><li>在 <code>/public</code> 目录下，一般放共享资源。</li><li>在 <code>/src/assets/</code> 目录下，一般放全局静态资源。</li><li>在 <code>/src/pages/</code> 里的各个页面目录下，放在这里的好处是更符合组件化开发的思想，便于拷贝复用。</li></ol><p>由于静态资源会受 <code>context.config.publicPath</code> 的影响，所以在 <code>document.ejs</code> 中应该这样引入比较安全：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">&quot;text/javascript&quot;</span> <span class="attr">src</span>=<span class="string">&quot;&lt;%= context.config.publicPath %&gt;ol.js&quot;</span> /&gt;</span></span><br></pre></td></tr></table></figure><h2 id="如何访问静态图片"><a href="#如何访问静态图片" class="headerlink" title="如何访问静态图片"></a>如何访问静态图片</h2><p>1、如果在&#x2F;public目录下的静态图片，可以直接输入绝对路径，假设&#x2F;public&#x2F;yay.jpg,访问方式如下：</p><p><code>&lt;img src=&quot;/yay.jpg&quot; /&gt;</code></p><p>注意：以上必须构建后在dist中才能看到。</p><p>2、在 <code>/src/assets</code> 和 <code>/src/pages/</code> 目录下的图片，不能通过输入绝对路径访问，必须先 <code>import</code> 导入，才能访问。或者 <code>require</code> 导入。比如 <code>/src/assets/yay.jpg</code> 需：</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> yayImg <span class="keyword">from</span> <span class="string">&#x27;/src/assets/yay.jpg&#x27;</span>;</span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">&#123;yayImg&#125;</span> /&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">&#123;require(</span>&#x27;/<span class="attr">src</span>/<span class="attr">assets</span>/<span class="attr">yay.jpg</span>&#x27;)&#125;</span></span></span><br></pre></td></tr></table></figure><p>为什么会这样呢？主要是因为构建时，<code>/public</code> 目录下的文件会原样复制到 <code>/dist/</code> 目录下，而 <code>/src/assets/</code> 和 <code>/src/pages/</code> 目录下的文件会被改名并复制到 <code>/dist/</code> 下。</p><h2 id="react-router三种传参方式"><a href="#react-router三种传参方式" class="headerlink" title="react-router三种传参方式"></a>react-router三种传参方式</h2><blockquote><p>参考: <a href="https://www.kelede.win/posts/react-router%E4%B8%89%E7%A7%8D%E4%BC%A0%E5%8F%82%E6%96%B9%E5%BC%8F/">react-router三种传参方式</a></p></blockquote><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Component</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span></span><br><span class="line"><span class="keyword">import</span> router <span class="keyword">from</span> <span class="string">&#x27;umi/router&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">RouterDemo</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">onOk</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    router.<span class="title function_">push</span>(&#123;</span><br><span class="line">      <span class="attr">pathname</span>: <span class="string">&#x27;test/router&#x27;</span>,</span><br><span class="line">      <span class="comment">// 点击之后，页面会跳转且地址上会跟上query的参数，?id=1&amp;code=123</span></span><br><span class="line">      <span class="comment">// 获取方式传值内容的方式: this.props.location.query</span></span><br><span class="line">      <span class="attr">query</span>: &#123;</span><br><span class="line">        <span class="attr">id</span>: <span class="string">&#x27;1&#x27;</span>,</span><br><span class="line">        <span class="attr">code</span>: <span class="string">&#x27;123&#x27;</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="comment">// 点击之后，页面会跳转</span></span><br><span class="line">      <span class="comment">// 通过this.props.location.params可以获得params的值</span></span><br><span class="line">      <span class="comment">// params可以为其他名字，如text、nihao等，不一定是params</span></span><br><span class="line">      <span class="comment">// 刷新页面后，params的值会丢失。</span></span><br><span class="line">      <span class="attr">params</span>: &#123;</span><br><span class="line">        <span class="attr">d</span>: <span class="string">&#x27;1&#x27;</span>,</span><br><span class="line">        <span class="attr">code</span>: <span class="string">&#x27;123&#x27;</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="comment">// 使用state传值和params传值一样，都不会再url中显示出来</span></span><br><span class="line">      <span class="comment">// state传值与params传值的区别是state传值刷新页面值还在，而使用params传值刷新后值不没有了。</span></span><br><span class="line">      <span class="attr">state</span>: &#123;</span><br><span class="line">        <span class="attr">d</span>: <span class="string">&#x27;1&#x27;</span>,</span><br><span class="line">        <span class="attr">code</span>: <span class="string">&#x27;123&#x27;</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">onClick</span>=<span class="string">&#123;this.onOk&#125;</span>&gt;</span>点击测试<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="修改浏览器上方图标"><a href="#修改浏览器上方图标" class="headerlink" title="修改浏览器上方图标"></a>修改浏览器上方图标</h2><blockquote><p>参考: <a href="https://v2.umijs.org/zh/guide/html-template.html#%E9%85%8D%E7%BD%AE%E6%A8%A1%E6%9D%BF">HTML 配置模板</a></p></blockquote><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 图片在 /public 下 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">&quot;icon&quot;</span> <span class="attr">type</span>=<span class="string">&quot;image/x-icon&quot;</span> <span class="attr">href</span>=<span class="string">&quot;&lt;%= context.publicPath %&gt;favicon.png&quot;</span> /&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 图片在 /src/assets/ 下--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">&quot;icon&quot;</span> <span class="attr">type</span>=<span class="string">&quot;image/x-icon&quot;</span> <span class="attr">href</span>=<span class="string">&quot;&lt;%= context.publicPath %&gt;static/favicon.png&quot;</span> /&gt;</span></span><br></pre></td></tr></table></figure><h2 id="支持-ie11"><a href="#支持-ie11" class="headerlink" title="支持 ie11"></a>支持 ie11</h2><blockquote><p>参考: <a href="https://github.com/umijs/umi/issues/1394">ie11兼容问题</a></p></blockquote><p>配置浏览器最低版本，会自动引入 polyfill 和做语法转换，配置的 targets 会和合并到默认值，所以不需要重复配置:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// umi默认兼容最低浏览器版本</span></span><br><span class="line"><span class="comment">// Default: &#123; chrome: 49, firefox: 45, safari: 10, edge: 13, ios: 10 &#125;</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  <span class="attr">targets</span>: &#123;</span><br><span class="line">    <span class="attr">ie</span>: <span class="number">11</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="编译-node-modules-下的包"><a href="#编译-node-modules-下的包" class="headerlink" title="编译 node_modules 下的包"></a>编译 node_modules 下的包</h2><h3 id="UmiJS-2-x"><a href="#UmiJS-2-x" class="headerlink" title="UmiJS 2.x"></a>UmiJS 2.x</h3><blockquote><p>参考: <a href="https://github.com/umijs/umi/issues/2117#issuecomment-539982434">How to configure extraBabelIncludes</a></p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">&#x27;path&#x27;</span>);</span><br><span class="line">&#123;</span><br><span class="line">  <span class="attr">extraBabelIncludes</span>: [path.<span class="title function_">resolve</span>(__dirname, <span class="string">&#x27;node_modules/&lt;package_name&gt;&#x27;</span>)],</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="UmiJS-3-1"><a href="#UmiJS-3-1" class="headerlink" title="UmiJS 3.1+"></a>UmiJS 3.1+</h3><blockquote><p>参考: <a href="https://umijs.org/zh-CN/config#nodemodulestransform-31">nodeModulesTransform</a>、<a href="https://umijs.org/zh-CN/guide/boost-compile-speed">如何做编译提速</a></p></blockquote><p>UmiJS 3 删除了 <code>extraBabelIncludes</code> 和 <code>es5ImcompatibleVersions</code>，<code>node_modules</code> 也走 babel 编译后就没有意义了，无需配置</p><p>UmiJS 3 默认编译 <code>node_modules</code> 下的文件，带来一些收益的同时，也增加了额外的编译时间。如果不希望 <code>node_modules</code> 下的文件走 babel 编译，可通过以下配置减少 40% 到 60% 的编译时间。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  <span class="attr">nodeModulesTransform</span>: &#123;</span><br><span class="line">    <span class="attr">type</span>: <span class="string">&#x27;none&#x27;</span>,</span><br><span class="line">    <span class="attr">exclude</span>: [], <span class="comment">// 忽略的依赖库，包名，暂不支持绝对路径；可通过 exclude 配置添加额外需要编译的</span></span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="并行运行任务"><a href="#并行运行任务" class="headerlink" title="并行运行任务"></a>并行运行任务</h2><h3 id="call"><a href="#call" class="headerlink" title="call"></a>call</h3><blockquote><p>参考: <a href="https://github.com/dvajs/dva/issues/756#issuecomment-317770608">求教多个异步的请求问题？</a>、<a href="https://redux-saga-in-chinese.js.org/docs/advanced/RunningTasksInParallel.html">同时执行多个任务</a></p></blockquote><p><code>yield</code> 指令可以很简单的将异步控制流以同步的写法表现出来，但与此同时我们将也会需要同时执行多个任务，我们不能直接这样写：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 错误写法，effects 将按照顺序执行</span></span><br><span class="line"><span class="keyword">const</span> users = <span class="keyword">yield</span> <span class="title function_">call</span>(fetch, <span class="string">&#x27;/users&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> repos = <span class="keyword">yield</span> <span class="title function_">call</span>(fetch, <span class="string">&#x27;/repos&#x27;</span>)</span><br></pre></td></tr></table></figure><p>由于第二个 effect 将会在第一个 call 执行完毕才开始。所以我们需要这样写：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 正确写法, effects 将会同步执行</span></span><br><span class="line">*<span class="title function_">effects</span>(<span class="params">&#123;&#125;, &#123; all, call &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [users, repos] = <span class="keyword">yield</span> <span class="title function_">all</span>([</span><br><span class="line">    <span class="title function_">call</span>(fetch, <span class="string">&#x27;/users&#x27;</span>),</span><br><span class="line">    <span class="title function_">call</span>(fetch, <span class="string">&#x27;/repos&#x27;</span>)</span><br><span class="line">  ])</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当我们需要 <code>yield</code> 一个包含 effects 的数组， generator 会被阻塞直到所有的 effects 都执行完毕，或者当一个 effect 被拒绝 （就像 <code>Promise.all</code> 的行为）。</p><h3 id="put"><a href="#put" class="headerlink" title="put"></a>put</h3><blockquote><p>参考: <a href="https://github.com/dvajs/dva/issues/2094">yield all中放put而出现的问题</a></p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">*<span class="title function_">effects</span>(<span class="params">&#123;&#125;, &#123; all, call &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [users, repos] = <span class="keyword">yield</span> <span class="title function_">all</span>([</span><br><span class="line">    <span class="keyword">yield</span> <span class="title function_">put</span>(&#123; <span class="attr">type</span>: <span class="string">&#x27;getUsers&#x27;</span> &#125;),</span><br><span class="line">    <span class="keyword">yield</span> <span class="title function_">put</span>(&#123; <span class="attr">type</span>: <span class="string">&#x27;getRepos&#x27;</span> &#125;)</span><br><span class="line">  ])</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>或者使用 <code>put.resolve</code>:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">*<span class="title function_">effects</span>(<span class="params">&#123;&#125;, &#123; all, call &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [users, repos] = <span class="keyword">yield</span> <span class="title function_">all</span>([</span><br><span class="line">    put.<span class="title function_">resolve</span>(&#123; <span class="attr">type</span>: <span class="string">&#x27;getUsers&#x27;</span> &#125;),</span><br><span class="line">    put.<span class="title function_">resolve</span>(&#123; <span class="attr">type</span>: <span class="string">&#x27;getRepos&#x27;</span> &#125;)</span><br><span class="line">  ])</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="局部覆盖antd-样式"><a href="#局部覆盖antd-样式" class="headerlink" title="局部覆盖antd 样式"></a>局部覆盖antd 样式</h2><p>由于业务的个性化需求，我们经常会遇到需要覆盖组件样式的情况，这里举个简单的例子。</p><p>antd Select 在多选状态下，默认会展示所有选中项，这里我们给它加一个限制高度，超过此高度就出滚动条。</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&lt;<span class="title class_">Select</span></span><br><span class="line">  mode=<span class="string">&quot;multiple&quot;</span></span><br><span class="line">  style=&#123;&#123; <span class="attr">width</span>: <span class="number">300</span> &#125;&#125;</span><br><span class="line">  placeholder=<span class="string">&quot;Please select&quot;</span></span><br><span class="line">  className=&#123;styles.<span class="property">customSelect</span>&#125;</span><br><span class="line">&gt;</span><br><span class="line">  &#123;children&#125;</span><br><span class="line">&lt;/<span class="title class_">Select</span>&gt;</span><br></pre></td></tr></table></figure><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.customSelect</span> &#123;</span><br><span class="line">  :global &#123;</span><br><span class="line">    <span class="selector-class">.ant-select-selection</span> &#123;</span><br><span class="line">      <span class="attribute">max-height</span>: <span class="number">51px</span>;</span><br><span class="line">      <span class="attribute">overflow</span>: auto;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>方法很简单，有两点需要注意：</p><ul><li>引入的 antd 组件类名没有被 CSS Modules 转化，所以被覆盖的类名 <code>.ant-select-selection</code> 必须放到 <code>:global</code> 中。</li><li>因为覆盖是全局性的。为了防止对其他 Select 组件造成影响，所以需要包裹额外的 className 限制样式的生效范围。</li></ul><h2 id="优化包大小"><a href="#优化包大小" class="headerlink" title="优化包大小"></a>优化包大小</h2><blockquote><p>参考: <a href="https://juejin.im/post/5ef8581ce51d4534c14d9a5f">H5 分包实现首屏加载时间优化</a>、<a href="https://juejin.im/post/5c08fe7d6fb9a04a0d56a702">webapck4 玄妙的 SplitChunks Plugin</a>、<a href="https://github.com/umijs/umi/issues/3535#issuecomment-617590579">请问如何单独打包组件</a></p></blockquote><h3 id="UmiJS-2-x-1"><a href="#UmiJS-2-x-1" class="headerlink" title="UmiJS 2.x"></a>UmiJS 2.x</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="comment">// 忽略 moment 的 locale 文件，用于减少尺寸。</span></span><br><span class="line">  <span class="comment">// https://v2.umijs.org/zh/config/#ignoremomentlocale</span></span><br><span class="line">  <span class="attr">ignoreMomentLocale</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="comment">// 配置是否开启 treeShaking，默认关闭。</span></span><br><span class="line">  <span class="comment">// https://v2.umijs.org/zh/config/#treeshaking</span></span><br><span class="line">  <span class="attr">treeShaking</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="comment">// 通过 [webpack-chain](https://github.com/mozilla-neutrino/webpack-chain) 的 API 扩展或修改 webpack 配置。</span></span><br><span class="line">  <span class="comment">// https://v2.umijs.org/zh/config/#chainwebpack</span></span><br><span class="line">  <span class="title function_">chainWebpack</span>(<span class="params">config</span>) &#123;</span><br><span class="line">    config.<span class="property">optimization</span>.<span class="title function_">splitChunks</span>(&#123;</span><br><span class="line">      <span class="attr">chunks</span>: <span class="string">&#x27;all&#x27;</span>,</span><br><span class="line">      <span class="attr">automaticNameDelimiter</span>: <span class="string">&#x27;～&#x27;</span>,</span><br><span class="line">      <span class="attr">name</span>: <span class="literal">true</span>,</span><br><span class="line">      <span class="attr">minSize</span>: <span class="number">30000</span>,</span><br><span class="line">      <span class="attr">minChunks</span>: <span class="number">1</span>,</span><br><span class="line">      <span class="attr">cacheGroups</span>: &#123;</span><br><span class="line">        <span class="attr">echarts</span>: &#123;</span><br><span class="line">          <span class="attr">name</span>: <span class="string">&#x27;echarts&#x27;</span>,</span><br><span class="line">          <span class="attr">test</span>: <span class="regexp">/[\\/]node_modules[\\/](echarts)[\\/]/</span>,</span><br><span class="line">          <span class="attr">priority</span>: -<span class="number">9</span>,</span><br><span class="line">          <span class="attr">enforce</span>: <span class="literal">true</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="attr">antd</span>: &#123;</span><br><span class="line">          <span class="attr">name</span>: <span class="string">&#x27;antd&#x27;</span>,</span><br><span class="line">          <span class="attr">test</span>: <span class="regexp">/[\\/]node_modules[\\/](@ant-design|antd|antd-mobile)[\\/]/</span>,</span><br><span class="line">          <span class="attr">priority</span>: -<span class="number">10</span>,</span><br><span class="line">          <span class="attr">enforce</span>: <span class="literal">true</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="attr">vendors</span>: &#123;</span><br><span class="line">          <span class="attr">name</span>: <span class="string">&#x27;vendors&#x27;</span>,</span><br><span class="line">          <span class="attr">test</span>: <span class="regexp">/[\\/]node_modules[\\/]/</span>,</span><br><span class="line">          <span class="attr">priority</span>: -<span class="number">11</span>,</span><br><span class="line">          <span class="attr">enforce</span>: <span class="literal">true</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">plugins</span>: [</span><br><span class="line">    [</span><br><span class="line">      <span class="comment">// 这是官方封装的一个插件集，包含 18 个常用的进阶功能。</span></span><br><span class="line">      <span class="comment">// https://v2.umijs.org/zh/plugin/umi-plugin-react.html#%E5%AE%89%E8%A3%85</span></span><br><span class="line">      <span class="string">&#x27;umi-plugin-react&#x27;</span>,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="comment">// 默认是 [&#x27;umi&#x27;]，可修改，比如做了 vendors 依赖提取之后，会需要在 umi.js 之前加载 vendors.js</span></span><br><span class="line">        <span class="comment">// https://v2.umijs.org/zh/plugin/umi-plugin-react.html#chunks</span></span><br><span class="line">        <span class="attr">chunks</span>: [<span class="string">&#x27;vendors&#x27;</span>, <span class="string">&#x27;antd&#x27;</span>, <span class="string">&#x27;echarts&#x27;</span>, <span class="string">&#x27;umi&#x27;</span>],</span><br><span class="line">      &#125;,</span><br><span class="line">    ],</span><br><span class="line">  ],</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="UmiJS-3-x"><a href="#UmiJS-3-x" class="headerlink" title="UmiJS 3.x"></a>UmiJS 3.x</h3><blockquote><p>参考 <a href="https://umijs.org/zh-CN/docs/upgrade-to-umi-3#%E5%8D%87%E7%BA%A7-umi-plugin-react-%E4%B8%BA-umijspreset-react">升级 umi-plugin-react 为 @umijs&#x2F;preset-react</a></p></blockquote><p>由于 Umi 3 的配置方式是拍平的方式，还需要修改配置:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="comment">// 忽略 moment 的 locale 文件，用于减少尺寸。</span></span><br><span class="line">  <span class="comment">// https://v2.umijs.org/zh/config/#ignoremomentlocale</span></span><br><span class="line">  <span class="attr">ignoreMomentLocale</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="comment">// 配置是否开启 treeShaking，默认关闭。</span></span><br><span class="line">  <span class="comment">// https://v2.umijs.org/zh/config/#treeshaking</span></span><br><span class="line">  <span class="attr">treeShaking</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="comment">// 通过 [webpack-chain](https://github.com/mozilla-neutrino/webpack-chain) 的 API 扩展或修改 webpack 配置。</span></span><br><span class="line">  <span class="comment">// https://v2.umijs.org/zh/config/#chainwebpack</span></span><br><span class="line">  <span class="title function_">chainWebpack</span>(<span class="params">config</span>) &#123;</span><br><span class="line">    config.<span class="property">optimization</span>.<span class="title function_">splitChunks</span>(&#123;</span><br><span class="line">      <span class="attr">chunks</span>: <span class="string">&#x27;all&#x27;</span>,</span><br><span class="line">      <span class="attr">automaticNameDelimiter</span>: <span class="string">&#x27;～&#x27;</span>,</span><br><span class="line">      <span class="attr">name</span>: <span class="literal">true</span>,</span><br><span class="line">      <span class="attr">minSize</span>: <span class="number">30000</span>,</span><br><span class="line">      <span class="attr">minChunks</span>: <span class="number">1</span>,</span><br><span class="line">      <span class="attr">cacheGroups</span>: &#123;</span><br><span class="line">        <span class="attr">echarts</span>: &#123;</span><br><span class="line">          <span class="attr">name</span>: <span class="string">&#x27;echarts&#x27;</span>,</span><br><span class="line">          <span class="attr">test</span>: <span class="regexp">/[\\/]node_modules[\\/](echarts)[\\/]/</span>,</span><br><span class="line">          <span class="attr">priority</span>: -<span class="number">9</span>,</span><br><span class="line">          <span class="attr">enforce</span>: <span class="literal">true</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="attr">antd</span>: &#123;</span><br><span class="line">          <span class="attr">name</span>: <span class="string">&#x27;antd&#x27;</span>,</span><br><span class="line">          <span class="attr">test</span>: <span class="regexp">/[\\/]node_modules[\\/](@ant-design|antd|antd-mobile)[\\/]/</span>,</span><br><span class="line">          <span class="attr">priority</span>: -<span class="number">10</span>,</span><br><span class="line">          <span class="attr">enforce</span>: <span class="literal">true</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="attr">vendors</span>: &#123;</span><br><span class="line">          <span class="attr">name</span>: <span class="string">&#x27;vendors&#x27;</span>,</span><br><span class="line">          <span class="attr">test</span>: <span class="regexp">/[\\/]node_modules[\\/]/</span>,</span><br><span class="line">          <span class="attr">priority</span>: -<span class="number">11</span>,</span><br><span class="line">          <span class="attr">enforce</span>: <span class="literal">true</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="comment">// https://umijs.org/zh-CN/config#chunks</span></span><br><span class="line">  <span class="attr">chunks</span>: [<span class="string">&#x27;vendors&#x27;</span>, <span class="string">&#x27;antd&#x27;</span>, <span class="string">&#x27;echarts&#x27;</span>, <span class="string">&#x27;umi&#x27;</span>],</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="momentjs"><a href="#momentjs" class="headerlink" title="momentjs"></a>momentjs</h2><h3 id="使用中文配置"><a href="#使用中文配置" class="headerlink" title="使用中文配置"></a>使用中文配置</h3><blockquote><p>参考: <a href="https://blog.csdn.net/hu_lanlan/article/details/91043696">antd design国际化配置为中文时，日期组件中月与星期显示为英文，其他显示为中文</a></p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">LocaleProvider</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;antd&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> zh_CN <span class="keyword">from</span> <span class="string">&#x27;antd/lib/locale-provider/zh_CN&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> moment <span class="keyword">from</span> <span class="string">&#x27;moment&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">&#x27;moment/locale/zh-cn&#x27;</span>;</span><br><span class="line"></span><br><span class="line">moment.<span class="title function_">locale</span>(<span class="string">&#x27;zh-cn&#x27;</span>);</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">LocaleProvider</span> <span class="attr">locale</span>=<span class="string">&#123;zh_CN&#125;</span>&gt;</span><span class="tag">&lt;<span class="name">App</span> /&gt;</span><span class="tag">&lt;/<span class="name">LocaleProvider</span>&gt;</span></span>;</span><br></pre></td></tr></table></figure><h3 id="替换-momentjs"><a href="#替换-momentjs" class="headerlink" title="替换 momentjs"></a>替换 momentjs</h3><blockquote><p>参考: <a href="https://github.com/ant-design/antd-dayjs-webpack-plugin">antd-dayjs-webpack-plugin</a>、<a href="https://ant.design/docs/react/replace-moment-cn">替换 Moment.js</a>、<a href="https://zhuanlan.zhihu.com/p/136510620">基于umi、antd的前端工程优化实践</a></p></blockquote><p>请先删除 <code>ignoreMomentLocale: true</code> 配置再进行以下操作：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn add antd-dayjs-webpack-plugin -D</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  <span class="title function_">chainWebpack</span>(<span class="params">config</span>) &#123;</span><br><span class="line">    <span class="comment">// antd moment -&gt; dayjs</span></span><br><span class="line">    <span class="comment">// 如果在 Ant Design 3.x 的项目中使用本插件，需要传入以下配置，指定 preset。</span></span><br><span class="line">    config.<span class="title function_">plugin</span>(<span class="string">&#x27;moment2dayjs&#x27;</span>).<span class="title function_">use</span>(<span class="string">&#x27;antd-dayjs-webpack-plugin&#x27;</span>, [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">preset</span>: <span class="string">&#x27;antdv3&#x27;</span></span><br><span class="line">      &#125;</span><br><span class="line">    ])</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果项目中需要使用中文语言，还要引入dayjs的中文语言包并与antd的ConfigProvider配合服用。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 设置dayjs中文</span></span><br><span class="line"><span class="keyword">import</span> dayjs <span class="keyword">from</span> <span class="string">&#x27;dayjs&#x27;</span></span><br><span class="line"><span class="keyword">import</span> <span class="string">&#x27;dayjs/locale/zh-cn&#x27;</span></span><br><span class="line">dayjs.<span class="title function_">locale</span>(<span class="string">&#x27;zh-cn&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">ConfigProvider</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;antd&#x27;</span></span><br><span class="line"><span class="keyword">import</span> zhCN <span class="keyword">from</span> <span class="string">&#x27;antd/lib/locale-provider/zh_CN&#x27;</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> (&#123;children&#125;) =&gt; <span class="language-xml"><span class="tag">&lt;<span class="name">ConfigProvider</span> <span class="attr">locale</span>=<span class="string">&#123;zhCN&#125;</span>&gt;</span>&#123;children&#125;<span class="tag">&lt;/<span class="name">ConfigProvider</span>&gt;</span></span></span><br></pre></td></tr></table></figure><p>通过上述配置后，使用DatePicker组件拿到的日期与之前一致，但可以直接使用dayjs的API操作日期，moment不复存在。最终dayjs打包体积为14.64KB，减小了330KB之多。</p><p>注：目前<a href="mailto:&#x64;&#97;&#121;&#106;&#x73;&#x40;&#49;&#x2e;&#x38;&#x2e;&#x32;&#x30;">dayjs@1.8.20</a>后有个bug会导致替换后WeekPicker显示不正常，1.8.21版本之后已修复。</p><h2 id="dva-loading-使用"><a href="#dva-loading-使用" class="headerlink" title="dva-loading 使用"></a>dva-loading 使用</h2><blockquote><p>参考: <a href="https://www.jianshu.com/p/61fe7a57fad4">dva-loading 实践用法</a></p></blockquote><p>loading 分为四种使用情况，下面依次用代码展示：</p><p><strong>1、全局</strong></p><p>监听的是应用中所有 effect 是否执行完毕，若执行完毕。loading 的值就变为 <code>false</code>。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span></span><br><span class="line"><span class="keyword">const</span> &#123;useSelector,useDispatch&#125; = <span class="string">&#x27;dva&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">Spin</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;antd&#x27;</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">DemoPage</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123;loading&#125; = <span class="title function_">useSelector</span>(<span class="function"><span class="params">stores</span> =&gt;</span> (&#123;</span><br><span class="line">    <span class="attr">loading</span>: stores.<span class="property">loading</span></span><br><span class="line">  &#125;))</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">Spin</span> <span class="attr">spinning</span>=<span class="string">&#123;loading.global&#125;/</span>&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>2、model</strong></p><p>监听某个模块的所有 effect 是否执行完毕，若执行完毕。loading 的值就变为 <code>false</code>。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span></span><br><span class="line"><span class="keyword">const</span> &#123;useSelector,useDispatch&#125; = <span class="string">&#x27;dva&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">Spin</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;antd&#x27;</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">DemoPage</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123;loading,demoModel&#125; = <span class="title function_">useSelector</span>(<span class="function"><span class="params">stores</span> =&gt;</span> (&#123;</span><br><span class="line">    <span class="attr">loading</span>: stores.<span class="property">loading</span>,</span><br><span class="line">    <span class="attr">demoModel</span>: stores.<span class="property">loading</span>,</span><br><span class="line">  &#125;))</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">Spin</span> <span class="attr">spinning</span>=<span class="string">&#123;loading.models.demoModel&#125;/</span>&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>3、effect:</strong></p><p>监听某个 effect 是否执行完毕，若执行完毕。loading 的值就变为 <code>false</code>。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span></span><br><span class="line"><span class="keyword">const</span> &#123;useSelector,useDispatch&#125; = <span class="string">&#x27;dva&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">Spin</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;antd&#x27;</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">DemoPage</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123;loading,demoModel&#125; = <span class="title function_">useSelector</span>(<span class="function"><span class="params">stores</span> =&gt;</span> (&#123;</span><br><span class="line">    <span class="attr">loading</span>: stores.<span class="property">loading</span>,</span><br><span class="line">    <span class="attr">demoModel</span>: stores.<span class="property">loading</span>,</span><br><span class="line">  &#125;))</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">Spin</span> <span class="attr">spinning</span>=<span class="string">&#123;loading.effects[</span>&#x27;<span class="attr">demoModel</span>/<span class="attr">effect1</span>&#x27;]/&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>4、effects</strong></p><p>如果想监听某个 model 中的某几个 effect，可以使用 <code>||</code> 连接，当全部执行完毕时，返回的是 <code>undefined</code>，所以必须在末尾拼接 <code>|| false</code>：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span></span><br><span class="line"><span class="keyword">const</span> &#123;useSelector,useDispatch&#125; = <span class="string">&#x27;dva&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">Spin</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;antd&#x27;</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">DemoPage</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123;loading,demoModel&#125; = <span class="title function_">useSelector</span>(<span class="function"><span class="params">stores</span> =&gt;</span> (&#123;</span><br><span class="line">    <span class="attr">loading</span>: stores.<span class="property">loading</span>,</span><br><span class="line">    <span class="attr">demoModel</span>: stores.<span class="property">loading</span>,</span><br><span class="line">  &#125;))</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">Spin</span> <span class="attr">spinning</span>=<span class="string">&#123;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">loading.effects</span>[&#x27;<span class="attr">demoModel</span>/<span class="attr">effect1</span>&#x27;] ||</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">loading.effects</span>[&#x27;<span class="attr">demoModel</span>/<span class="attr">effect3</span>&#x27;] ||</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">loading.effects</span>[&#x27;<span class="attr">demoModel</span>/<span class="attr">effect4</span>&#x27;] || <span class="attr">false</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      &#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    /&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="关闭-Umi-UI"><a href="#关闭-Umi-UI" class="headerlink" title="关闭 Umi UI"></a>关闭 Umi UI</h2><p>umi 项目默认启动 <code>umi ui</code>，会出现一个mini图标气泡浮在右下角，关闭有两种方式，一种是直接用样式 <code>display none</code>。另一种是在启动时加上 <code>UMI_UI=none</code> 环境变量。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;start&quot;</span><span class="punctuation">:</span> <span class="string">&quot;UMI_UI=none umi dev&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;dev&quot;</span><span class="punctuation">:</span> <span class="string">&quot;MOCK=none UMI_UI=none umi dev&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/f477c87db8e2/</id>
    <link href="https://blog.zisheng.pro/posts/f477c87db8e2/"/>
    <published>2020-06-08T02:17:55.000Z</published>
    <summary>鉴于 UmiJS 是重度的封装了很多工具和功能，对于刚接触的人形成了一层技术壁垒（大佬请忽略这句话）。所以我总结了使用UmiJS 开发中遇到的坑和技巧。</summary>
    <title>UmiJS开发技巧</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React/"/>
    <category term="React" scheme="https://blog.zisheng.pro/tags/React/"/>
    <category term="antd" scheme="https://blog.zisheng.pro/tags/antd/"/>
    <category term="Form 表单" scheme="https://blog.zisheng.pro/tags/Form-%E8%A1%A8%E5%8D%95/"/>
    <category term="Input" scheme="https://blog.zisheng.pro/tags/Input/"/>
    <category term="Table 表格" scheme="https://blog.zisheng.pro/tags/Table-%E8%A1%A8%E6%A0%BC/"/>
    <content>
      <![CDATA[<h2 id="Form-表单"><a href="#Form-表单" class="headerlink" title="Form 表单"></a>Form 表单</h2><h3 id="让-Input-组件自动获取焦点"><a href="#让-Input-组件自动获取焦点" class="headerlink" title="让 Input 组件自动获取焦点"></a>让 Input 组件自动获取焦点</h3><p>添加 <code>autoFocus</code> 属性：</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;<span class="title class_">Input</span> autoFocus /&gt;</span><br></pre></td></tr></table></figure><ul><li><a href="https://github.com/ant-design/ant-design/issues/2952">https://github.com/ant-design/ant-design/issues/2952</a></li><li><a href="https://stackoverflow.com/questions/28889826/how-to-set-focus-on-an-input-field-after-rendering">https://stackoverflow.com/questions/28889826/how-to-set-focus-on-an-input-field-after-rendering</a></li></ul><h2 id="Table-表格"><a href="#Table-表格" class="headerlink" title="Table 表格"></a>Table 表格</h2><h3 id="table组件多选时，会选中下一页的表格同行数据的解决办法"><a href="#table组件多选时，会选中下一页的表格同行数据的解决办法" class="headerlink" title="table组件多选时，会选中下一页的表格同行数据的解决办法"></a>table组件多选时，会选中下一页的表格同行数据的解决办法</h3><blockquote><p>解决办法来自：<a href="https://blog.csdn.net/weixin_41606276/article/details/99312155">https://blog.csdn.net/weixin_41606276/article/details/99312155</a></p></blockquote><p>类似选中某页的几行数据，在删除等操作后其他页面的表格或者删除后本表格的同行页为选中状态。这是因为没有区分每页表格的<code>id</code>。解决办法是给 Table 加上 <code>rowKey</code></p><h3 id="defaultExpandAllRows-true-刷新后不展开问题"><a href="#defaultExpandAllRows-true-刷新后不展开问题" class="headerlink" title="defaultExpandAllRows&#x3D;{true} 刷新后不展开问题"></a>defaultExpandAllRows&#x3D;{true} 刷新后不展开问题</h3><blockquote><p>解决办法来自：<a href="https://github.com/ant-design/ant-design/issues/4145#issuecomment-271230317">https://github.com/ant-design/ant-design/issues/4145#issuecomment-271230317</a></p></blockquote><p>借助了key改变自动变成新的component，虽然可以解决默认问题。。但总觉得不是key正确的用法</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> [data,setData] = <span class="title function_">useState</span>([])</span><br><span class="line"><span class="keyword">const</span> [expandedRowKeys, setExpandedRowKeys] = <span class="title function_">useState</span>([]);</span><br><span class="line"></span><br><span class="line"><span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">setExpandedRowKeys</span>(listTableData.<span class="property">list</span>.<span class="title function_">map</span>(<span class="function"><span class="params">item</span> =&gt;</span> item.<span class="property">id</span>));</span><br><span class="line">&#125;, [listTableData]);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">onExpand</span> = (<span class="params">expanded, record</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">if</span> (expanded) &#123;</span><br><span class="line">    <span class="title function_">setExpandedRowKeys</span>([...expandedRowKeys, record.<span class="property">tableCode</span>]);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="title function_">setExpandedRowKeys</span>(expandedRowKeys.<span class="title function_">filter</span>(<span class="function"><span class="params">item</span> =&gt;</span> item !== record.<span class="property">tableCode</span>));</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">setData</span>([&#123;<span class="attr">id</span>:<span class="number">1</span>&#125;,&#123;<span class="attr">id</span>:<span class="number">2</span>&#125;,&#123;<span class="attr">id</span>:<span class="number">3</span>&#125;])</span><br><span class="line">&#125;,[])</span><br><span class="line">...</span><br><span class="line">&lt;<span class="title class_">Table</span></span><br><span class="line">  dataSource=&#123;data&#125;</span><br><span class="line">  rowKey=<span class="string">&quot;id&quot;</span></span><br><span class="line">  expandedRowKeys=&#123;expandedRowKeys&#125;</span><br><span class="line">  onExpand=&#123;onExpand&#125;</span><br><span class="line">&gt;</span><br></pre></td></tr></table></figure><h3 id="表格的column筛选排序状态重置问题"><a href="#表格的column筛选排序状态重置问题" class="headerlink" title="表格的column筛选排序状态重置问题"></a>表格的column筛选排序状态重置问题</h3><blockquote><p>解决办法来自：<a href="https://github.com/ant-design/ant-design-pro/issues/3695#issuecomment-642460173">https://github.com/ant-design/ant-design-pro/issues/3695#issuecomment-642460173</a></p></blockquote><p>表格设置了排序以及筛选之后, 当我们从外部点击按钮刷新data时,数据列表实际并没有经过筛选排序。解决办法如下：</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> [sortedInfo, setSortedInfo] = <span class="title function_">useState</span>(&#123;&#125;);</span><br><span class="line"><span class="comment">// 分页、排序、筛选变化时触发</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">handleTableChange</span> = <span class="keyword">async</span> (<span class="params">pagination, filters, sorter</span>) =&gt; &#123;</span><br><span class="line">  <span class="title function_">setSortedInfo</span>(sorter);</span><br><span class="line">&#125;;</span><br><span class="line">...</span><br><span class="line">&lt;<span class="title class_">Table</span> onChange=&#123;handleTableChange&#125;&gt;</span><br><span class="line">  ...</span><br><span class="line">  &lt;<span class="title class_">Column</span></span><br><span class="line">    title=<span class="string">&quot;查看次数&quot;</span></span><br><span class="line">    dataIndex=<span class="string">&quot;queryCount&quot;</span></span><br><span class="line">    key=<span class="string">&quot;queryCount&quot;</span></span><br><span class="line">    sorter</span><br><span class="line">    sortOrder=&#123;sortedInfo.<span class="property">columnKey</span> === <span class="string">&#x27;queryCount&#x27;</span> &amp;&amp; sortedInfo.<span class="property">order</span>&#125;</span><br><span class="line">  /&gt;</span><br><span class="line">  ...</span><br><span class="line">&lt;/<span class="title class_">Table</span>&gt;</span><br></pre></td></tr></table></figure><h3 id="Table-Row-Hover-时不改变背景"><a href="#Table-Row-Hover-时不改变背景" class="headerlink" title="Table Row Hover 时不改变背景"></a>Table Row Hover 时不改变背景</h3><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.ant-table-thead</span>&gt;<span class="selector-tag">tr</span><span class="selector-class">.ant-table-row-hover</span><span class="selector-pseudo">:not</span>(<span class="selector-class">.ant-table-expanded-row</span>)&gt;<span class="selector-tag">td</span>,</span><br><span class="line"><span class="selector-class">.ant-table-tbody</span>&gt;<span class="selector-tag">tr</span><span class="selector-class">.ant-table-row-hover</span><span class="selector-pseudo">:not</span>(<span class="selector-class">.ant-table-expanded-row</span>)&gt;<span class="selector-tag">td</span>,</span><br><span class="line"><span class="selector-class">.ant-table-thead</span>&gt;<span class="selector-tag">tr</span><span class="selector-pseudo">:hover</span><span class="selector-pseudo">:not</span>(<span class="selector-class">.ant-table-expanded-row</span>)&gt;<span class="selector-tag">td</span>,</span><br><span class="line"><span class="selector-class">.ant-table-tbody</span>&gt;<span class="selector-tag">tr</span><span class="selector-pseudo">:hover</span><span class="selector-pseudo">:not</span>(<span class="selector-class">.ant-table-expanded-row</span>)&gt;<span class="selector-tag">td</span> &#123;</span><br><span class="line">  <span class="attribute">background</span>: unset; //Change the existing <span class="attribute">color</span> <span class="selector-tag">to</span> `unset`</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Tooltip"><a href="#Tooltip" class="headerlink" title="Tooltip"></a>Tooltip</h2><h3 id="如何更改-ant-tooltip-组件的文本颜色和背景颜色"><a href="#如何更改-ant-tooltip-组件的文本颜色和背景颜色" class="headerlink" title="如何更改 ant-tooltip 组件的文本颜色和背景颜色"></a>如何更改 ant-tooltip 组件的文本颜色和背景颜色</h3><blockquote><p>解决办法来自: <a href="https://www.coder.work/article/3437880">https://www.coder.work/article/3437880</a></p></blockquote><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">:global &#123;</span><br><span class="line">  <span class="selector-class">.ant-tooltip-inner</span> &#123;</span><br><span class="line">    <span class="attribute">color</span>: <span class="number">#ffffff</span>;</span><br><span class="line">    <span class="attribute">background-color</span>: <span class="built_in">rgba</span>(<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">0.6</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="selector-class">.ant-tooltip-arrow</span><span class="selector-pseudo">::before</span> &#123;</span><br><span class="line">    <span class="attribute">background-color</span>: <span class="built_in">rgba</span>(<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">0.6</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="selector-class">.ant-tooltip-placement-bottom</span> <span class="selector-class">.ant-tooltip-arrow</span>, <span class="selector-class">.ant-tooltip-placement-bottomLeft</span> <span class="selector-class">.ant-tooltip-arrow</span>, <span class="selector-class">.ant-tooltip-placement-bottomRight</span> <span class="selector-class">.ant-tooltip-arrow</span> &#123;</span><br><span class="line">    <span class="attribute">border-bottom-color</span>: <span class="number">#000000</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Icon"><a href="#Icon" class="headerlink" title="Icon"></a>Icon</h2><h3 id="自定义-svg-图标"><a href="#自定义-svg-图标" class="headerlink" title="自定义 svg 图标"></a>自定义 svg 图标</h3><blockquote><p>解决办法参考自: <a href="https://blog.csdn.net/c5211314963/article/details/103371845">https://blog.csdn.net/c5211314963/article/details/103371845</a></p></blockquote><p>如果你们的 UED 同学就是传给你一个 svg 图片，你可以按照下面的方式进行。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Icon</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;antd&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">PointIconSvg</span> = (<span class="params">&#123; size = <span class="number">1</span>, color = <span class="string">&#x27;#1a37ea&#x27;</span> &#125;</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">svg</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">width</span>=<span class="string">&#123;</span>`$&#123;<span class="attr">10</span> * <span class="attr">size</span>&#125;<span class="attr">px</span>`&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">height</span>=<span class="string">&#123;</span>`$&#123;<span class="attr">13</span> * <span class="attr">size</span>&#125;<span class="attr">px</span>`&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">viewBox</span>=<span class="string">&quot;0 0 10 13&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">version</span>=<span class="string">&quot;1.1&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">xmlns</span>=<span class="string">&quot;http://www.w3.org/2000/svg&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">display:</span> &#x27;<span class="attr">block</span>&#x27; &#125;&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    &gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">g</span> <span class="attr">id</span>=<span class="string">&quot;污染源档案&quot;</span> <span class="attr">stroke</span>=<span class="string">&quot;none&quot;</span> <span class="attr">strokeWidth</span>=<span class="string">&quot;1&quot;</span> <span class="attr">fill</span>=<span class="string">&quot;none&quot;</span> <span class="attr">fillRule</span>=<span class="string">&quot;evenodd&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">g</span> <span class="attr">id</span>=<span class="string">&quot;6企业详情-详细信息-环境管理-2&quot;</span> <span class="attr">transform</span>=<span class="string">&quot;translate(-698.000000, -746.000000)&quot;</span> <span class="attr">fill</span>=<span class="string">&#123;color&#125;</span> <span class="attr">fillRule</span>=<span class="string">&quot;nonzero&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">g</span> <span class="attr">id</span>=<span class="string">&quot;基本信息&quot;</span> <span class="attr">transform</span>=<span class="string">&quot;translate(24.000000, 335.000000)&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">g</span> <span class="attr">id</span>=<span class="string">&quot;编组-7&quot;</span> <span class="attr">transform</span>=<span class="string">&quot;translate(30.000000, 120.000000)&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">              <span class="tag">&lt;<span class="name">g</span> <span class="attr">id</span>=<span class="string">&quot;地图&quot;</span> <span class="attr">transform</span>=<span class="string">&quot;translate(0.000000, 273.000000)&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">                <span class="tag">&lt;<span class="name">g</span> <span class="attr">id</span>=<span class="string">&quot;编组&quot;</span> <span class="attr">transform</span>=<span class="string">&quot;translate(644.000000, 16.000000)&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">                  <span class="tag">&lt;<span class="name">g</span> <span class="attr">id</span>=<span class="string">&quot;编组-4&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">                    <span class="tag">&lt;<span class="name">path</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">                      <span class="attr">d</span>=<span class="string">&quot;M4.99877841,2 C2.24285365,2 0,4.29103476 0,7.10616241 C0,9.82395853 4.48082093,14.6506047 4.67139018,14.8552505 C4.75690203,14.9475907 4.87661862,15 5.0012216,15 C5.0036648,15 5.00855119,15 5.01099438,15 C5.13804056,15 5.26020034,14.9401037 5.343269,14.8402764 L6.89469827,13.0259167 C8.95675544,10.4478787 10,8.4563256 10,7.10865809 C10,4.29103476 7.75714636,2 4.99877841,2 Z M4.99877841,9.42714533 C3.74297582,9.42714533 2.7266064,8.38894222 2.7266064,7.10616241 C2.7266064,5.82338261 3.74297582,4.78517949 4.99877841,4.78517949 C6.254581,4.78517949 7.27095042,5.82338261 7.27095042,7.10616241 C7.27095042,8.38894222 6.254581,9.42714533 4.99877841,9.42714533 Z&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">                      <span class="attr">id</span>=<span class="string">&quot;形状&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">                    /&gt;</span></span></span><br><span class="line"><span class="language-xml">                  <span class="tag">&lt;/<span class="name">g</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">                <span class="tag">&lt;/<span class="name">g</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">              <span class="tag">&lt;/<span class="name">g</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;/<span class="name">g</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;/<span class="name">g</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">g</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">g</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">svg</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> props =&gt; <span class="language-xml"><span class="tag">&lt;<span class="name">Icon</span> <span class="attr">component</span>=<span class="string">&#123;()</span> =&gt;</span> <span class="tag">&lt;<span class="name">PointIconSvg</span> &#123;<span class="attr">...props</span>&#125; /&gt;</span>&#125; /&gt;</span>;</span><br></pre></td></tr></table></figure><h2 id="Button"><a href="#Button" class="headerlink" title="Button"></a>Button</h2><h3 id="点击动画边框-css-清除"><a href="#点击动画边框-css-清除" class="headerlink" title="点击动画边框 css 清除"></a>点击动画边框 css 清除</h3><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">:global&#123;</span><br><span class="line">  <span class="selector-class">.ant-click-animating-without-extra-node</span><span class="selector-pseudo">:after</span> &#123;</span><br><span class="line">    <span class="attribute">border</span>: <span class="number">0</span> none;</span><br><span class="line">    <span class="attribute">opacity</span>: <span class="number">0</span>;</span><br><span class="line">    <span class="attribute">animation</span>:none <span class="number">0</span> ease <span class="number">0</span> <span class="number">1</span> normal;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/681c13911f61/</id>
    <link href="https://blog.zisheng.pro/posts/681c13911f61/"/>
    <published>2020-06-05T08:59:43.000Z</published>
    <summary>本文介绍了一些在开发中常用的 Antd 技巧</summary>
    <title>Antd 开发技巧</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="echarts" scheme="https://blog.zisheng.pro/tags/echarts/"/>
    <category term="可视化" scheme="https://blog.zisheng.pro/tags/%E5%8F%AF%E8%A7%86%E5%8C%96/"/>
    <category term="开发技巧" scheme="https://blog.zisheng.pro/tags/%E5%BC%80%E5%8F%91%E6%8A%80%E5%B7%A7/"/>
    <content>
      <![CDATA[<h2 id="柱状图颜色渐变"><a href="#柱状图颜色渐变" class="headerlink" title="柱状图颜色渐变"></a>柱状图颜色渐变</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> echarts <span class="keyword">from</span> <span class="string">&#x27;echarts&#x27;</span></span><br><span class="line">...</span><br><span class="line"><span class="attr">series</span>: [</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">type</span>: <span class="string">&#x27;bar&#x27;</span>,</span><br><span class="line">    <span class="comment">// 图形样式</span></span><br><span class="line">    <span class="attr">itemStyle</span>: &#123;</span><br><span class="line">      <span class="attr">color</span>: <span class="keyword">new</span> echarts.<span class="property">graphic</span>.<span class="title class_">LinearGradient</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">1</span>, [</span><br><span class="line">        &#123; <span class="attr">offset</span>: <span class="number">0</span>, <span class="attr">color</span>: <span class="string">&#x27;#178ffc&#x27;</span> &#125;,</span><br><span class="line">        &#123; <span class="attr">offset</span>: <span class="number">1</span>, <span class="attr">color</span>: <span class="string">&#x27;#ffffff&#x27;</span> &#125;,</span><br><span class="line">      ]),</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">emphasis</span>: &#123;</span><br><span class="line">      <span class="attr">color</span>: <span class="keyword">new</span> echarts.<span class="property">graphic</span>.<span class="title class_">LinearGradient</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">1</span>, [</span><br><span class="line">        &#123;<span class="attr">offset</span>: <span class="number">0</span>, <span class="attr">color</span>: <span class="string">&#x27;#178ffc&#x27;</span>&#125;,</span><br><span class="line">        &#123;<span class="attr">offset</span>: <span class="number">1</span>, <span class="attr">color</span>: <span class="string">&#x27;#ffffff&#x27;</span>&#125;</span><br><span class="line">      ])</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">],</span><br></pre></td></tr></table></figure><h2 id="横坐标值显示不全（自动隐藏）"><a href="#横坐标值显示不全（自动隐藏）" class="headerlink" title="横坐标值显示不全（自动隐藏）"></a>横坐标值显示不全（自动隐藏）</h2><p>echarts中，横轴数据如果非常多，会自动隐藏一部分数据，我们可以通过属性 <code>interval</code> 来进行调整。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">xAxis</span>: &#123;</span><br><span class="line">  <span class="attr">type</span>: <span class="string">&#x27;category&#x27;</span>,</span><br><span class="line">  <span class="attr">data</span>: labels,</span><br><span class="line">  <span class="attr">axisLabel</span>: &#123;</span><br><span class="line">    <span class="attr">interval</span>: <span class="number">0</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/95a45272613e/</id>
    <link href="https://blog.zisheng.pro/posts/95a45272613e/"/>
    <published>2020-06-05T06:05:24.000Z</published>
    <summary>这里记录了一些实用的 Echarts 开发技巧</summary>
    <title>Echarts 开发技巧</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="CSS3" scheme="https://blog.zisheng.pro/tags/CSS3/"/>
    <category term="flex" scheme="https://blog.zisheng.pro/tags/flex/"/>
    <category term="lineHeight" scheme="https://blog.zisheng.pro/tags/lineHeight/"/>
    <category term="textAlign" scheme="https://blog.zisheng.pro/tags/textAlign/"/>
    <category term="height" scheme="https://blog.zisheng.pro/tags/height/"/>
    <content>
      <![CDATA[<p>居中的效果是Web开发中常见的需求，几乎和阴影、圆角可以并列为产品三大法宝，本文的目的是捋顺开发中常用的居中计较，方便记忆和日后查阅。为了方便演示，本文所有 demo 使用 React 的 CSS-in-JS 语法完成。</p><h2 id="单行文本"><a href="#单行文本" class="headerlink" title="单行文本"></a>单行文本</h2><h3 id="height、lineHeight、textAlign"><a href="#height、lineHeight、textAlign" class="headerlink" title="height、lineHeight、textAlign"></a>height、lineHeight、textAlign</h3><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">&lt;div</span><br><span class="line">  style=&#123;&#123;</span><br><span class="line">    <span class="attr">boxSizing</span>: <span class="string">&#x27;border-box&#x27;</span>,</span><br><span class="line">    <span class="attr">height</span>: <span class="string">&#x27;100px&#x27;</span>,</span><br><span class="line">    <span class="attr">width</span>: <span class="string">&#x27;300px&#x27;</span>,</span><br><span class="line">    <span class="attr">background</span>: <span class="string">&#x27;purple&#x27;</span>,</span><br><span class="line">    <span class="attr">color</span>: <span class="string">&#x27;#fff&#x27;</span>,</span><br><span class="line">    <span class="attr">textAlign</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">    <span class="attr">lineHeight</span>: <span class="string">&#x27;98px&#x27;</span>,</span><br><span class="line">    <span class="attr">border</span>: <span class="string">&#x27;1px solid red&#x27;</span>,</span><br><span class="line">  &#125;&#125;</span><br><span class="line">&gt;</span><br><span class="line">  单行文本</span><br><span class="line">&lt;/div&gt;</span><br></pre></td></tr></table></figure><h3 id="Flex"><a href="#Flex" class="headerlink" title="Flex"></a>Flex</h3><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&lt;div</span><br><span class="line">  style=&#123;&#123;</span><br><span class="line">    <span class="attr">height</span>: <span class="string">&#x27;100px&#x27;</span>,</span><br><span class="line">    <span class="attr">width</span>: <span class="string">&#x27;300px&#x27;</span>,</span><br><span class="line">    <span class="attr">background</span>: <span class="string">&#x27;purple&#x27;</span>,</span><br><span class="line">    <span class="attr">color</span>: <span class="string">&#x27;#fff&#x27;</span>,</span><br><span class="line">    <span class="attr">display</span>: <span class="string">&#x27;flex&#x27;</span>,</span><br><span class="line">    <span class="attr">justifyContent</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">    <span class="attr">alignItems</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">  &#125;&#125;</span><br><span class="line">&gt;</span><br><span class="line">  单行文本</span><br><span class="line">&lt;/div&gt;</span><br></pre></td></tr></table></figure><h2 id="多行文本居中"><a href="#多行文本居中" class="headerlink" title="多行文本居中"></a>多行文本居中</h2><h3 id="table-cell、vertical-align"><a href="#table-cell、vertical-align" class="headerlink" title="table-cell、vertical-align"></a>table-cell、vertical-align</h3><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&lt;div</span><br><span class="line">  style=&#123;&#123;</span><br><span class="line">    <span class="attr">boxSizing</span>: <span class="string">&#x27;border-box&#x27;</span>,</span><br><span class="line">    <span class="attr">height</span>: <span class="string">&#x27;100px&#x27;</span>,</span><br><span class="line">    <span class="attr">width</span>: <span class="string">&#x27;300px&#x27;</span>,</span><br><span class="line">    <span class="attr">background</span>: <span class="string">&#x27;purple&#x27;</span>,</span><br><span class="line">    <span class="attr">color</span>: <span class="string">&#x27;#fff&#x27;</span>,</span><br><span class="line">    <span class="attr">display</span>: <span class="string">&#x27;table-cell&#x27;</span>,</span><br><span class="line">    <span class="attr">verticalAlign</span>: <span class="string">&#x27;middle&#x27;</span>,</span><br><span class="line">  &#125;&#125;</span><br><span class="line">&gt;</span><br><span class="line">  多行文本多行文本多行文本多行文本多行文本多行文本多行文本多行文本多行文本多行文本多行文本多行文本多行文本多行文本多行文本多行文本多行文本多行文本</span><br><span class="line">&lt;/div&gt;</span><br></pre></td></tr></table></figure><h3 id="Flex-1"><a href="#Flex-1" class="headerlink" title="Flex"></a>Flex</h3><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&lt;div</span><br><span class="line">  style=&#123;&#123;</span><br><span class="line">    <span class="attr">height</span>: <span class="string">&#x27;100px&#x27;</span>,</span><br><span class="line">    <span class="attr">width</span>: <span class="string">&#x27;300px&#x27;</span>,</span><br><span class="line">    <span class="attr">background</span>: <span class="string">&#x27;purple&#x27;</span>,</span><br><span class="line">    <span class="attr">color</span>: <span class="string">&#x27;#fff&#x27;</span>,</span><br><span class="line">    <span class="attr">display</span>: <span class="string">&#x27;flex&#x27;</span>,</span><br><span class="line">    <span class="attr">justifyContent</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">    <span class="attr">alignItems</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">  &#125;&#125;</span><br><span class="line">&gt;</span><br><span class="line">  多行文本多行文本多行文本多行文本多行文本多行文本多行文本多行文本多行文本多行文本多行文本多行文本多行文本多行文本多行文本多行文本多行文本多行文本</span><br><span class="line">&lt;/div&gt;</span><br></pre></td></tr></table></figure><h2 id="图片居中"><a href="#图片居中" class="headerlink" title="图片居中"></a>图片居中</h2><h3 id="table-cell、verticalAlign、textAlign"><a href="#table-cell、verticalAlign、textAlign" class="headerlink" title="table-cell、verticalAlign、textAlign"></a>table-cell、verticalAlign、textAlign</h3><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">&lt;div</span><br><span class="line">  style=&#123;&#123;</span><br><span class="line">    <span class="attr">height</span>: <span class="string">&#x27;100px&#x27;</span>,</span><br><span class="line">    <span class="attr">width</span>: <span class="string">&#x27;300px&#x27;</span>,</span><br><span class="line">    <span class="attr">background</span>: <span class="string">&#x27;purple&#x27;</span>,</span><br><span class="line">    <span class="attr">display</span>: <span class="string">&#x27;table-cell&#x27;</span>,</span><br><span class="line">    <span class="attr">verticalAlign</span>: <span class="string">&#x27;middle&#x27;</span>,</span><br><span class="line">    <span class="attr">textAlign</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">  &#125;&#125;</span><br><span class="line">&gt;</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">&quot;http://iph.href.lu/50x50&quot;</span> <span class="attr">alt</span>=<span class="string">&quot;&quot;</span> /&gt;</span></span></span><br><span class="line">&lt;/div&gt;</span><br></pre></td></tr></table></figure><h3 id="Flex-2"><a href="#Flex-2" class="headerlink" title="Flex"></a>Flex</h3><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">&lt;div</span><br><span class="line">  style=&#123;&#123;</span><br><span class="line">    <span class="attr">height</span>: <span class="string">&#x27;100px&#x27;</span>,</span><br><span class="line">    <span class="attr">width</span>: <span class="string">&#x27;300px&#x27;</span>,</span><br><span class="line">    <span class="attr">background</span>: <span class="string">&#x27;purple&#x27;</span>,</span><br><span class="line">    <span class="attr">display</span>: <span class="string">&#x27;flex&#x27;</span>,</span><br><span class="line">    <span class="attr">justifyContent</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">    <span class="attr">alignItems</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">  &#125;&#125;</span><br><span class="line">&gt;</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">&quot;http://iph.href.lu/50x50&quot;</span> <span class="attr">alt</span>=<span class="string">&quot;&quot;</span> /&gt;</span></span></span><br><span class="line">&lt;/div&gt;</span><br></pre></td></tr></table></figure><h2 id="块级元素"><a href="#块级元素" class="headerlink" title="块级元素"></a>块级元素</h2><h3 id="子绝父相"><a href="#子绝父相" class="headerlink" title="子绝父相"></a>子绝父相</h3><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">&lt;div</span><br><span class="line">  style=&#123;&#123;</span><br><span class="line">    <span class="attr">height</span>: <span class="string">&#x27;100px&#x27;</span>,</span><br><span class="line">    <span class="attr">width</span>: <span class="string">&#x27;300px&#x27;</span>,</span><br><span class="line">    <span class="attr">background</span>: <span class="string">&#x27;purple&#x27;</span>,</span><br><span class="line">    <span class="attr">position</span>: <span class="string">&#x27;relative&#x27;</span>,</span><br><span class="line">  &#125;&#125;</span><br><span class="line">&gt;</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">div</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">style</span>=<span class="string">&#123;&#123;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">background:</span> &#x27;#<span class="attr">fff</span>&#x27;,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">width:</span> &#x27;<span class="attr">50px</span>&#x27;,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">height:</span> &#x27;<span class="attr">50px</span>&#x27;,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">position:</span> &#x27;<span class="attr">absolute</span>&#x27;,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">left:</span> &#x27;<span class="attr">50</span>%&#x27;,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">top:</span> &#x27;<span class="attr">50</span>%&#x27;,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">transform:</span> &#x27;<span class="attr">translate</span>(<span class="attr">-50</span>%,<span class="attr">-50</span>%)&#x27;,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    &#125;&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">  /&gt;</span></span></span><br><span class="line">&lt;/div&gt;</span><br></pre></td></tr></table></figure><h3 id="Flex-3"><a href="#Flex-3" class="headerlink" title="Flex"></a>Flex</h3><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">&lt;div</span><br><span class="line">  style=&#123;&#123;</span><br><span class="line">    <span class="attr">height</span>: <span class="string">&#x27;100px&#x27;</span>,</span><br><span class="line">    <span class="attr">width</span>: <span class="string">&#x27;300px&#x27;</span>,</span><br><span class="line">    <span class="attr">background</span>: <span class="string">&#x27;purple&#x27;</span>,</span><br><span class="line">    <span class="attr">display</span>: <span class="string">&#x27;flex&#x27;</span>,</span><br><span class="line">    <span class="attr">justifyContent</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">    <span class="attr">alignItems</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">  &#125;&#125;</span><br><span class="line">&gt;</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">div</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">style</span>=<span class="string">&#123;&#123;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">background:</span> &#x27;#<span class="attr">fff</span>&#x27;,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">width:</span> &#x27;<span class="attr">50px</span>&#x27;,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">height:</span> &#x27;<span class="attr">50px</span>&#x27;,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    &#125;&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">  /&gt;</span></span></span><br><span class="line">&lt;/div&gt;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/97c353d732f9/</id>
    <link href="https://blog.zisheng.pro/posts/97c353d732f9/"/>
    <published>2020-06-03T05:35:13.000Z</published>
    <summary>居中的效果是Web开发中常见的需求，几乎和阴影、圆角可以并列为产品三大法宝，本文的目的是捋顺开发中常用的居中计较，方便记忆和日后查阅。为了方便演示，本文所有 demo 使用 React 的 CSS-in-JS 语法完成。</summary>
    <title>CSS 中的居中操作</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React-Native/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/tags/React-Native/"/>
    <content>
      <![CDATA[<h2 id="code-push-server"><a href="#code-push-server" class="headerlink" title="code-push-server"></a>code-push-server</h2><h2 id="code-push-cli"><a href="#code-push-cli" class="headerlink" title="code-push-cli"></a>code-push-cli</h2><p>由于 <code>appcenter-cli</code> 只支持微软官方的 CodePush 服务，因此我们只好继续使用 code-push-cli。</p><h3 id="登录"><a href="#登录" class="headerlink" title="登录"></a>登录</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">code-push login http://192.168.2.119:3000/</span></span><br></pre></td></tr></table></figure><p>这时候，浏览器会弹出页面让你填写密码，以此获取 <code>accessKey</code>，将获取的<code>accessKey</code>填回命令行，即可登录成功，记住这个 <code>accessKey</code>，下次即可通过以下命令直接登录：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">code-push login http://192.168.2.119:3000/ --accessKey 8CB9b37oz6CuHyW2vwsDaXut6biy4ksvOXqog</span></span><br></pre></td></tr></table></figure><h3 id="添加应用"><a href="#添加应用" class="headerlink" title="添加应用"></a>添加应用</h3><blockquote><p><code>code-push app add &lt;appName&gt; &lt;os&gt; &lt;platform&gt;</code></p></blockquote><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">code-push app add ios_myapp ios react-native</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">code-push app add android_myapp Android react-native</span></span><br></pre></td></tr></table></figure><blockquote><p>注意：如果你的应用分为 Android 和 ios 版，那么注册应用的时候需要注册两个 App 获取两套 <code>deployment key</code>，可以通过 <code>code-push deployment ls &lt;appName&gt; -k</code> 获取部署的密钥</p></blockquote><h3 id="操作应用"><a href="#操作应用" class="headerlink" title="操作应用"></a>操作应用</h3><ul><li>移除应用：<code>code-push app remove|rm &lt;appName&gt;</code></li><li>重命名应用：<code>code-push app rename &lt;currentAppName&gt; &lt;newAppName&gt;</code></li><li>应用列表：<code>code-push app list|ls</code></li></ul><h3 id="添加部署"><a href="#添加部署" class="headerlink" title="添加部署"></a>添加部署</h3><p>添加应用后，它默认包含两个部署环境：<code>Staging</code> 和 <code>Production</code> 。这让你可以理解发布更新到一个内部的环境，你可以在推送到终端用户之前彻底的测试每个更新。这个工作流是至关重要的，以确保你的版本准备好给大众，而且这是一个在Web上实践很久的惯例。</p><p>如果你的App有 <code>Staging</code> 和 <code>Production</code> 环境其实已经满足了你的需求，然后你不需要做任何事情。不过，如果你需要 <code>alpha</code>，<code>dev</code>等部署环境，那你可以简单的使用如下命令创建：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">code-push deployment add &lt;appName&gt; &lt;deploymentName&gt;</span></span><br></pre></td></tr></table></figure><h3 id="操作部署"><a href="#操作部署" class="headerlink" title="操作部署"></a>操作部署</h3><ul><li><code>code-push deployment clear &lt;appName&gt; &lt;deploymentName&gt;</code>： 清除部署历史</li><li><code>code-push deployment remove|rm &lt;appName&gt; &lt;deploymentName&gt;</code>：删除一个部署</li><li><code>code-push deployment rename &lt;appName&gt; &lt;currentDeploymentName&gt; &lt;newDeploymentName&gt;</code>：重命名部署</li><li><code>code-push deployment list|ls &lt;appName&gt; [options]</code>：部署列表<ul><li><code>--format</code> ：Output format to display your deployments with (<code>&quot;json&quot;</code>or <code>&quot;table&quot;</code>) [字符串] [默认值: <code>&quot;table&quot;</code>]</li><li><code>--displayKeys</code>,<code> -k</code>： Specifies whether to display the deployment keys [布尔] [默认值: <code>false</code>]</li></ul></li><li><code>code-push deployment history|h &lt;appName&gt; &lt;deploymentName&gt; [options]</code>：查看发布的历史记录<ul><li><code>--format</code>：Output format to display the release history with (<code>&quot;json&quot;</code> or <code>&quot;table&quot;</code>) [字符串] [默认值: <code>&quot;table&quot;</code>]</li><li><code>--displayAuthor</code>,<code> -a</code>：Specifies whether to display the release author [布尔] [默认值: <code>false</code>]</li></ul></li></ul><h3 id="发布部署"><a href="#发布部署" class="headerlink" title="发布部署"></a>发布部署</h3><ul><li><code>code-push release-react &lt;appName&gt; &lt;platform&gt; [options]</code>: 发布新的热更新版本<ul><li><code>code-push release-react android_myapp android --gradleFile=android/app/build.gradle</code></li><li><code>code-push release-react ios_myapp ios --plistFile=ios/betty/Info.plist</code></li></ul></li></ul><p>其中参数 <code>-t</code> 为二进制(<code>.ipa</code>与<code>apk</code>)安装包的的版本，建议动态读；<code>--dev</code> 为是否启用开发者模式(默认为<code>false</code>)；<code>–d</code> 是要发布更新的环境分 <code>Production</code> 与 <code>Staging</code> (默认为 <code>Staging</code>)；<code>--des</code> 为更新说明；<code>–m</code> 是强制更新。</p><h2 id="react-native-code-push"><a href="#react-native-code-push" class="headerlink" title="react-native-code-push"></a>react-native-code-push</h2><h3 id="安装组件"><a href="#安装组件" class="headerlink" title="安装组件"></a>安装组件</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">yarn add react-native-code-push</span></span><br></pre></td></tr></table></figure><h3 id="iOS设置"><a href="#iOS设置" class="headerlink" title="iOS设置"></a>iOS设置</h3><p>获得 CodePush 插件后，必须将其集成到 React Native 应用程序的 Xcode 项目中并正确配置。</p><ol><li><p>运行<code>cd ios &amp;&amp; pod install &amp;&amp; cd ..</code>以安装所有必需的CocoaPods依赖项。</p></li><li><p>打开<code>AppDelegate.m</code>文件，并为CodePush标头添加导入语句：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">#import &lt;CodePush/CodePush.h&gt;</span><br></pre></td></tr></table></figure></li><li><p>查找以下代码行，该代码为生产版本的网桥设置源URL：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">return [[NSBundle mainBundle] URLForResource:@&quot;main&quot; withExtension:@&quot;jsbundle&quot;];</span><br></pre></td></tr></table></figure></li><li><p>用以下行替换它：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">return [CodePush bundleURL];</span><br></pre></td></tr></table></figure><p>此更改将你的应用配置为始终加载应用JS捆绑包的最新版本。在首次启动时，这将与使用该应用程序编译的文件相对应。但是，在通过CodePush推送更新后，这将返回最近安装的更新的位置。</p><p>通常，您只想使用 CodePush 来解决发行版本中的 JS 包位置，因此，我们建议使用<code>DEBUG</code>预处理器宏在是否使用打包程序服务器和 CodePush 之间进行动态切换。这样可以更轻松地确保您在生产中获得所需的正确行为，同时仍可以在调试时使用Chrome开发工具，实时重新加载等。</p><p>你的<code>sourceURLForBridge</code>方法应如下所示：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge</span><br><span class="line">&#123;</span><br><span class="line">  #if DEBUG</span><br><span class="line">    return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@&quot;index&quot; fallbackResource:nil];</span><br><span class="line">  #else</span><br><span class="line">    return [CodePush bundleURL];</span><br><span class="line">  #endif</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>将部署密钥添加到<code>Info.plist</code>：</p><p>为了让 CodePush 运行时知道应该针对哪个部署查询更新，请打开你的应用的 <code>Info.plist</code> 文件，并添加一个名为<code>CodePushDeploymentKey</code>的新条目，其值是你要配置的应用的 <code>Staging Deployment Key</code>。</p><p>你可以通过 <code>code-push deployment ls &lt;appName&gt; -k</code> 来检索这个值（该 <code>-k</code> 标志是必需的，因为默认情况下不会显示键），然后复制相对应的 <code>Deployment Key</code> 即可。</p><p><img src="https://user-gold-cdn.xitu.io/2020/2/27/170823d9cfe4d5ba?w=1764&h=414&f=png&s=250595" alt="紫升"></p><p>为了有效利用与 CodePush 应用程序一起创建的 <code>Staging</code> 和 <code>Production</code> 部署，请在实际将你的应用程序对 CodePush 的使用移入生产环境之前，进行<strong>多部署测试</strong>的配置。</p></li></ol><h4 id="多部署测试"><a href="#多部署测试" class="headerlink" title="多部署测试"></a>多部署测试</h4><blockquote><p>该部分适用于 Xcode 11</p></blockquote><p>Xcode 允许你为每个<strong>配置</strong> (如 <code>debug</code>, <code>release</code>) 自定义构建设置，然后可以将其引用为 <code>Info.plist</code> 文件中的键值（如 <code>CodePushDeploymentKey</code> 设置）。此机制是您可以轻松地进行构建配置以生成二进制文件，这些二进制文件被配置为与不同的 CodePush 部署同步。</p><p>要进行设置，请执行以下步骤：</p><ol><li><p>打开您的 Xcode 项目，然后在 <code>Project navigator</code> 窗口中选择您的项目</p></li><li><p>确保已选择 <code>PROJECT</code> 节点，而不是 <code>TARGETS</code></p></li><li><p>选择 <code>Info</code> 标签</p></li><li><p>点击 <code>+</code> 的内部按钮<code>Duplicate &quot;Release&quot; Configuration</code><br> <img src="https://user-gold-cdn.xitu.io/2020/3/9/170bb342a289a24a?w=1026&h=366&f=png&s=63534" alt="紫升"></p></li><li><p>将新配置命名为 <code>Staging</code>（或您喜欢的任何名称）</p></li><li><p>选择 <code>Build Settings</code> 选项卡</p></li><li><p>单击工具栏上的 <code>+</code> 按钮，创建一个名为  <code>CONFIGURATION_BUILD_DIR</code> 的 <code>User-Defined Setting</code>, 使用相同的 <code>per-configuration</code> 配置。</p>  <figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$</span><span class="language-bash">(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)`为`$(BUILD_DIR)/Release$(EFFECTIVE_PLATFORM_NAME)`</span></span><br></pre></td></tr></table></figure></li></ol><p><img src="https://user-gold-cdn.xitu.io/2020/3/9/170bb34307cf8b7f?w=1058&h=316&f=png&s=99721" alt="紫升"></p><blockquote><p>注意：每次创建这个 Xcode 都会崩溃，只能先把值写入之后，在 <code>project.pbxproj</code> 中新建。</p></blockquote><ol start="8"><li><p>点击工具栏的 <code>+</code>  并选择 <code>Add User-Defined Setting</code></p><p><img src="https://user-gold-cdn.xitu.io/2020/3/9/170bb342a1fca283?w=1308&h=228&f=png&s=62560" alt="紫升"></p></li><li><p>将此新设置命名为<code>CodePushDeploymentKey</code>，展开它，然后为 <code>Staging </code>配置指定您的 <code>Staging</code> 部署密钥，为 <code>Release</code> 配置指定您的 <code>Production</code> 部署密钥。</p><p><img src="https://user-gold-cdn.xitu.io/2020/3/9/170bb342d671ab43?w=890&h=172&f=png&s=67687" alt="紫升"></p><blockquote><p>提醒一下，您可以通过<code>appcenter codepush deployment list -a &lt;ownerName&gt;/&lt;appName&gt; --displayKeys</code>从终端运行来检索这些键。</p></blockquote></li><li><p>打开项目的 <code>Info.plist</code> 文件，然后将<code>CodePushDeploymentKey</code>条目的值更改为<code>$(CODEPUSH_KEY)</code></p><p><img src="https://user-gold-cdn.xitu.io/2020/3/9/170bb342f42a6b4e?w=890&h=110&f=png&s=29574" alt="紫升"></p></li></ol><p>就是这样了，现在当你运行或构建你的App，你的 <code>Staging</code> 包将自动同步你的 <code>Staging</code> 部署，你的 <code>Release</code> 包将自动同步你的 <code>Production</code> 部署。</p><h3 id="Android设置"><a href="#Android设置" class="headerlink" title="Android设置"></a>Android设置</h3><p>为了将CodePush集成到您的Android项目中，请执行以下步骤：</p><ol><li><p>在<code>android/app/build.gradle</code>文件中，将文件<code>codepush.gradle</code>添加为下面的其他构建任务定义：</p><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">apply <span class="attr">from:</span> <span class="string">&quot;../../node_modules/react-native-code-push/android/codepush.gradle&quot;</span></span><br><span class="line">...</span><br></pre></td></tr></table></figure></li><li><p><code>MainApplication.java</code>通过以下更改更新文件以使用 CodePush：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line"><span class="comment">// 1. 导入插件的类</span></span><br><span class="line"><span class="keyword">import</span> com.microsoft.codepush.react.CodePush;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MainApplication</span> <span class="keyword">extends</span> <span class="title class_">Application</span> <span class="keyword">implements</span> <span class="title class_">ReactApplication</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">ReactNativeHost</span> <span class="variable">mReactNativeHost</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ReactNativeHost</span>(<span class="built_in">this</span>) &#123;</span><br><span class="line">        ...</span><br><span class="line">        <span class="comment">// 2. 重写 getJSBundleFile 方法，每次 app 启动的时候让 CodePush 运行时决定从哪里加载 JS bundle</span></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">protected</span> String <span class="title function_">getJSBundleFile</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> CodePush.getJSBundleFile();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>将部署密钥添加到<code>strings.xml</code>：</p><p>为了让 CodePush 运行时知道它应该查询哪些部署更新，请打开您的应用程序的 <code>string.xml</code> 文件，并添加一个名为 <code>CodePushDeploymentKey</code> 的新字符串，它的值是应用的 <code>Staging</code> 部署。你可以通过 <code>appcenter deployment list &lt;ownerName&gt;/&lt;appName&gt; -k</code> 获取该值。</p><p><img src="https://user-gold-cdn.xitu.io/2020/2/27/170823d9cfe4d5ba?w=1764&h=414&f=png&s=250595" alt="紫升"></p><p>您<code>strings.xml</code>应该看起来像这样：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">resources</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">string</span> <span class="attr">name</span>=<span class="string">&quot;app_name&quot;</span>&gt;</span>AppName<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">string</span> <span class="attr">moduleConfig</span>=<span class="string">&quot;true&quot;</span> <span class="attr">name</span>=<span class="string">&quot;CodePushDeploymentKey&quot;</span>&gt;</span>DeploymentKey<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">resources</span>&gt;</span></span><br></pre></td></tr></table></figure><p>为了有效利用与CodePush应用程序一起创建的<code>Staging</code>和<code>Production</code>部署，请在实际将您的应用程序对CodePush的使用移入生产环境之前，请参考下面的<strong>多部署测试</strong>。</p></li></ol><h4 id="多部署测试-1"><a href="#多部署测试-1" class="headerlink" title="多部署测试"></a>多部署测试</h4><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">android &#123;</span><br><span class="line">  ...</span><br><span class="line">  buildTypes &#123;</span><br><span class="line">    debug &#123;</span><br><span class="line">      signingConfig signingConfigs.debug</span><br><span class="line">      <span class="comment">// Note: CodePush updates should not be tested in Debug mode as they are overriden by the RN packager. However, because CodePush checks for updates in all modes, we must supply a key.</span></span><br><span class="line">      resValue <span class="string">&quot;string&quot;</span>, <span class="string">&quot;CodePushDeploymentKey&quot;</span>, <span class="string">&#x27;&quot;&quot;&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">    release &#123;</span><br><span class="line">      <span class="comment">// Caution! In production, you need to generate your own keystore file.</span></span><br><span class="line">      <span class="comment">// see https://facebook.github.io/react-native/docs/signed-apk-android.</span></span><br><span class="line">      signingConfig signingConfigs.release</span><br><span class="line">      minifyEnabled enableProguardInReleaseBuilds</span><br><span class="line">      proguardFiles getDefaultProguardFile(<span class="string">&quot;proguard-android.txt&quot;</span>), <span class="string">&quot;proguard-rules.pro&quot;</span></span><br><span class="line">      resValue <span class="string">&quot;string&quot;</span>, <span class="string">&quot;CodePushDeploymentKey&quot;</span>, <span class="string">&#x27;&quot;&quot;&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">    releaseStaging.initWith(release)</span><br><span class="line">    releaseStaging &#123;</span><br><span class="line">        resValue <span class="string">&quot;string&quot;</span>, <span class="string">&quot;CodePushDeploymentKey&quot;</span>, <span class="string">&#x27;&quot;&quot;&#x27;</span></span><br><span class="line">        <span class="comment">// Note: It is a good idea to provide matchingFallbacks for the new buildType you create to prevent build issues</span></span><br><span class="line">        <span class="comment">// Add the following line if not already there</span></span><br><span class="line">        matchingFallbacks = [<span class="string">&#x27;release&#x27;</span>]</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="动态部署分配"><a href="#动态部署分配" class="headerlink" title="动态部署分配"></a>动态部署分配</h2><p>上一节说明了如何利用多个<code>CodePush</code>部署，以便在更新发布给用户之前，有效地测试您的更新内容。 但是，由于该工作流静态地将部署分配嵌入到实际二进制文件中，因此<code>临时构建</code>和<code>生产构建</code>只会同步该部署的更新内容。</p><p>在许多情况下，这是足够的，因为您只希望您的团队，客户，利益相关者等与您的预生产版本同步，因此，他们只需要知道如何与该版本同步构建。</p><p>但是，如果你希望能够进行 <code>A / B</code> 测试，或者为某些用户提供应用程序的早期访问权限，那么能够在运行时将特定用户（或受众）动态地置于特定部署中将非常有用。</p><p>为了实现此工作流程，你需要做的就是指定调用该<code>codePush</code>方法时希望当前用户与之同步的部署密钥。指定后，此密钥将覆盖应用程序的 <code>Info.plist</code>（iOS）或 <code>MainActivity.java</code>（Android）文件中提供的“默认”密钥。这允许您生成临时或生产构建，也可以根据需要动态“重定向”。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Imagine that &quot;userProfile&quot; is a prop that this component received</span></span><br><span class="line"><span class="comment">// which includes the deployment key that the current user should use.</span></span><br><span class="line">codePush.<span class="title function_">sync</span>(&#123; <span class="attr">deploymentKey</span>: userProfile.<span class="property">CODEPUSH_KEY</span> &#125;);</span><br></pre></td></tr></table></figure><p>有了这样的变化后，现在只需选择应用程序如何为当前用户配置正确的部署密钥。 在实践中，通常有两种解决方案：</p><ol><li>将更改部署的功能开放给用户。例如，您的设置页面可能会有一个切换按钮以启用“测试版”的访问权限。 如果您不在乎预生产更新的内容被得知，并且您的某些用户可能希望根据自己的意愿选择使用最新（并且可能有错误）的更新（有点像Chrome渠道）。 但是，此解决方案将决策权交给您的用户，这无法帮助您透明地执行 <code>A / B</code> 测试。</li><li>使用额外的元数据注释用户的服务器端配置文件，标明与其同步的部署。 默认情况下，您的应用只能使用二进制嵌入密钥，但在用户通过身份验证后，您的服务器可以选择将其“重定向”到其他部署，这样您就可以根据需要逐步将某些用户或组放置在不同的部署中。您甚至可以选择将服务器响应存储在本地存储中，以使其成为新的默认值。 如何将密钥与用户的配置文件一起存储完全取决于您的身份验证解决方案（例如 <code>Auth0</code>，<code>Firebase</code>，自定义<code>DB</code> + <code>REST API</code>），但这通常非常简单。</li></ol><blockquote><p>注意：如果需要，您还可以实施混合解决方案，允许最终用户在不同部署之间切换，同时还允许您的服务器覆盖该决策。 这样，您就拥有了“部署解决方案”的层次结构，可确保您的应用程序能够自行更新，用户可以通过获得最新内容的访问权限来获得最新体验，但您也有能力根据需要对用户进行 <code>A / B</code> 测试。</p></blockquote><p>由于我们建议将<code>Staging</code>部署用于更新的预发布测试（如上一节中所述），因此使用该部署对用户执行 <code>A / B</code> 测试并不一定有意义，与此相反，你应该允许早期访问（如上面选项1中所述）。因此，我们建议充分利用自定义应用程序部署，以便您可以按用户需求对用户进行细分。例如，您可以创建长期甚至是一次性的部署，向其发布应用程序的变体，然后将某些用户放入其中，以查看其参与度。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">// <span class="comment">#1) Create your new deployment to hold releases of a specific app variant</span></span><br><span class="line">appcenter codepush deployment add -a &lt;ownerName&gt;/&lt;appName&gt; test-variant-one</span><br><span class="line"></span><br><span class="line">// <span class="comment">#2) Target any new releases at that custom deployment</span></span><br><span class="line">appcenter codepush release-react -a &lt;ownerName&gt;/&lt;appName&gt; -d test-variant-one</span><br></pre></td></tr></table></figure><blockquote><p>注意：从一个部署“切换”到另一个部署的用户数，被纳入到部署中的“安装度量”中报告的总用户数。例如，如果您的<code>Production</code> 部署当前报告的用户总数为1，但您将该用户动态切换为 <code>Staging</code> 部署，则 <code>Production</code> 部署将报告 0个总用户，而 <code>Staging</code> 部署将报告1（刚刚切换的用户）。 即使在使用基于运行时的部署重定向解决方案的情况下，这种行为可以让你准确地跟踪您的版本使用情况。</p></blockquote><h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><blockquote><p>源码：<a href="https://github.com/youngjuning/AppCenterCodePushDemo">https://github.com/youngjuning/AppCenterCodePushDemo</a></p></blockquote><h3 id="App-js"><a href="#App-js" class="headerlink" title="App.js"></a>App.js</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span>, &#123;useEffect&#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">View</span>, <span class="title class_">StyleSheet</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;react-native&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> codePush <span class="keyword">from</span> <span class="string">&#x27;react-native-code-push&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">AwesomeButton</span> <span class="keyword">from</span> <span class="string">&#x27;react-native-really-awesome-button&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;codePushSync, checkForUpdate&#125; <span class="keyword">from</span> <span class="string">&#x27;./CodePushUtils&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">App</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">getUpdateMetadata</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> running = <span class="keyword">await</span> codePush.<span class="title function_">getUpdateMetadata</span>(</span><br><span class="line">      codePush.<span class="property">UpdateState</span>.<span class="property">RUNNING</span>,</span><br><span class="line">    );</span><br><span class="line">    <span class="keyword">const</span> pending = <span class="keyword">await</span> codePush.<span class="title function_">getUpdateMetadata</span>(</span><br><span class="line">      codePush.<span class="property">UpdateState</span>.<span class="property">PENDING</span>,</span><br><span class="line">    );</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;[CodePush] running&#x27;</span>, running);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;[CodePush] pending&#x27;</span>, pending);</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">codePushSync</span>();</span><br><span class="line">  &#125;, []);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">View</span> <span class="attr">style</span>=<span class="string">&#123;styles.container&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">AwesomeButton</span> <span class="attr">onPress</span>=<span class="string">&#123;checkForUpdate&#125;</span> <span class="attr">style</span>=<span class="string">&#123;&#123;marginBottom:</span> <span class="attr">10</span>&#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">        Check For Update!</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">AwesomeButton</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">AwesomeButton</span> <span class="attr">onPress</span>=<span class="string">&#123;()</span> =&gt;</span> codePush.clearUpdates()&#125; style=&#123;&#123;marginBottom: 10&#125;&#125;&gt;</span></span><br><span class="line"><span class="language-xml">        Clear Updates!</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">AwesomeButton</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">AwesomeButton</span> <span class="attr">onPress</span>=<span class="string">&#123;getUpdateMetadata&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        getUpdateMetadata!</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">AwesomeButton</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">View</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> styles = <span class="title class_">StyleSheet</span>.<span class="title function_">create</span>(&#123;</span><br><span class="line">  <span class="attr">container</span>: &#123;</span><br><span class="line">    <span class="attr">flex</span>: <span class="number">1</span>,</span><br><span class="line">    <span class="attr">justifyContent</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">    <span class="attr">alignItems</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure><h3 id="CodePushUtils-js"><a href="#CodePushUtils-js" class="headerlink" title="CodePushUtils.js"></a>CodePushUtils.js</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">AppState</span>, <span class="title class_">Platform</span>, <span class="title class_">Alert</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;react-native&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> codePush <span class="keyword">from</span> <span class="string">&#x27;react-native-code-push&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> configReader <span class="keyword">from</span> <span class="string">&#x27;react-native-config-reader&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">CodePushDeploymentKey</span> = &#123;</span><br><span class="line">  <span class="attr">ios</span>: &#123;</span><br><span class="line">    <span class="attr">debug</span>: <span class="string">&#x27;&#x27;</span>,</span><br><span class="line">    <span class="attr">staging</span>: <span class="string">&#x27;944zuIiRSds-ZZY6AQF82aRl0b1vUL_mMxiie&#x27;</span>,</span><br><span class="line">    <span class="attr">release</span>: <span class="string">&#x27;yyJfk2vtpLUUlOCg3FnvCcky9o4U1lEWR1UJV&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">android</span>: &#123;</span><br><span class="line">    <span class="attr">debug</span>: <span class="string">&#x27;&#x27;</span>,</span><br><span class="line">    <span class="attr">releasestaging</span>: <span class="string">&#x27;tOncLvKACzzSkUaML9tCOUfPZxHVnobfaNIUe&#x27;</span>,</span><br><span class="line">    <span class="attr">release</span>: <span class="string">&#x27;Gtc4iXTPn24yu6CBrbl_V2GTy21xtdQyfm6x1&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">getDeploymentKey</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> buildType = configReader.<span class="property">BUILD_TYPE</span>.<span class="title function_">toLowerCase</span>();</span><br><span class="line">  <span class="keyword">const</span> deploymentKey = <span class="title class_">CodePushDeploymentKey</span>[<span class="title class_">Platform</span>.<span class="property">OS</span>][buildType];</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;[CodePushUtils]&#x27;</span>, deploymentKey);</span><br><span class="line">  <span class="keyword">return</span> deploymentKey;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">codePushStatusDidChange</span> = <span class="keyword">async</span> syncStatus =&gt; &#123;</span><br><span class="line">  <span class="keyword">switch</span> (syncStatus) &#123;</span><br><span class="line">    <span class="keyword">case</span> codePush.<span class="property">SyncStatus</span>.<span class="property">CHECKING_FOR_UPDATE</span>:</span><br><span class="line">      <span class="comment">// 0 - 正在查询CodePush服务器以进行更新。</span></span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">info</span>(<span class="string">&#x27;[CodePush] Checking for update.&#x27;</span>);</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> codePush.<span class="property">SyncStatus</span>.<span class="property">AWAITING_USER_ACTION</span>:</span><br><span class="line">      <span class="comment">// 1 - 有可用的更新，并且向最终用户显示了一个确认对话框。（仅在updateDialog使用时适用）</span></span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">info</span>(<span class="string">&#x27;[CodePush] Awaiting user action.&#x27;</span>);</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> codePush.<span class="property">SyncStatus</span>.<span class="property">DOWNLOADING_PACKAGE</span>:</span><br><span class="line">      <span class="comment">// 2 - 正在从CodePush服务器下载可用更新。</span></span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">info</span>(<span class="string">&#x27;[CodePush] Downloading package.&#x27;</span>);</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> codePush.<span class="property">SyncStatus</span>.<span class="property">INSTALLING_UPDATE</span>:</span><br><span class="line">      <span class="comment">// 3 - 已下载一个可用的更新，并将其安装。</span></span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">info</span>(<span class="string">&#x27;[CodePush] Installing update.&#x27;</span>);</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> codePush.<span class="property">SyncStatus</span>.<span class="property">UP_TO_DATE</span>:</span><br><span class="line">      <span class="comment">// 4 - 应用程序已配置的部署完全最新。</span></span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">info</span>(<span class="string">&#x27;[CodePush] App is up to date.&#x27;</span>);</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> codePush.<span class="property">SyncStatus</span>.<span class="property">UPDATE_IGNORED</span>:</span><br><span class="line">      <span class="comment">// 5 该应用程序具有可选更新，最终用户选择忽略该更新。（仅在updateDialog使用时适用）</span></span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">info</span>(<span class="string">&#x27;[CodePush] User cancelled the update.&#x27;</span>);</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> codePush.<span class="property">SyncStatus</span>.<span class="property">UPDATE_INSTALLED</span>:</span><br><span class="line">      <span class="comment">// 6 - 安装了一个可用的更新，它将根据 SyncOptions 中的 InstallMode指定在 syncStatusChangedCallback 函数返回后立即或在下次应用恢复/重新启动时立即运行。</span></span><br><span class="line"></span><br><span class="line">      <span class="comment">// 由于安装了CodePush更新，暂时禁止任何程序性重启。这是高级API，当您应用中的组件（例如，入职流程）需要确保在其生命周期内不会出现最终用户中断时，此功能非常有用。</span></span><br><span class="line">      <span class="comment">// codePush.disallowRestart();</span></span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">info</span>(<span class="string">&#x27;[CodePush] Installed update.&#x27;</span>);</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> codePush.<span class="property">SyncStatus</span>.<span class="property">SYNC_IN_PROGRESS</span>:</span><br><span class="line">      <span class="comment">// 7 - 正在执行的 sync 操作</span></span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">info</span>(<span class="string">&#x27;[CodePush] Sync already in progress.&#x27;</span>);</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> codePush.<span class="property">SyncStatus</span>.<span class="property">UNKNOWN_ERROR</span>:</span><br><span class="line">      <span class="comment">// -1 - 同步操作遇到未知错误。</span></span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">info</span>(<span class="string">&#x27;[CodePush] An unknown error occurred.&#x27;</span>);</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">codePushDownloadDidProgress</span> = progress =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> curPercent = (</span><br><span class="line">    (progress.<span class="property">receivedBytes</span> / progress.<span class="property">totalBytes</span>) *</span><br><span class="line">    <span class="number">100</span></span><br><span class="line">  ).<span class="title function_">toFixed</span>(<span class="number">0</span>);</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;[CodePushUtils] Downloading Progress&#x27;</span>, <span class="string">`<span class="subst">$&#123;curPercent&#125;</span>%`</span>);</span><br><span class="line">  <span class="comment">// console.log(`$&#123;progress.receivedBytes&#125; of $&#123;progress.totalBytes&#125; received.`);</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">syncImmediate</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> deploymentKey = <span class="title function_">getDeploymentKey</span>();</span><br><span class="line">  codePush.<span class="title function_">sync</span>(</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">updateDialog</span>: &#123;</span><br><span class="line">        <span class="comment">// 是否显示更新描述</span></span><br><span class="line">        <span class="attr">appendReleaseDescription</span>: <span class="literal">true</span>,</span><br><span class="line">        <span class="comment">// 更新描述的前缀。 默认为&quot;Description&quot;</span></span><br><span class="line">        <span class="attr">descriptionPrefix</span>: <span class="string">&#x27;\n\n更新内容：\n&#x27;</span>,</span><br><span class="line">        <span class="comment">// 强制更新按钮文字，默认为continue</span></span><br><span class="line">        <span class="attr">mandatoryContinueButtonLabel</span>: <span class="string">&#x27;立即更新&#x27;</span>,</span><br><span class="line">        <span class="comment">// 强制更新时的信息. 默认为&quot;An update is available that must be installed.&quot;</span></span><br><span class="line">        <span class="attr">mandatoryUpdateMessage</span>: <span class="string">&#x27;必须更新后才能使用&#x27;</span>,</span><br><span class="line">        <span class="comment">// 非强制更新时，按钮文字,默认为&quot;ignore&quot;</span></span><br><span class="line">        <span class="attr">optionalIgnoreButtonLabel</span>: <span class="string">&#x27;稍后&#x27;</span>,</span><br><span class="line">        <span class="comment">// 非强制更新时，确认按钮文字. 默认为&quot;Install&quot;</span></span><br><span class="line">        <span class="attr">optionalInstallButtonLabel</span>: <span class="string">&#x27;后台更新&#x27;</span>,</span><br><span class="line">        <span class="comment">// 非强制更新时，检查到更新的消息文本</span></span><br><span class="line">        <span class="attr">optionalUpdateMessage</span>: <span class="string">&#x27;有新版本了，是否更新？&#x27;</span>,</span><br><span class="line">        <span class="comment">// Alert窗口的标题</span></span><br><span class="line">        <span class="attr">title</span>: <span class="string">&#x27;更新&#x27;</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">      deploymentKey,</span><br><span class="line">      <span class="attr">installMode</span>: codePush.<span class="property">InstallMode</span>.<span class="property">IMMEDIATE</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    codePushStatusDidChange,</span><br><span class="line">    codePushDownloadDidProgress,</span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">checkForUpdate</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> deploymentKey = <span class="title function_">getDeploymentKey</span>();</span><br><span class="line">  <span class="keyword">const</span> update = <span class="keyword">await</span> codePush.<span class="title function_">checkForUpdate</span>(deploymentKey);</span><br><span class="line">  <span class="keyword">if</span> (!update) &#123;</span><br><span class="line">    <span class="title class_">Alert</span>.<span class="title function_">alert</span>(<span class="string">&#x27;提示&#x27;</span>, <span class="string">&#x27;已是最新版本&#x27;</span>);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="title function_">syncImmediate</span>();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">codePushSync</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="title function_">syncImmediate</span>();</span><br><span class="line">  <span class="title class_">AppState</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;change&#x27;</span>, <span class="function"><span class="params">newState</span> =&gt;</span> &#123;</span><br><span class="line">    newState === <span class="string">&#x27;active&#x27;</span> &amp;&amp; <span class="title function_">syncImmediate</span>();</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="Npm-Scripts"><a href="#Npm-Scripts" class="headerlink" title="Npm Scripts"></a>Npm Scripts</h3><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    ...</span><br><span class="line">    <span class="attr">&quot;gradle:clean&quot;</span><span class="punctuation">:</span> <span class="string">&quot;cd android &amp;&amp; ./gradlew clean&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;an:release&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn gradle:clean &amp;&amp; cd android &amp;&amp; ./gradlew app:assembleRelease&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;an:installRelease&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn gradle:clean &amp;&amp; cd android &amp;&amp; ./gradlew app:installRelease&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;an:staging&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn gradle:clean &amp;&amp; cd android &amp;&amp; ./gradlew app:assembleReleaseStaging&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;an:installStaging&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn gradle:clean &amp;&amp; cd android &amp;&amp; ./gradlew app:installReleaseStaging&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;displayKeys&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn disPlayIosKeys &amp;&amp; yarn disPlayAndroidKeys&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;disPlayIosKeys&quot;</span><span class="punctuation">:</span> <span class="string">&quot;appcenter codepush deployment list --app youngjuning/AppCenterCodePushDemo-iOS --displayKeys&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;disPlayAndroidKeys&quot;</span><span class="punctuation">:</span> <span class="string">&quot;appcenter codepush deployment list --app youngjuning/AppCenterCodePushDemo-Android --displayKeys&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;release-react&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn release-react-ios &amp;&amp; yarn release-react-android&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;release-react-ios&quot;</span><span class="punctuation">:</span> <span class="string">&quot;appcenter codepush release-react --app youngjuning/AppCenterCodePushDemo-iOS&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;release-react-android&quot;</span><span class="punctuation">:</span> <span class="string">&quot;appcenter codepush release-react --app youngjuning/AppCenterCodePushDemo-Android&quot;</span></span><br><span class="line">    ...</span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/2233b15714d5/</id>
    <link href="https://blog.zisheng.pro/posts/2233b15714d5/"/>
    <published>2020-05-30T13:59:19.000Z</published>
    <summary>本文记录了一些 React Native 热更新的搭建过程</summary>
    <title>React Native 热更新不求人</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="运维" scheme="https://blog.zisheng.pro/categories/%E8%BF%90%E7%BB%B4/"/>
    <category term="android" scheme="https://blog.zisheng.pro/tags/android/"/>
    <category term="Jenkins" scheme="https://blog.zisheng.pro/tags/Jenkins/"/>
    <category term="docker" scheme="https://blog.zisheng.pro/tags/docker/"/>
    <content>
      <![CDATA[<p>Docker 安装配置请参考我的另一篇文章: <a href="https://juejin.im/post/5e839f2851882573ab44f1b4#heading-11">https://juejin.im/post/5e839f2851882573ab44f1b4#heading-11</a></p><h2 id="安装Jenkins"><a href="#安装Jenkins" class="headerlink" title="安装Jenkins"></a>安装Jenkins</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker run -d \</span></span><br><span class="line"><span class="language-bash"> -u root \</span></span><br><span class="line"><span class="language-bash"> -p 8080:8080 -p 50000:50000 \</span></span><br><span class="line"><span class="language-bash"> -v jenkins_home:/var/jenkins_home \</span></span><br><span class="line"><span class="language-bash"> -e PLUGINS_FORCE_UPGRADE=<span class="literal">true</span> \</span></span><br><span class="line"><span class="language-bash"> -e TRY_UPGRADE_IF_NO_MARKER=<span class="literal">true</span> \</span></span><br><span class="line"><span class="language-bash"> -e JAVA_OPTS=-Duser.timezone=Asia/Shanghai \</span></span><br><span class="line"><span class="language-bash"> --name jenkins \</span></span><br><span class="line"><span class="language-bash"> --restart always \</span></span><br><span class="line"><span class="language-bash"> jenkins/jenkins</span></span><br></pre></td></tr></table></figure><p>需要更多定制参数你可以参考 <a href="https://github.com/jenkinsci/docker">https://github.com/jenkinsci/docker</a></p><h2 id="Nginx-反向代理"><a href="#Nginx-反向代理" class="headerlink" title="Nginx 反向代理"></a>Nginx 反向代理</h2><p>我没有域名，所以我的配置是基于端口的，你可以查看<a href="https://github.com/jenkinsci/docker/blob/master/README.md">官方文档</a> 获取更多方案：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">server</span> &#123;</span><br><span class="line"><span class="attribute">listen</span> <span class="number">8882</span>;</span><br><span class="line"><span class="attribute">server_name</span> _;</span><br><span class="line"></span><br><span class="line"><span class="section">location</span> / &#123;</span><br><span class="line"><span class="attribute">proxy_set_header</span>        Host <span class="variable">$host</span>:<span class="variable">$server_port</span>;</span><br><span class="line"><span class="attribute">proxy_set_header</span>        X-Real-IP <span class="variable">$remote_addr</span>;</span><br><span class="line"><span class="attribute">proxy_set_header</span>        X-Forwarded-For <span class="variable">$proxy_add_x_forwarded_for</span>;</span><br><span class="line"><span class="attribute">proxy_set_header</span>        X-Forwarded-Proto <span class="variable">$scheme</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment"># Fix the &quot;It appears that your reverse proxy set up is broken&quot; error.</span></span><br><span class="line"><span class="attribute">proxy_pass</span>          http://127.0.0.1:8080;</span><br><span class="line"><span class="attribute">proxy_read_timeout</span>  <span class="number">90</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment"># Required for new HTTP-based CLI</span></span><br><span class="line"><span class="attribute">proxy_http_version</span> <span class="number">1</span>.<span class="number">1</span>;</span><br><span class="line"><span class="attribute">proxy_request_buffering</span> <span class="literal">off</span>;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="初始化jenkins"><a href="#初始化jenkins" class="headerlink" title="初始化jenkins"></a>初始化jenkins</h2><p>1、执行<code>cat /var/lib/docker/volumes/jenkins_home/_data/secrets/initialAdminPassword</code>，获取<strong>管理员密码</strong></p><p>2、启动完jenkins后通过浏览器输入地址<code>http://部署jenkins主机IP:端口</code></p><img src="https://i.loli.net/2020/04/10/YtRrWTZVQc7Mma3.png" style="zoom:75%;" /><p>3、选择安装插件方式，这里我是默认第一个：</p><img src="https://i.loli.net/2020/04/10/IBlrbMFusXmdeA5.png" style="zoom:75%;" /><p>4、进入插件安装界面，连网等待插件安装：</p><img src="https://i.loli.net/2020/04/10/gimNZtUID5YjK9f.png" style="zoom:75%;" /><p>5、安装完插件后，进入创建管理员界面：</p><img src="https://i.loli.net/2020/04/10/SxvtAW3McBCqTEX.png" style="zoom:75%;" /><h2 id="基于角色的认证策略"><a href="#基于角色的认证策略" class="headerlink" title="基于角色的认证策略"></a>基于角色的认证策略</h2><ol><li><p>请确保在 <strong>系统管理</strong> -&gt; <strong>插件管理</strong> 中安装了<strong>Role-based Authorization Strategy</strong>  插件！！</p></li><li><p>在 <strong>系统管理</strong> -&gt; <strong>安全</strong> -&gt; <strong>全局安全设置</strong> 中的 <strong>授权策略</strong> 配置项中选择 <strong>Role-Based Strategy</strong></p><p><img src="https://i.loli.net/2020/05/09/FiR59LEtqwCbDIh.png" alt="紫升"></p></li><li><p>然后就可以在 <code>Manage and Assign Roles</code> 中管理角色</p></li></ol><h2 id="全局工具配置"><a href="#全局工具配置" class="headerlink" title="全局工具配置"></a>全局工具配置</h2><h3 id="JDK"><a href="#JDK" class="headerlink" title="JDK"></a>JDK</h3><blockquote><p>自动安装请先到Oracle注册账号</p></blockquote><p><img src="https://i.loli.net/2020/05/09/jbzQ15qthxmTu9Z.png" alt="紫升"></p><h3 id="Git"><a href="#Git" class="headerlink" title="Git"></a>Git</h3><blockquote><p>需要先执行 <code>apt install git</code> 安装git，然后通过 <code>which git</code> 查看 git 安装位置！</p></blockquote><p><img src="https://i.loli.net/2020/05/09/lLSYIN5FARQ38uH.png" alt="紫升"></p><h3 id="NodeJS"><a href="#NodeJS" class="headerlink" title="NodeJS"></a>NodeJS</h3><blockquote><p>需要先需要安装 <a href="https://plugins.jenkins.io/nodejs">NodeJS Plugin</a> 插件</p></blockquote><p><img src="https://i.loli.net/2020/05/09/B9R7pgIXd5OSCbq.png" alt="紫升"></p><h2 id="Android-打包环境"><a href="#Android-打包环境" class="headerlink" title="Android 打包环境"></a>Android 打包环境</h2><h3 id="安装-android-sdk、sdkmagager"><a href="#安装-android-sdk、sdkmagager" class="headerlink" title="安装 android-sdk、sdkmagager"></a>安装 android-sdk、sdkmagager</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">apt update | apt install android-sdk</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">apt install snapd</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">snap install androidsdk</span></span><br></pre></td></tr></table></figure><h3 id="复制android-sdk到-jenkins-volume"><a href="#复制android-sdk到-jenkins-volume" class="headerlink" title="复制android-sdk到 jenkins volume"></a>复制android-sdk到 jenkins volume</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">cp</span> -R /usr/lib/android-sdk/ /var/lib/docker/volume/jenkins_home/_data/</span></span><br></pre></td></tr></table></figure><p>android-sdk 的安装位置可以通过 <code>adb --version</code> 查看：</p><img src="https://user-gold-cdn.xitu.io/2020/5/9/171f7fae7cf4ff7a?w=864&h=230&f=png&s=176444" alt="" style="zoom: 75%;" /><h3 id="设置环境变量"><a href="#设置环境变量" class="headerlink" title="设置环境变量"></a>设置环境变量</h3><p>将下面的的配置加入到 <code>~/.bash_profile</code> 或 <code>~/.zshrc</code> 中，并执行 <code>srouce ~/.bash_profile</code> 或 <code>srouce ~/.zshrc</code>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">export ANDROID_HOME=/var/lib/docker/volumes/jenkins_home/_data/android-sdk</span><br><span class="line">export PATH=$PATH:$ANDROID_HOME/tools</span><br><span class="line">export PATH=$PATH:$ANDROID_HOME/tools/bin</span><br><span class="line">export PATH=$PATH:$ANDROID_HOME/platform-tools</span><br><span class="line">export PATH=$PATH:$ANDROID_HOME/emulator</span><br></pre></td></tr></table></figure><h3 id="accepted-licences"><a href="#accepted-licences" class="headerlink" title="accepted licences"></a>accepted licences</h3><blockquote><p>如果不授权会报错： Failed to install the following Android SDK packages as some licences have not been accepted.</p></blockquote><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">yes</span> | sdkmanager --sdk_root=<span class="variable">$&#123;ANDROID_HOME&#125;</span> --licenses</span></span><br></pre></td></tr></table></figure><blockquote><p>如果不指定 <code>--sdk_root=${ANDROID_HOME}</code> 会报错：<a href="https://stackoverflow.com/questions/60730615/sdkmanager-warning-could-not-create-setting-java-lang-illegalargumentexcepti">SDKmanager : Warning: Could not create setting. java.lang.IllegalArgumentException</a></p></blockquote><h3 id="设置-Jenkins-ANDROID-HOME"><a href="#设置-Jenkins-ANDROID-HOME" class="headerlink" title="设置 Jenkins ANDROID_HOME"></a>设置 Jenkins ANDROID_HOME</h3><p>进入 <strong>系统管理</strong> -&gt; <strong>系统配置</strong> -&gt; <strong>全局属性</strong> 新增环境变量 <code>ANDROID_HOME</code>：</p><p><img src="https://i.loli.net/2020/05/09/jWANFZh8xCD1Sgm.png" alt="紫升"></p><h2 id="GitHub-Plugin"><a href="#GitHub-Plugin" class="headerlink" title="GitHub Plugin"></a>GitHub Plugin</h2><p>这个插件的作用是集成 GitHub 到 Jenkins</p><h3 id="获取-GitHub-的-Personal-access-token"><a href="#获取-GitHub-的-Personal-access-token" class="headerlink" title="获取 GitHub 的 Personal access token"></a>获取 GitHub 的 Personal access token</h3><blockquote><p><strong>GitHub 主页</strong> -&gt; <strong>Settings</strong> -&gt; <strong>Developer settings</strong> -&gt; <strong>Personal access tokens</strong></p></blockquote><p><img src="https://i.loli.net/2020/05/09/qTLBC359Z2rNday.png" alt="紫升"></p><h3 id="添加Github服务器"><a href="#添加Github服务器" class="headerlink" title="添加Github服务器"></a>添加Github服务器</h3><blockquote><p><strong>系统管理</strong> -&gt; <strong>系统设置</strong> -&gt; <strong>GitHub</strong> -&gt; <strong>添加Github服务器</strong></p></blockquote><p>这里的名称随便填，API URL 填写 <code>https://api.github.com/</code>：</p><p><img src="https://i.loli.net/2020/05/09/1FLEfTHhpexyXQW.png" alt="紫升"></p><p>凭证类型选 <code>Secret text</code>：</p><p><img src="https://i.loli.net/2020/05/09/3Oypr4xniKZamG9.png" alt="紫升"></p><p>在凭据选上刚刚你添加的，勾上管理 Hook，点击“连接测试”，成功之后如下所示：</p><p><img src="https://i.loli.net/2020/05/09/pBWKZMfq5IkGvyz.png" alt="紫升"></p><h3 id="配置-GitHub-的-webhook-地址"><a href="#配置-GitHub-的-webhook-地址" class="headerlink" title="配置 GitHub 的 webhook 地址"></a>配置 GitHub 的 webhook 地址</h3><p>webhook 是通知 Jenkins 时的请求地址，用来填写到 GitHub 上，这样 GitHub 就能通过该地址通知到 Jenkins</p><p>假设Jenkins所在服务器的地址是：192.168.0.1，端口为8080，那么webhook地址就是 <code>http://192.168.0.1:8080/github-webhook/</code></p><p><img src="https://i.loli.net/2020/05/09/dU7AeiX13mWHqTp.png" alt="image-20200509175614040"></p><p>在 Payload URL 位置填入 webhook 地址，再点击底部的 Add webhook 按钮，这样就完成 webhook 配置了，今后当前工程有代码提交，GitHub 就会向此 webhook 地址发请求，通知 Jenkins 构建：</p><p><img src="https://i.loli.net/2020/05/09/BqoXjUQsile3K7A.png" alt="紫升"></p><h3 id="任务配置"><a href="#任务配置" class="headerlink" title="任务配置"></a>任务配置</h3><ul><li><p>构造触发器选择：GitHub hook trigger for GITScm polling</p><p><img src="https://i.loli.net/2020/05/09/EF5DYKJHgOPayxA.png" alt="紫升"></p></li><li><p>构建环境和绑定</p><p>如下图所示，勾选 Use secret text(s) or file(s)，下面的”凭据”选择我们之前配置过的凭证</p><p><img src="https://i.loli.net/2020/05/09/2noTEULmhAzd7Dg.png" alt="紫升"></p></li></ul><h2 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h2><h3 id="修改默认时区为上海时区"><a href="#修改默认时区为上海时区" class="headerlink" title="修改默认时区为上海时区"></a>修改默认时区为上海时区</h3><p>在 <strong>系统管理</strong> -&gt; <strong>脚本命令行</strong> 里运行 <code>System.setProperty(&#39;org.apache.commons.jelly.tags.fmt.timeZone&#39;, &#39;Asia/Shanghai&#39;)</code></p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/f0d2184d4dfe/</id>
    <link href="https://blog.zisheng.pro/posts/f0d2184d4dfe/"/>
    <published>2020-05-08T10:16:43.000Z</published>
    <summary>本文介绍了如何通过 Docker 安装配置 Jenkins</summary>
    <title>使用 Docker 安装配置 Jenkins</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="工具" scheme="https://blog.zisheng.pro/categories/%E5%B7%A5%E5%85%B7/"/>
    <category term="typora" scheme="https://blog.zisheng.pro/tags/typora/"/>
    <category term="markdown" scheme="https://blog.zisheng.pro/tags/markdown/"/>
    <category term="图床" scheme="https://blog.zisheng.pro/tags/%E5%9B%BE%E5%BA%8A/"/>
    <content>
      <![CDATA[<h2 id="下载-PicGo-Core"><a href="#下载-PicGo-Core" class="headerlink" title="下载 PicGo-Core"></a>下载 PicGo-Core</h2><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ npm install picgo -g</span><br><span class="line"><span class="comment"># or</span></span><br><span class="line">$ yarn global add picgo</span><br></pre></td></tr></table></figure><h2 id="获取路径"><a href="#获取路径" class="headerlink" title="获取路径"></a>获取路径</h2><ul><li>node安装路径（<code>which node</code>）：<code>/usr/local/bin/node</code></li><li>picgo安装路径（<code>which picgo</code>）：<code>/usr/local/bin/picgo</code></li></ul><h3 id="Imgae-Upldoad-Setting"><a href="#Imgae-Upldoad-Setting" class="headerlink" title="Imgae Upldoad Setting"></a>Imgae Upldoad Setting</h3><blockquote><p>打开 Typora -&gt; 偏好设置 -&gt; 图像：</p></blockquote><p>上传服务选择“Custom Command”，自定义命令格式是 “[your node path] [your picgo-core path] upload”，比如我的是 <code>/usr/local/bin/node /usr/local/bin/picgo upload </code></p><p><img src="https://i.loli.net/2020/04/10/aCFG5ZW2xuR6qls.png" alt="紫升"></p><h2 id="配置-sm-ms-api-token"><a href="#配置-sm-ms-api-token" class="headerlink" title="配置 sm.ms api token"></a>配置 sm.ms api token</h2><p>picgo 的默认配置文件为<code>~/.picgo/config.json</code>。其中<code>~</code>为用户目录。不同系统的用户目录不太一样。</p><p>linux 和 macOS 均为<code>~/.picgo/config.json</code>。</p><p>windows 则为<code>C:\Users\你的用户名/.picgo\config.json</code>。</p><p>配置文件需要至少有如下的配置项：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;picBed&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;uploader&quot;</span><span class="punctuation">:</span> <span class="string">&quot;smms&quot;</span><span class="punctuation">,</span> <span class="comment">// 代表当前的默认上传图床为 SM.MS,</span></span><br><span class="line">    <span class="attr">&quot;smms&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;token&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&quot;</span> <span class="comment">// 从https://sm.ms/home/apitoken获取的token</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;picgoPlugins&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="punctuation">&#125;</span> <span class="comment">// 为插件预留</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="验证"><a href="#验证" class="headerlink" title="验证"></a>验证</h2><p>点击<strong>验证图片上传选项</strong> 按钮</p><p><img src="https://i.loli.net/2020/04/10/LYNhz8rACdORjP7.png" alt="image-20200410180446932"></p><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><ul><li><a href="https://picgo.github.io/PicGo-Core-Doc/zh/guide/config.html#%E9%BB%98%E8%AE%A4%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6">Picgo-Core</a></li><li><a href="https://support.typora.io/Upload-Image/">Typora Upload Image</a></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/cc88fa106758/</id>
    <link href="https://blog.zisheng.pro/posts/cc88fa106758/"/>
    <published>2020-04-10T09:57:41.000Z</published>
    <summary>本文介绍了如何用 Typora 基于 PicGo-Core 实现粘贴上传图片</summary>
    <title>Typora 基于 PicGo-Core 实现粘贴上传图片</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="运维" scheme="https://blog.zisheng.pro/categories/%E8%BF%90%E7%BB%B4/"/>
    <category term="Docker" scheme="https://blog.zisheng.pro/tags/Docker/"/>
    <category term="笔记" scheme="https://blog.zisheng.pro/tags/%E7%AC%94%E8%AE%B0/"/>
    <category term="紫升的笔记" scheme="https://blog.zisheng.pro/tags/%E7%B4%AB%E5%8D%87%E7%9A%84%E7%AC%94%E8%AE%B0/"/>
    <category term="DevOps" scheme="https://blog.zisheng.pro/tags/DevOps/"/>
    <category term="Container" scheme="https://blog.zisheng.pro/tags/Container/"/>
    <category term="容器" scheme="https://blog.zisheng.pro/tags/%E5%AE%B9%E5%99%A8/"/>
    <category term="daemon" scheme="https://blog.zisheng.pro/tags/daemon/"/>
    <category term="守护进程" scheme="https://blog.zisheng.pro/tags/%E5%AE%88%E6%8A%A4%E8%BF%9B%E7%A8%8B/"/>
    <category term="qshell" scheme="https://blog.zisheng.pro/tags/qshell/"/>
    <category term="crontab" scheme="https://blog.zisheng.pro/tags/crontab/"/>
    <content>
      <![CDATA[<p>Docker 是个划时代的开源项目，它彻底释放了计算虚拟化的威力，极大提高了应用的维护效率，降低了云计算应用开发的成本！使用 Docker，可以让应用的部署、测试和分发都变得前所未有的高效和轻松！</p><p>无论是应用开发者、运维人员、还是其他信息技术从业人员，都有必要认识和掌握 Docker，节约有限的生命。</p><p>本文是笔者以一个前端工程师的视角学习 Docker 过程中的笔记，如果对您有所帮助，荣幸之至。</p><h2 id="Docker-是什么"><a href="#Docker-是什么" class="headerlink" title="Docker 是什么"></a>Docker 是什么</h2><p><strong>Docker</strong> 使用 <code>Google</code> 公司推出的 <a href="https://golang.org/">Go 语言</a> 进行开发实现，基于 <code>Linux</code> 内核的 <a href="https://zh.wikipedia.org/wiki/Cgroups">cgroup</a>，<a href="https://en.wikipedia.org/wiki/Linux_namespaces">namespace</a>，以及 <a href="https://docs.docker.com/storage/storagedriver/overlayfs-driver/">OverlayFS</a> 类的 <a href="https://en.wikipedia.org/wiki/Union_mount">Union FS</a> 等技术，对进程进行封装隔离，属于 <a href="https://en.wikipedia.org/wiki/Operating-system-level_virtualization">操作系统层面的虚拟化技术</a>。由于隔离的进程独立于宿主和其它的隔离的进程，因此也称其为容器。最初实现是基于 <a href="https://linuxcontainers.org/lxc/introduction/">LXC</a>，从 0.7 版本以后开始去除 <code>LXC</code>，转而使用自行开发的 <a href="https://github.com/docker/libcontainer">libcontainer</a>，从 1.11 开始，则进一步演进为使用 <a href="https://github.com/opencontainers/runc">runC</a> 和 <a href="https://github.com/containerd/containerd">containerd</a>。</p><h2 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h2><h3 id="DevOps"><a href="#DevOps" class="headerlink" title="DevOps"></a><strong>DevOps</strong></h3><p>DevOps（<strong>Dev</strong>elopment和<strong>Op</strong>erations的组合词）是一种重视软件开发人员（Dev）和IT运维技术人员（Ops）之间沟通合作的文化、运动或惯例。透过自动化“软件交付”和“架构变更”的流程，来使得构建、测试、发布软件能够更加地快捷、频繁和可靠。</p><p>DevOps 的引入能对产品交付、<a href="https://zh.wikipedia.org/wiki/%E6%B5%8B%E8%AF%95">测试</a>、功能开发和<a href="https://zh.wikipedia.org/wiki/%E8%BB%9F%E9%AB%94%E7%B6%AD%E8%AD%B7">维护</a>（包括曾经罕见但如今已屡见不鲜的“<a href="https://zh.wikipedia.org/wiki/Hot_fix">热补丁</a>”）起到意义深远的影响。在缺乏 DevOps 能力的组织中，开发与运营之间存在着信息“鸿沟”。例如运营人员要求更好的可靠性和安全性，开发人员则希望<a href="https://zh.wikipedia.org/wiki/%E5%9F%BA%E7%A1%80%E8%AE%BE%E6%96%BD">基础设施</a>响应更快，而业务用户的需求则是更快地将更多的特性发布给最终用户使用。这种信息鸿沟就是最常出问题的地方。</p><h3 id="容器"><a href="#容器" class="headerlink" title="容器"></a>容器</h3><p>容器有效地将由单个操作系统管理的资源划分到孤立的组中，以更好地在孤立的组之间平衡有冲突的资源使用需求。与虚拟化相比，这样既不需要指令级模拟，也不需要即时编译。容器可以在核心 CPU 本地运行指令，而不需要任何专门的解释机制。此外，也避免了准虚拟化（para-virtualization）和系统调用替换中的复杂性。</p><h3 id="虚拟化"><a href="#虚拟化" class="headerlink" title="虚拟化"></a>虚拟化</h3><p>在计算机技术中，虚拟化是一种资源管理技术，是将计算机中的各种实体资源，如服务器、网络、内存及存储等，予以抽象、转换后呈现出来，打破实体结构间的不可切割的障碍，使用户可以用比原来的组态更好的方式来应用这些资源。</p><h3 id="Docker-与虚拟机比较"><a href="#Docker-与虚拟机比较" class="headerlink" title="Docker 与虚拟机比较"></a>Docker 与虚拟机比较</h3><table><thead><tr><th>特性</th><th>容器</th><th>虚拟机</th></tr></thead><tbody><tr><td>启动</td><td>秒级</td><td>分钟级</td></tr><tr><td>硬盘使用</td><td>一般为 <code>MB</code></td><td>一般为 <code>GB</code></td></tr><tr><td>性能</td><td>接近原生</td><td>弱于</td></tr><tr><td>系统支持量</td><td>单机支持上千个容器</td><td>一般几十个</td></tr></tbody></table><p>如下图，虚拟机是在硬件层面实现虚拟化，需要额外的虚拟机管理应用和虚拟机操作系统层。Docker容器是在操作系统层面上实现虚拟化，直接复用本地主机的操作系统，因此更加轻量级。</p><p><img src="https://i.loli.net/2020/03/31/mZiyHL2kGAgrMFx.png" alt="Docker 与虚拟机比较"></p><h3 id="Docker核心概念"><a href="#Docker核心概念" class="headerlink" title="Docker核心概念"></a>Docker核心概念</h3><h4 id="镜像（Image）"><a href="#镜像（Image）" class="headerlink" title="镜像（Image）"></a>镜像（Image）</h4><p>我们都知道，操作系统分为内核和用户空间。对于 Linux 而言，内核启动后，会挂载 <code>root</code> 文件系统为其提供用户空间支持。而 Docker 镜像（Image），就相当于是一个 <code>root</code> 文件系统。比如官方镜像 <code>ubuntu:18.04</code> 就包含了完整的一套 Ubuntu 18.04 最小系统的 <code>root</code> 文件系统。</p><p>Docker 镜像是一个特殊的文件系统，除了提供容器运行时所需的程序、库、资源、配置等文件外，还包含了一些为运行时准备的一些配置参数（如匿名卷、环境变量、用户等）。镜像不包含任何动态数据，其内容在构建之后也不会被改变。</p><p><strong>分层存储</strong></p><p>因为镜像包含操作系统完整的 <code>root</code> 文件系统，其体积往往是庞大的，因此在 Docker 设计时，就充分利用 <a href="https://en.wikipedia.org/wiki/Union_mount">Union FS</a> 的技术，将其设计为分层存储的架构。所以严格来说，镜像并非是像一个 ISO 那样的打包文件，镜像只是一个虚拟的概念，其实际体现并非由一个文件组成，而是由一组文件系统组成，或者说，由多层文件系统联合组成。</p><p>镜像构建时，会一层层构建，前一层是后一层的基础。每一层构建完就不会再发生改变，后一层上的任何改变只发生在自己这一层。比如，删除前一层文件的操作，实际不是真的删除前一层的文件，而是仅在当前层标记为该文件已删除。在最终容器运行的时候，虽然不会看到这个文件，但是实际上该文件会一直跟随镜像。因此，在构建镜像的时候，需要额外小心，每一层尽量只包含该层需要添加的东西，任何额外的东西应该在该层构建结束前清理掉。</p><p>分层存储的特征还使得镜像的复用、定制变的更为容易。甚至可以用之前构建好的镜像作为基础层，然后进一步添加新的层，以定制自己所需的内容，构建新的镜像。</p><h4 id="容器（Container）"><a href="#容器（Container）" class="headerlink" title="容器（Container）"></a>容器（Container）</h4><p>镜像（<code>Image</code>）和容器（<code>Container</code>）的关系，就像是面向对象程序设计中的 <code>类</code> 和 <code>实例</code> 一样，镜像是静态的定义，容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。</p><p>容器的实质是进程，但与直接在宿主执行的进程不同，容器进程运行于属于自己的独立的 <a href="https://en.wikipedia.org/wiki/Linux_namespaces">命名空间</a>。因此容器可以拥有自己的 <code>root</code> 文件系统、自己的网络配置、自己的进程空间，甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里，使用起来，就好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。也因为这种隔离的特性，很多人初学 Docker 时常常会混淆容器和虚拟机。</p><p>前面讲过镜像使用的是分层存储，容器也是如此。每一个容器运行时，是以镜像为基础层，在其上创建一个当前容器的存储层，我们可以称这个为容器运行时读写而准备的存储层为 <strong>容器存储层</strong>。</p><p>容器存储层的生存周期和容器一样，容器消亡时，容器存储层也随之消亡。因此，任何保存于容器存储层的信息都会随容器删除而丢失。</p><p>按照 Docker 最佳实践的要求，容器不应该向其存储层内写入任何数据，容器存储层要保持无状态化。所有的文件写入操作，都应该使用 <a href="https://vuepress.mirror.docker-practice.com/data_management/volume.html">数据卷（Volume）</a>、或者绑定宿主目录，在这些位置的读写会跳过容器存储层，直接对宿主（或网络存储）发生读写，其性能和稳定性更高。</p><p>数据卷的生存周期独立于容器，容器消亡，数据卷不会消亡。因此，使用数据卷后，容器删除或者重新运行之后，数据却不会丢失。</p><h4 id="仓库注册服务器（Registry）"><a href="#仓库注册服务器（Registry）" class="headerlink" title="仓库注册服务器（Registry）"></a>仓库注册服务器（Registry）</h4><p>一个 <strong>Docker Registry</strong> 中可以包含多个 <strong>仓库</strong>（<code>Repository</code>）；每个仓库可以包含多个 <strong>标签</strong>（<code>Tag</code>）；每个标签对应一个镜像。</p><p>通常，一个仓库会包含同一个软件不同版本的镜像，而标签就常用于对应该软件的各个版本。我们可以通过 <code>&lt;仓库名&gt;:&lt;标签&gt;</code> 的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签，将以 <code>latest</code> 作为默认标签。</p><p>以 <a href="https://hub.docker.com/_/ubuntu">Ubuntu 镜像</a> 为例，<code>ubuntu</code> 是仓库的名字，其内包含有不同的版本标签，如，<code>16.04</code>, <code>18.04</code>。我们可以通过 <code>ubuntu:16.04</code>，或者 <code>ubuntu:18.04</code> 来具体指定所需哪个版本的镜像。如果忽略了标签，比如 <code>ubuntu</code>，那将视为 <code>ubuntu:latest</code>。</p><p>仓库名经常以 <em>两段式路径</em> 形式出现，比如 <code>jwilder/nginx-proxy</code>，前者往往意味着 Docker Registry 多用户环境下的用户名，后者则往往是对应的软件名。但这并非绝对，取决于所使用的具体 Docker Registry 的软件或服务。</p><p><strong>公有 Docker Registry：</strong></p><ul><li><a href="https://hub.docker.com/">Docker Hub</a></li><li><a href="https://c.163.com/hub#/m/library/">网易云镜像服务</a></li><li><a href="https://hub.daocloud.io/">DaoCloud 镜像市场</a></li><li><a href="https://cr.console.aliyun.com/">aliyun镜像库</a></li></ul><p><strong>私有 Docker Registry：</strong></p><ul><li><a href="https://vuepress.mirror.docker-practice.com/repository/nexus3_registry.html">Sonatype Nexus</a></li><li><a href="https://github.com/goharbor/harbor">Harbor</a></li></ul><h3 id="守护进程-daemon"><a href="#守护进程-daemon" class="headerlink" title="守护进程 daemon"></a>守护进程 daemon</h3><p>在一个多任务的电脑操作系统中，守护进程（daemon）是一种在后台执行的电脑程序。此类程序会被以进程的形式初始化。守护进程程序的名称通常以字母”d“结尾：例如，<code>syslogd</code> 就是指管理系统日志的守护进程。</p><p>通常，守护进程没有任何存在的父进程（即PPID&#x3D;1），且在 UNIX 系统进程层级中直接位于 init 之下。守护进程程序通常通过如下方法是自己成为守护进程：对一个子进程进行 fork，然后使其父进程立即终止，使得这个子进程能在 init 下运行。这种方法通常被称为”脱壳“。</p><p>系统通常在启动时一同引导守护进程。守护进程为对网络请求，硬件活动等进行响应，或其他通过某些任务对其他应用程序的请求进行回应提供支持。守护进程也能够对硬件进行配置（如某些Linux系统上的devfsd），运行计划任务（例如cron），以及运行其他任务。</p><p>在 DOS 环境中，此类应用程序被称为驻留程序（TSR）。在 Windows 系统中，由称为 Windows服务的应用程序来履行守护进程的职责。</p><p>在原本的 Mac OS 系统中，此类应用程序被称为”extensions“。而作为 Unux-like 的 Mac OS X 有守护进程。</p><h2 id="安装配置"><a href="#安装配置" class="headerlink" title="安装配置"></a>安装配置</h2><ul><li>操作系统：Linux ubuntu18 4.15.0-91-generic，通过 <code>uname -a</code> 查看</li><li>docker-ce镜像：<a href="https://developer.aliyun.com/mirror/docker-ce?spm=a2c6h.13651102.0.0.52471b11cIp2pH">https://developer.aliyun.com/mirror/docker-ce?spm=a2c6h.13651102.0.0.52471b11cIp2pH</a></li></ul><h3 id="卸载旧版本"><a href="#卸载旧版本" class="headerlink" title="卸载旧版本"></a>卸载旧版本</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">apt remove docker docker-engine docker.io containerd runc</span></span><br></pre></td></tr></table></figure><h3 id="通过软件包安装"><a href="#通过软件包安装" class="headerlink" title="通过软件包安装"></a>通过软件包安装</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">step 1: 安装必要的一些系统工具</span></span><br><span class="line">sudo apt-get update</span><br><span class="line">sudo apt-get -y install apt-transport-https ca-certificates curl gnupg-agent pass software-properties-common</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">step 2: 安装GPG证书</span></span><br><span class="line">curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">Step 3: 写入软件源信息</span></span><br><span class="line">sudo add-apt-repository &quot;deb [arch=amd64] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable&quot;</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">Step 4: 更新并安装Docker-CE</span></span><br><span class="line">sudo apt-get -y update</span><br><span class="line">sudo apt-get -y install docker-ce</span><br></pre></td></tr></table></figure><h3 id="通过脚本安装"><a href="#通过脚本安装" class="headerlink" title="通过脚本安装"></a>通过脚本安装</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">curl -fsSL https://get.docker.com -o get-docker.sh</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">sudo sh get-docker.sh --mirror Aliyun</span></span><br></pre></td></tr></table></figure><p>安装成功后，会自动启动 Docker 服务。用户可以使用 <code>systemctl is-enabled docker</code> 来确认 Docker 服务是否是开机自启动。</p><h3 id="可选配置"><a href="#可选配置" class="headerlink" title="可选配置"></a>可选配置</h3><p><strong>解决 <code>WARNING: Your kernel does not support cgroup swap limit capabilities</code>：</strong></p><ol><li><p>编辑 <code>/etc/default/grub</code> 文件</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">nano /etc/default/grub</span></span><br></pre></td></tr></table></figure></li><li><p>找到 <code>GRUB_CMDLINE_LINUX=</code> 配置项，并追加 <code>cgroup_enable=memory swapaccount=1</code>。</p></li><li><p>保存文件后执行一下命令：<code>sudo update-grub</code></p></li><li><p>重启服务器：<code>reboot</code></p></li></ol><h3 id="测试-Docker-是否安装正确"><a href="#测试-Docker-是否安装正确" class="headerlink" title="测试 Docker 是否安装正确"></a>测试 Docker 是否安装正确</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker run hello-world</span></span><br></pre></td></tr></table></figure><p>执行以上命令，若能正常输出以下信息，则说明安装成功。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">Unable to find image &#x27;hello-world:latest&#x27; locally</span><br><span class="line">latest: Pulling from library/hello-world</span><br><span class="line">1b930d010525: Pull complete</span><br><span class="line">Digest: sha256:f9dfddf63636d84ef479d645ab5885156ae030f611a56f3a7ac7f2fdd86d7e4e</span><br><span class="line">Status: Downloaded newer image for hello-world:latest</span><br><span class="line"></span><br><span class="line">Hello from Docker!</span><br><span class="line">This message shows that your installation appears to be working correctly.</span><br><span class="line"></span><br><span class="line">To generate this message, Docker took the following steps:</span><br><span class="line"> 1. The Docker client contacted the Docker daemon.</span><br><span class="line"> 2. The Docker daemon pulled the &quot;hello-world&quot; image from the Docker Hub.</span><br><span class="line">    (amd64)</span><br><span class="line"> 3. The Docker daemon created a new container from that image which runs the</span><br><span class="line">    executable that produces the output you are currently reading.</span><br><span class="line"> 4. The Docker daemon streamed that output to the Docker client, which sent it</span><br><span class="line">    to your terminal.</span><br><span class="line"></span><br><span class="line">To try something more ambitious, you can run an Ubuntu container with:</span><br><span class="line"> $ docker run -it ubuntu bash</span><br><span class="line"></span><br><span class="line">Share images, automate workflows, and more with a free Docker ID:</span><br><span class="line"> https://hub.docker.com/</span><br><span class="line"></span><br><span class="line">For more examples and ideas, visit:</span><br><span class="line"> https://docs.docker.com/get-started/</span><br></pre></td></tr></table></figure><h3 id="Docker-Deamon-配置"><a href="#Docker-Deamon-配置" class="headerlink" title="Docker Deamon 配置"></a>Docker Deamon 配置</h3><p>执行 <code>nano /etc/docker/daemon.json</code> 中写入如下内容：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;ip&quot;</span><span class="punctuation">:</span> <span class="string">&quot;127.0.0.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;experimental&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;registry-mirrors&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;https://registry.docker-cn.com&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;https://mirror.ccs.tencentyun.com&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;http://docker.mirrors.ustc.edu.cn&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;http://hub-mirror.c.163.com&quot;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>重新启动服务:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">systemctl daemon-reload</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">systemctl restart docker.service</span></span><br></pre></td></tr></table></figure><h2 id="使用-Docker-镜像"><a href="#使用-Docker-镜像" class="headerlink" title="使用 Docker 镜像"></a>使用 Docker 镜像</h2><h3 id="获取镜像"><a href="#获取镜像" class="headerlink" title="获取镜像"></a>获取镜像</h3><blockquote><p><code>docker pull [选项] [Docker Registry 地址[:端口号]/][用户名]&lt;仓库名&gt;[:TAG]</code></p></blockquote><ul><li>默认选项<ul><li><code>-a</code>，<code>--all-tags=true|false</code>：是否获取仓库中的所有镜像，默认为否</li><li><code>--disable-content-trust</code>：取消镜像的内容校验，默认为真</li></ul></li><li>默认 Docker Registry：<code>registry.hub.docker.com</code></li><li>默认用户名：<code>library</code>，也就是官方镜像</li><li>默认TAG：<code>latest</code></li></ul><h3 id="查看镜像信息"><a href="#查看镜像信息" class="headerlink" title="查看镜像信息"></a>查看镜像信息</h3><h4 id="列出本地主机上已有镜像"><a href="#列出本地主机上已有镜像" class="headerlink" title="列出本地主机上已有镜像"></a>列出本地主机上已有镜像</h4><blockquote><p><code>docker image ls</code> | <code>docker images</code></p></blockquote><p>镜像的大小信息只是表示了该镜像的逻辑体积大小，实际上由于相同的镜像层本地只会存储一份，物理上占用的存储空间会小于各镜像逻辑体积之和。</p><h4 id="使用-tag-命令添加镜像标签"><a href="#使用-tag-命令添加镜像标签" class="headerlink" title="使用 tag 命令添加镜像标签"></a>使用 tag 命令添加镜像标签</h4><blockquote><p><code>docker tag ubuntu:latest myubuntu:latest</code></p></blockquote><p>为了方便在后续工作中使用特定镜像，还可以使用 <code>docker tag</code> 命令来为本地镜像任意添加新的标签。</p><h4 id="使用-inspect-命令查看详细信息"><a href="#使用-inspect-命令查看详细信息" class="headerlink" title="使用 inspect 命令查看详细信息"></a>使用 inspect 命令查看详细信息</h4><blockquote><p><code>docker inspect &lt;仓库&gt;</code></p></blockquote><p>使用 <code>docker inspect</code> 命令可以获取该镜像的详细信息，包括制作者、适应架构、各层的数字摘要等。</p><h4 id="使用-history-命令查看镜像历史"><a href="#使用-history-命令查看镜像历史" class="headerlink" title="使用 history 命令查看镜像历史"></a>使用 history 命令查看镜像历史</h4><blockquote><p><code>docker history &lt;REPOSITORY&gt;[:TAG]</code> 或 <code>docker history &lt;IMAGE ID&gt;</code></p></blockquote><p>注意，过长的命令会被自动截断了，可以使用 <code>--no-trunc</code> 选项来输出完整命令。</p><h4 id="删除镜像"><a href="#删除镜像" class="headerlink" title="删除镜像"></a>删除镜像</h4><ol><li><p>使用标签删除镜像</p><blockquote><p><code>docker rmi &lt;IMAGE&gt; [IMAGE...]</code> 或 <code>docker image rm &lt;IMAGE&gt; [IMAGE...]</code></p></blockquote></li><li><p>使用镜像 ID 来删除镜像</p><blockquote><p><code>docker rmi &lt;IMAGE ID&gt;</code></p></blockquote><p>当使用 <code>docker rmi</code> 命令，并且后面跟上镜像的 ID（也可以是能进行区分的部分 ID 串前缀）时，会先尝试删除所有指向该镜像的标签，然后删除该镜像文件本身。</p><blockquote><p>注意，当有基于该镜像创建的容器时，镜像文件默认是无法被删除的。我们可以使用 <code>docker ps -a</code> 命令可以查看本机上存在的所有容器。</p><p>最佳实践：先用 <code>docker rm &lt;Container ID&gt;</code> 删除依赖该镜像的所有容易，然后执行 <code>docker rmi &lt;IMAGE ID&gt;</code> 再来删除镜像。</p></blockquote></li></ol><h4 id="清理镜像"><a href="#清理镜像" class="headerlink" title="清理镜像"></a>清理镜像</h4><blockquote><p><code>docker image prune [options]</code></p><ul><li><code>-a</code>，<code>--all</code>：删除所有无用镜像，不光是临时镜像</li><li><code>-f</code>,<code>--force</code>：强制删除镜像，而不进行提示确认</li></ul></blockquote><p>使用 Docker 一段时间后，系统中可能会遗留一些临时的镜像文件，以及没有使用的镜像，可以通过 <code>docker image prune</code> 命令来进行清理。</p><p>我们可以结合 crontab 来定时清理，执行 <code>crontab -e</code>，写入一下配置：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># 一定要记得在后面按 Enter 输入换行符，否则不会生效的</span><br><span class="line">59 23 * * * docker image prune -f</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="创建镜像"><a href="#创建镜像" class="headerlink" title="创建镜像"></a>创建镜像</h3><h4 id="1-基于已有容器创建"><a href="#1-基于已有容器创建" class="headerlink" title="1. 基于已有容器创建"></a>1. 基于已有容器创建</h4><blockquote><p><code>docker commit [OPTIONS] &lt;CONTAINER&gt; &lt;REPOSITORY&gt;[:TAG]</code></p><ul><li><code>-a</code>，<code>--author=</code>：作者信息</li><li><code>-m</code>，<code>--message=&quot;&quot;</code>：提交信息</li><li><code>-p</code>，<code>--pause=true</code>：提交时暂停容器执行</li></ul></blockquote><p>首先，启动一个 alpine 镜像，并在其中进行安装 nano 的操作，然后发布一个新的镜像：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker run -it alpine bash</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker commit -m <span class="string">&quot;install nano&quot;</span> -a <span class="string">&quot;紫升&quot;</span> ff3034d2ffa7 my-alpine:0.1</span></span><br></pre></td></tr></table></figure><h4 id="2-基于-Dockerfile-创建"><a href="#2-基于-Dockerfile-创建" class="headerlink" title="2. 基于 Dockerfile 创建"></a>2. 基于 Dockerfile 创建</h4><blockquote><p><code>docker build -t &lt;IMAGE NAME&gt; &lt;上下文路径/URL/-&gt;</code></p></blockquote><p>通过 Dockerfile 创建是最常见的方式。Dockerfile 是一个文本文件，利用指定的指令描述基于某个父镜像创建新镜像的过程。</p><p>下面给出 Dockerfile 的一个简单示例，基于 alpine 镜像安装 node 环境，构成一个新的 <code>youngjuning/alpine</code> 镜像：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">FROM alpine</span><br><span class="line"></span><br><span class="line">LABEL version=&quot;1.0&quot; maintainer=&quot;youngjuning&lt;youngjuning@aliyun.com&gt;&quot;</span><br><span class="line"></span><br><span class="line">RUN sed -i &#x27;s/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g&#x27; /etc/apk/repositories</span><br></pre></td></tr></table></figure><p>构建：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker build -t youngjuning/alpine:latest .</span></span><br></pre></td></tr></table></figure><h3 id="存储镜像"><a href="#存储镜像" class="headerlink" title="存储镜像"></a>存储镜像</h3><p>如果要导出镜像到本地文件，可以使用 <code>docker save</code> 命令。该命令支持 <code>-o &lt;string&gt;</code> 或 <code>--output &lt;string&gt;</code> 参数，导出镜像到指定的文件中。</p><p>例如，导出本地 alpine 镜像为文件 alpine.tar，如下所示：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker save -o alpine.tar alpine</span></span><br></pre></td></tr></table></figure><p>之后，用户就可以通过复制 alpine.tar 文件将该镜像分享给他人。</p><h3 id="载入镜像"><a href="#载入镜像" class="headerlink" title="载入镜像"></a>载入镜像</h3><p>可以使用 <code>docker load</code> 将导出的 tar 文件再导入到本地镜像库。支持 <code>-i &lt;string&gt;</code> 或 <code>-input &lt;string&gt;</code> 选项，从指定文件中读入镜像内容。</p><p>例如，从文件 alpine.tar 导入镜像到本地镜像列表，如下所示：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker load -i alpine.tar</span></span><br></pre></td></tr></table></figure><h3 id="上传镜像"><a href="#上传镜像" class="headerlink" title="上传镜像"></a>上传镜像</h3><blockquote><p><code>docker push [选项] [Docker Registry 地址[:端口号]/][用户名]&lt;仓库名&gt;[:TAG]</code></p></blockquote><p><strong>发布新版本流程：</strong></p><ul><li>发布 latest 版本：<code>docker push youngjuning/alpine:latest</code></li><li>添加新标签：<code>docker tag youngjuning/alpine:latest youngjuning/alpine:1.0.0</code></li><li>发布 1.0.0 版本：<code>docker push youngjuning/alpine:1.0.0</code></li></ul><blockquote><p>可以查看 [<a href="https://hub.docker.com/r/youngjuning/alpine]">https://hub.docker.com/r/youngjuning/alpine]</a> 项目查看我发布的基于aliyun镜像的 Aplpine Docker Image</p></blockquote><h2 id="操作-Docker-容器"><a href="#操作-Docker-容器" class="headerlink" title="操作 Docker 容器"></a>操作 Docker 容器</h2><ul><li>Docker 容器是镜像的一个运行实例。</li><li>Docker 容器是独立运行的一个（或一组）应用，以及它们必需的运行环境</li></ul><h3 id="启动容器"><a href="#启动容器" class="headerlink" title="启动容器"></a>启动容器</h3><h4 id="1-新建并启动"><a href="#1-新建并启动" class="headerlink" title="1. 新建并启动"></a>1. 新建并启动</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker run -it ubuntu:18.04 /bin/bash</span></span><br></pre></td></tr></table></figure><p>其中, <code>-t</code> 选项让 Docker 分配一个伪终端（pseudo-tty）并绑定到容器的标准输入上，<code>-i</code> 则让容器的标准输入保持打开。</p><p>当利用 <code>docker run</code> 来创建容器时，Docker 在后台运行的标准操作包括：</p><ol><li>检查本地是否存在指定的镜像，不存在就从公有仓库下载</li><li>利用镜像创建并启动一个容器</li><li>分配一个文件系统，并在只读的镜像层外面挂在一层可读写层</li><li>从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去</li><li>从地址池配置一个ip地址给容器</li><li>执行用户指定的应用程序</li><li>执行完毕后容器被终止</li></ol><p><strong>一些常用选项：</strong></p><ul><li><code>-d</code>，<code>--detach=true|false</code>：是否在后台运行容器，默认为<code>false</code></li><li><code>-i</code>，<code>--interactive=true|false</code>：保持标准输入打开，默认为 <code>false</code></li><li><code>-p</code>，<code>--publish=[]</code>：指定如何映射到本地主机端口，例如 <code>-p 9000:9000</code></li><li><code>--restart=&quot;no&quot;</code>：容器的重启策略，包括 <code>no</code>、<code>on-failure[:max-retry]</code>、<code>always</code>、<code>unless-stopped</code> 等</li><li><code>--rm=true|false</code>：容器退出后是否自动删除，不能跟 <code>-d</code> 同时使用</li><li><code>-t</code>，<code>--tty=true|false</code>：是否分配一个伪终端，默认为 <code>false</code></li><li><code>-v [HOST-DIR:]&lt;CONTAINER-DIR&gt;[:OPTIONS]</code>，<code>--volume=[HOST-DIR:]&lt;CONTAINER-DIR&gt;[:OPTIONS]</code>：挂在主机上的文件卷到容器内</li><li><code>--name=&quot;&quot;</code>：指定容器的别名</li></ul><h4 id="2-启动已终止容器"><a href="#2-启动已终止容器" class="headerlink" title="2. 启动已终止容器"></a>2. 启动已终止容器</h4><p>可以利用 <code>docker start &lt;CONTAINER ID&gt;</code> 命令，直接将一个已经终止的容器启动运行。</p><h4 id="3-查看容器输出"><a href="#3-查看容器输出" class="headerlink" title="3. 查看容器输出"></a>3. 查看容器输出</h4><p>要获取容器的输出信息，可以通过 <code>docker &lt;CONTAINER ID&gt; logs</code> 命令。</p><h3 id="终止容器"><a href="#终止容器" class="headerlink" title="终止容器"></a>终止容器</h3><p>可以使用 <code>docker stop &lt;CONTAINER ID&gt;</code> 来终止一个运行中的容器。</p><p>处于终止状态的容器，可以通过 <code>docker container start</code> 命令来重新启动。</p><p>此外，<code>docker container restart</code> 命令会将一个运行态的容器终止，然后再重新启动它。</p><h3 id="exec进入容器"><a href="#exec进入容器" class="headerlink" title="exec进入容器"></a><code>exec</code>进入容器</h3><p>在使用 <code>-d</code> 参数时，容器启动后会进入后台。</p><p>某些时候需要进入容器进行操作，推荐大家使用 <code>docker exec</code> 命令：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker run -dit alpine</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker ps</span></span><br><span class="line">CONTAINER ID        IMAGE                 COMMAND             CREATED             STATUS              PORTS                      NAMES</span><br><span class="line">3d95dabef801        alpine                &quot;/bin/sh&quot;           21 seconds ago      Up 19 seconds                                  recursing_aryabhata</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker <span class="built_in">exec</span> -it &lt;CONTAINER ID&gt;</span></span><br></pre></td></tr></table></figure><p>如果从这个 stdin 中 exit，不会导致容器的停止。</p><h3 id="删除容器"><a href="#删除容器" class="headerlink" title="删除容器"></a>删除容器</h3><p>可以使用 <code>docker container rm</code> 来删除一个处于终止状态的容器。例如</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker <span class="built_in">rm</span>  &lt;CONTAINER ID&gt;</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">删除运行中的容器，并删除容器挂载的数据卷</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker <span class="built_in">rm</span> -vf</span></span><br></pre></td></tr></table></figure><p>如果要删除一个运行中的容器，可以添加 <code>-f</code> 参数。Docker 会发送 <code>SIGKILL</code> 信号给容器。</p><h3 id="清理所有处于终止状态的容器"><a href="#清理所有处于终止状态的容器" class="headerlink" title="清理所有处于终止状态的容器"></a>清理所有处于终止状态的容器</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker container prune</span></span><br></pre></td></tr></table></figure><h3 id="导出和导入容器"><a href="#导出和导入容器" class="headerlink" title="导出和导入容器"></a>导出和导入容器</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker <span class="built_in">export</span> 7691a814370e &gt; ubuntu.tar</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">cat</span> ubuntu.tar | docker import - <span class="built_in">test</span>/ubuntu:v1.0</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker import http://example.com/exampleimage.tgz example/imagerepo</span></span><br></pre></td></tr></table></figure><h3 id="查看容器"><a href="#查看容器" class="headerlink" title="查看容器"></a>查看容器</h3><h4 id="1-查看容器详情"><a href="#1-查看容器详情" class="headerlink" title="1. 查看容器详情"></a>1. 查看容器详情</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker inspect [OPTIONS] &lt;CONTAINER ID&gt;</span></span><br></pre></td></tr></table></figure><h4 id="2-查看容器内进程"><a href="#2-查看容器内进程" class="headerlink" title="2. 查看容器内进程"></a>2. 查看容器内进程</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker top [OPTIONS] &lt;CONTAINER ID&gt;</span></span><br></pre></td></tr></table></figure><h4 id="3-查看统计信息"><a href="#3-查看统计信息" class="headerlink" title="3. 查看统计信息"></a>3. 查看统计信息</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker stats [OPTIONS] &lt;CONTAINER ID&gt;</span></span><br></pre></td></tr></table></figure><h3 id="更新配置"><a href="#更新配置" class="headerlink" title="更新配置"></a>更新配置</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker update --restart=always &lt;CONTAINER ID&gt;</span></span><br></pre></td></tr></table></figure><h3 id="重命名容器"><a href="#重命名容器" class="headerlink" title="重命名容器"></a>重命名容器</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker rename &lt;old name&gt; &lt;new name&gt;</span></span><br></pre></td></tr></table></figure><h3 id="查看容器日志"><a href="#查看容器日志" class="headerlink" title="查看容器日志"></a>查看容器日志</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker logs -f &lt;CONTAINER ID&gt;</span></span><br></pre></td></tr></table></figure><h3 id="Portainer-容器管理工具"><a href="#Portainer-容器管理工具" class="headerlink" title="Portainer 容器管理工具"></a>Portainer 容器管理工具</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker volume create portainer_data</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker run -d -p 9000:9000 \</span></span><br><span class="line"><span class="language-bash">-v /var/run/docker.sock:/var/run/docker.sock \</span></span><br><span class="line"><span class="language-bash">-v portainer_data:/data \</span></span><br><span class="line"><span class="language-bash">--name portainer \</span></span><br><span class="line"><span class="language-bash">--restart always \</span></span><br><span class="line"><span class="language-bash">portainer/portainer</span></span><br></pre></td></tr></table></figure><p>配置 <code>/etc/nginx/sites-enabled/dafulat</code> 文件：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">upstream</span> portainer &#123;</span><br><span class="line">    <span class="attribute">server</span> <span class="number">127.0.0.1:9000</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="section">server</span> &#123;</span><br><span class="line">  <span class="attribute">listen</span> <span class="number">80</span>;</span><br><span class="line"></span><br><span class="line">  <span class="section">location</span> /portainer/ &#123;</span><br><span class="line">      <span class="attribute">proxy_http_version</span> <span class="number">1</span>.<span class="number">1</span>;</span><br><span class="line">      <span class="attribute">proxy_set_header</span> Connection <span class="string">&quot;&quot;</span>;</span><br><span class="line">      <span class="attribute">proxy_pass</span> http://portainer/;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="section">location</span> /portainer/ws/ &#123;</span><br><span class="line">      <span class="attribute">proxy_set_header</span> Upgrade <span class="variable">$http_upgrade</span>;</span><br><span class="line">      <span class="attribute">proxy_set_header</span> Connection <span class="string">&quot;upgrade&quot;</span>;</span><br><span class="line">      <span class="attribute">proxy_http_version</span> <span class="number">1</span>.<span class="number">1</span>;</span><br><span class="line">      <span class="attribute">proxy_pass</span> http://portainer/ws/;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Docker-数据持久化"><a href="#Docker-数据持久化" class="headerlink" title="Docker 数据持久化"></a>Docker 数据持久化</h2><p><code>数据卷</code> 是一个可供一个或多个容器使用的特殊目录，它绕过 UFS，可以提供很多有用的特性：</p><ul><li><code>数据卷</code> 可以在容器之间共享和重用</li><li>对 <code>数据卷</code> 的修改会立马生效</li><li>对 <code>数据卷</code> 的更新，不会影响镜像</li><li><code>数据卷</code> 默认会一直存在，即使容器被删除</li></ul><h3 id="1-创建数据卷"><a href="#1-创建数据卷" class="headerlink" title="1. 创建数据卷"></a>1. 创建数据卷</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker volume create my-vol</span></span><br></pre></td></tr></table></figure><p>除了 <code>create</code> 子命令外，docker volume 还支持 <code>inspect</code>(查看详细信息)、<code>ls</code>（列出已有数据卷）、<code>prune</code>（清理无用数据卷）、<code>rm</code>（删除数据卷）</p><h3 id="2-绑定数据卷"><a href="#2-绑定数据卷" class="headerlink" title="2. 绑定数据卷"></a>2. 绑定数据卷</h3><h4 id="mount"><a href="#mount" class="headerlink" title="--mount"></a><code>--mount</code></h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ docker run -d -P \</span><br><span class="line">    --name web \</span><br><span class="line">    --mount <span class="built_in">source</span>=my-vol,target=/webapp \</span><br><span class="line">    training/webapp \</span><br><span class="line">    python app.py</span><br></pre></td></tr></table></figure><h4 id="v，-volume"><a href="#v，-volume" class="headerlink" title="-v，--volume"></a><code>-v</code>，<code>--volume</code></h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker run -d -P \</span></span><br><span class="line"><span class="language-bash">    --name web \</span></span><br><span class="line"><span class="language-bash">    -v my-vol:/webapp \</span></span><br><span class="line"><span class="language-bash">    training/webapp \</span></span><br><span class="line"><span class="language-bash">    python app.py</span></span><br></pre></td></tr></table></figure><blockquote><p>source 也可以是绝对路径的任意系统位置。</p></blockquote><blockquote><p>如果直接挂载一个文件到容器，使用文件编辑工具，包括 vi 或者 <code>sed --in-place</code> 的时候，可能会造成文件 inode 的改变，从 Docker 1.1 起，这会导致报错误信息。所以推荐的方式是直接挂载文件所在的目录到容器内。</p></blockquote><h2 id="Dockerfile"><a href="#Dockerfile" class="headerlink" title="Dockerfile"></a>Dockerfile</h2><blockquote><p>详细指令详解请查看：<a href="https://vuepress.mirror.docker-practice.com/image/dockerfile/">Dockerfile 指令详解</a></p></blockquote><p><img src="https://i.loli.net/2020/04/11/hmlMV4QA2opON9j.png" alt="紫升"></p><h2 id="应用安装"><a href="#应用安装" class="headerlink" title="应用安装"></a>应用安装</h2><h2 id="GitLab-及其官方镜像"><a href="#GitLab-及其官方镜像" class="headerlink" title="GitLab 及其官方镜像"></a>GitLab 及其官方镜像</h2><blockquote><p>特别耗CPU，我的服务器太辣鸡带不动！！！</p></blockquote><h3 id="docker-compose-yml"><a href="#docker-compose-yml" class="headerlink" title="docker-compose.yml"></a>docker-compose.yml</h3><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">web:</span></span><br><span class="line">  <span class="attr">image:</span> <span class="string">&#x27;gitlab/gitlab-ce:latest&#x27;</span></span><br><span class="line">  <span class="attr">restart:</span> <span class="string">always</span></span><br><span class="line">  <span class="attr">hostname:</span> <span class="string">&#x27;gitlab.yangjunning.pro&#x27;</span></span><br><span class="line">  <span class="attr">environment:</span></span><br><span class="line">    <span class="attr">GITLAB_OMNIBUS_CONFIG:</span> <span class="string">|</span></span><br><span class="line"><span class="string">      external_url &#x27;http://gitlab.yangjunning.pro:8929&#x27;</span></span><br><span class="line"><span class="string">      gitlab_rails[&#x27;gitlab_shell_ssh_port&#x27;] = 2224</span></span><br><span class="line"><span class="string"></span>  <span class="attr">ports:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&#x27;8929:8929&#x27;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&#x27;2224:22&#x27;</span></span><br><span class="line">  <span class="attr">volumes:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&#x27;gitlab_config:/etc/gitlab&#x27;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&#x27;gitlab_logs:/var/log/gitlab&#x27;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&#x27;gitlab_data:/var/opt/gitlab&#x27;</span></span><br></pre></td></tr></table></figure><h3 id="运行容器"><a href="#运行容器" class="headerlink" title="运行容器"></a>运行容器</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ docker-compose up -d</span><br></pre></td></tr></table></figure><h3 id="更新-gitlab"><a href="#更新-gitlab" class="headerlink" title="更新 gitlab"></a>更新 gitlab</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker-compose pull</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker-compose up -d</span></span><br></pre></td></tr></table></figure><h2 id="Docker-相关的定时任务"><a href="#Docker-相关的定时任务" class="headerlink" title="Docker 相关的定时任务"></a>Docker 相关的定时任务</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># crontab -e</span><br><span class="line"># 每天凌晨强制删除无用镜像，不光是临时镜像；每天凌晨清理无用的数据卷</span><br><span class="line">00 00 * * * docker image prune -af &amp;&amp; docker volume prune -f &amp;&amp; rsync -arv /var/lib/docker/volumes /backups/docker</span><br></pre></td></tr></table></figure><blockquote><p>qshell 同步文件到七牛云的配置请参考<a href="https://juejin.im/post/5e81e2db518825737b4ad911#heading-59">备份到七牛云</a></p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/8e0831eec81f/</id>
    <link href="https://blog.zisheng.pro/posts/8e0831eec81f/"/>
    <published>2020-03-30T16:23:31.000Z</published>
    <summary>Docker 是个划时代的开源项目，它彻底释放了计算虚拟化的威力，极大提高了应用的维护效率，降低了云计算应用开发的成本！使用 Docker，可以让应用的部署、测试和分发都变得前所未有的高效和轻松！</summary>
    <title>一个前端工程师的 Docker 学习笔记</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="运维" scheme="https://blog.zisheng.pro/categories/%E8%BF%90%E7%BB%B4/"/>
    <category term="阿里云" scheme="https://blog.zisheng.pro/tags/%E9%98%BF%E9%87%8C%E4%BA%91/"/>
    <category term="腾讯云" scheme="https://blog.zisheng.pro/tags/%E8%85%BE%E8%AE%AF%E4%BA%91/"/>
    <category term="Linux" scheme="https://blog.zisheng.pro/tags/Linux/"/>
    <category term="Ubuntu" scheme="https://blog.zisheng.pro/tags/Ubuntu/"/>
    <category term="华为云" scheme="https://blog.zisheng.pro/tags/%E5%8D%8E%E4%B8%BA%E4%BA%91/"/>
    <category term="GNU" scheme="https://blog.zisheng.pro/tags/GNU/"/>
    <category term="ssh" scheme="https://blog.zisheng.pro/tags/ssh/"/>
    <content>
      <![CDATA[<p>笔者使用的是 AliYun 服务器 ECS，镜像选择的是 Ubuntu，Ubuntu的详细版本信息是：<code>Ubuntu 18.04.4 LTS (GNU/Linux 4.15.0-91-generic x86_64)</code>，如何购买云服务器不在本文讨论范围内，如果只是学习，那你完全可以使用 virtualbox 安装 Ubuntu。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">连接服务器</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">ssh &lt;username&gt;@&lt;hostname&gt;</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">更新系统源</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">apt update</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">升级系统源</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">apt upgrade</span></span><br></pre></td></tr></table></figure><h2 id="必备环境及应用"><a href="#必备环境及应用" class="headerlink" title="必备环境及应用"></a>必备环境及应用</h2><h3 id="Git"><a href="#Git" class="headerlink" title="Git"></a>Git</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">安装 git</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">apt install git</span></span><br></pre></td></tr></table></figure><h4 id="命令行配置"><a href="#命令行配置" class="headerlink" title="命令行配置"></a>命令行配置</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1、初始化设置</span></span><br><span class="line">$ git config --global user.name <span class="string">&#x27;youngjuning&#x27;</span></span><br><span class="line">$ git config --global user.email <span class="string">&#x27;youngjuning@aliyun.com&#x27;</span></span><br><span class="line"><span class="comment"># 2、将 `color.ui` 设置为 `auto` 可以让命令的输出拥有更高的可读性。</span></span><br><span class="line">$ git config --global color.ui auto</span><br><span class="line"><span class="comment"># 3、git 记住用户名和密码</span></span><br><span class="line">$ git config --global credential.helper store</span><br><span class="line"><span class="comment"># 4、core.autocrlf</span></span><br><span class="line">$ git config --global core.autocrlf input</span><br></pre></td></tr></table></figure><blockquote><p>Linux或Mac系统使用LF作为行结束符，因此你不想 Git 在签出文件时进行自动的转换；当一个以 <code>CRLF</code> 为行结束符的文件不小心被引入时你肯定想进行修正，把 <code>core.autocrlf</code> 设置成 <code>input</code> 来告诉 Git 在提交时把 <code>CRLF</code> 转换成 <code>LF</code>，签出时不转换：<br>这样会在 Windows 系统上的签出文件中保留 <code>CRLF</code>，会在 Mac 和 Linux 系统上，包括仓库中保留 <code>LF</code>。</p></blockquote><h3 id="Java"><a href="#Java" class="headerlink" title="Java"></a>Java</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">安装 jre、jdk</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">apt install openjdk-8-jre-headless openjdk-8-jdk-headless</span></span><br></pre></td></tr></table></figure><h3 id="Node"><a href="#Node" class="headerlink" title="Node"></a>Node</h3><p>Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时。我们使用 <code>n</code> 模块来维护 Node 的版本：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">apt install nodejs npm build-essential</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">sudo npm install -g npm n</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">安装最新版</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">sudo n latest</span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash"><span class="comment"># 其他命令##</span></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">安装指定版本</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">sudo n 10.16.0</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">安装最新的稳定版</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">sudo n lts</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">删除指定版本</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">n <span class="built_in">rm</span> 12.10.0</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">除去当前版本以外的所有缓存版本</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">n prune</span></span><br></pre></td></tr></table></figure><h3 id="oh-my-zsh"><a href="#oh-my-zsh" class="headerlink" title="oh-my-zsh"></a>oh-my-zsh</h3><blockquote><p>修改 <code>~/.zshrc</code> 之后都需要执行 <code>source ~/.zshrc</code> 命令使之立即生效</p><p><code>echo $SHELL</code> 可以查看当前 Shell</p></blockquote><ul><li><code>apt install zsh</code></li><li><code>git clone https://github.com/robbyrussell/oh-my-zsh.git ~/.oh-my-zsh</code></li><li><code>cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc</code></li><li><code>nano ~/.zshrc</code></li><li>找到 <code>ZSH_THEME=&quot;robbyrussell&quot;</code> ，把 <code>robbyrussell</code> 替换为 <code>ys</code></li><li>修改默认Shell：<code>chsh -s /bin/zsh</code></li><li>命令高亮<ul><li><code>git clone https://github.com/zsh-users/zsh-syntax-highlighting.git &quot;$HOME/.zsh-syntax-highlighting&quot; --depth 1</code></li><li><code>echo &quot;source $HOME/.zsh-syntax-highlighting/zsh-syntax-highlighting.zsh&quot; &gt;&gt; &quot;$HOME/.zshrc&quot;</code></li></ul></li><li>重新打开命令行</li><li><code>echo &quot;export EDITOR=nano&quot; &gt;&gt; ~/.zshrc</code></li></ul><h3 id="vim"><a href="#vim" class="headerlink" title="vim"></a>vim</h3><blockquote><p>建议使用：<a href="https://github.com/amix/vimrc">https://github.com/amix/vimrc</a></p></blockquote><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git <span class="built_in">clone</span> --depth=1 https://github.com/amix/vimrc.git ~/.vim_runtime</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">sh ~/.vim_runtime/install_awesome_vimrc.sh</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">echo</span> <span class="string">&quot;set number&quot;</span> &gt;&gt; ~/.vimrc</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">echo</span> <span class="string">&quot;set showcmd&quot;</span> &gt;&gt; ~/.vimrc</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">source</span> ~/.vimrc</span></span><br></pre></td></tr></table></figure><h2 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h2><ul><li><code>/</code><ul><li><code>bin</code>：包含了会被所有用户使用的可执行程序</li><li><code>boot</code>：包含与 Linux 启动密切相关的文件</li><li><code>dev</code>：包含外设。它里面的子目录，每一个对应一个外设</li><li><code>etc</code>：包含系统的配置文件</li><li><code>home</code>：用户的私人目录</li><li><code>lib</code>：包含被程序所调用的库文件，例如<code>.so</code>结尾的文件</li><li><code>media</code>：可移动的外设（USB盘，SD卡，DVD，光盘，等等）插入电脑时</li><li><code>mnt</code>：用于临时挂载一些装置</li><li><code>opt</code>：可选的应用软件包，用于安装多数第三方软件和插件</li><li><code>root</code>：超级用户 root 的家目录</li><li><code>sbin</code>：用于包含系统级的重要可执行程序</li><li><code>srv</code>：包含一些网络服务启动之后所需要取用的数据</li><li><code>tmp</code>：普通用户和程序存放临时文件的地方</li><li><code>usr</code>：Unix Software Resource，安装了大部分用户要调用的程序</li><li><code>var</code>：通常包含程序的数据，比如 log（日志）文件</li></ul></li></ul><h2 id="常用命令"><a href="#常用命令" class="headerlink" title="常用命令"></a>常用命令</h2><blockquote><p>Linux命令搜索引擎：<a href="https://git.io/linux">https://git.io/linux</a></p></blockquote><ul><li><code>reboot</code>：重新启动正在运行的Linux操作系统</li><li><code>halt</code>：关闭系统</li><li><code>shutdown</code>：用来系统关机命令。shutdown指令可以关闭所有程序，并依用户的需要，进行重新开机或关机的动作。</li><li><code>poweroff</code>：直接运行即可关机</li><li><code>history</code>：用于显示历史命令</li><li><code>whice</code>：查找并显示给定命令的绝对路径</li><li><code>pwd</code>：显示当前工作目录。（Print Working Directory）</li><li><code>source</code>：在当前Shell环境中从指定文件读取和执行命令，让文件修改立即生效。</li><li><code>updatedb</code>：创建或更新<code>locate</code>命令所必需的数据库文件</li><li><code>uname -a</code>：显示全部Linux系统信息</li><li><code>cat /proc/version</code>：显示Linux信息</li><li><code>cat /etc/lsb-release</code>：查看发行版信息</li><li><code>echo $SHELL</code>：当前的shell</li><li><code>service &lt;service&gt; force-reload|reload|restart|start|status|stop</code></li><li><code>systemctl start|stop|restart|status|reload</code>：<ul><li><code>enable</code>：开机自动启动服务</li><li><code>disable</code>：开机不自动启动服务</li><li><code>is-enabled</code>：查看服务是否开机自动启动</li><li><code>list-unit-files --type=service</code>：查看各个级别下服务的启动和禁用情况</li></ul></li></ul><h2 id="文件管理"><a href="#文件管理" class="headerlink" title="文件管理"></a>文件管理</h2><h3 id="文件目录操作"><a href="#文件目录操作" class="headerlink" title="文件目录操作"></a>文件目录操作</h3><ul><li><p><code>ls</code>：显示目录内容列表</p><ul><li><code>-a</code>：列出所有文件，包括以 “.” 开头的隐含文件。</li><li><code>-l</code>：除每个文件名外，增加显示文件类型、权限、硬链接数、所有者名、组名、大小（byte）及时间信息（如未指明是其它时间即指修改时间），可以用 <code>ll</code> 代替</li></ul></li><li><p><code>du</code>：显示每个文件和目录的磁盘使用空间（Disk Usage）</p><ul><li><code>-a</code>或<code>-all</code>：显示目录中个别文件的大小。</li><li><code>-h</code>或<code>--human-readable</code>：以K，M，G为单位，提高信息的可读性。</li><li><code>-s</code>或<code>--summarize</code>：仅显示总计，只列出最后加总的值。</li></ul></li><li><p><code>cat -N</code>：连接多个文件并打印到标准输出。(concatenate)</p></li><li><p><code>less -N</code></p><ul><li>空格键：前进一页（一个屏幕）</li><li>b键：后退一页（一个屏幕）</li><li>d键：前进半页（半个屏幕）</li><li>u键：后退半页（半个屏幕）</li><li>回车键&#x2F;e键&#x2F;下箭头：前进一行</li><li>y键&#x2F;上箭头：后退一行</li><li>q键：停止读取文件，中止 less 命令</li><li>&#x3D;键：显示你在文件中的什么位置</li><li>&#x2F;键：进入搜索模式<ul><li>n键：跳转到下一个匹配项</li><li>shift+n：跳转到上一个匹配项</li></ul></li></ul></li><li><p><code>head</code>：显示文件的开头部分</p></li><li><p><code>tail</code>：在屏幕上显示指定文件的末尾若干行</p><ul><li><code>-f</code>：显示文件最新追加的内容。</li><li><code>-s&lt;秒数&gt;</code>：与<code>-f</code>选项连用，指定监视文件变化时间隔的秒数，默认1秒</li></ul></li><li><p><code>touch</code>：创建新的空文件</p></li><li><p><code>mkdir</code>：用来创建目录</p><ul><li><code>-p</code>或<code>--parents</code>：若所要建立目录的上层目录目前尚未建立，则会一并建立上层目录</li></ul></li><li><p><code>cp</code>：将源文件或目录复制到目标文件或目录中</p><ul><li><code>-r</code>或<code>-R</code>：递归处理，将指定目录下的所有文件与子目录一并处理，recursive</li></ul></li><li><p><code>mv</code>：用来对文件或目录重新命名</p></li><li><p><code>rm</code>：用于删除给定的文件和目录</p><ul><li><code>-r</code>或<code>-R</code>：递归处理，将指定目录下的所有文件与子目录一并处理，recursive</li><li><code>-f</code>：强制删除文件或目录</li></ul></li><li><p><code>ln</code>：用来为文件创建链接</p><blockquote><ul><li>硬链接缺陷：一般情况下，只能创建指向文件的硬链接，不能创建指向目录的</li><li>硬链接指的是使用了同一个<code>inode</code>号，但是文件名不一样</li><li><code>ls -i</code>：显示<code>inode</code>号</li></ul></blockquote><ul><li><code>-s</code>, <code>--symbolic</code>：对源文件建立符号链接，而非硬链接</li></ul></li><li><p><code>locate</code>：比 <code>find</code> 好用的文件查找工具，需要配合 <code>updatedb</code> 使用</p></li><li><p><code>find</code>：在指定目录下查找文件</p><blockquote><p><code>find [何处] &lt;何物&gt; [做什么]</code></p></blockquote><ul><li><p><code>-name &lt;范本样式&gt;</code>：指定字符串作为寻找文件或目录的范本样式</p></li><li><p><code>-size &lt;文件大小&gt;</code>：查找符合指定的文件大小的文件</p></li><li><p><code>-type &lt;文件类型&gt;</code>：只寻找符合指定的文件类型的文件；</p><ul><li><strong>f</strong> 普通文件</li><li><strong>l</strong> 符号连接</li><li><strong>d</strong> 目录</li><li><strong>c</strong> 字符设备</li><li><strong>b</strong> 块设备</li><li><strong>s</strong> 套接字</li><li><strong>p</strong> Fifo</li></ul></li><li><p><code>-atime &lt;24小时数&gt;</code>：查找在指定时间曾被存取过的文件或目录，单位以24小时计算；</p></li><li><p><code>-exec &lt;执行指令&gt;</code>：假设find指令的回传值为True，就执行该指令</p><blockquote><p><code>find . -name *.txt -exec chmod 600 {} \;</code></p><p>可以换成<code>-ok</code>,ok 会让你确认操作</p></blockquote></li></ul></li></ul><h3 id="权限管理"><a href="#权限管理" class="headerlink" title="权限管理"></a>权限管理</h3><ul><li><p><code>sudo su</code>：切换 root 身份，substitute do</p></li><li><p><code>useradd -m</code>：创建的新的系统用户自动创建用户的家目录</p></li><li><p><code>passwd</code>：用于让用户可以更改自己的密码</p></li><li><p>&#96;userdel -r：用于删除给定的用户以及与用户相关的文件</p></li><li><p><code>users</code>：显示当前登录系统的所有用户</p></li><li><p><code>usermod</code>：用于修改用户的基本信息</p><ul><li><code>-l</code>：对用户重命名。<code>/home</code> 中的用户家目录名不改变，需要手动修改</li><li><code>-g</code>：修改用户所在群组</li><li><code>-G</code>：将用户添加到多个群组，多个群组用<code>,</code>分割</li><li><code>-aG</code> 或 <code>-ag</code>：不离开原来的群组进入新的群组</li></ul></li><li><p><code>groupadd</code>：用于创建一个新的工作组</p></li><li><p><code>groups</code>：用来打印指定用户所属的工作组</p></li><li><p><code>chown</code>：用来变更文件或目录的拥有者或所属群组</p><blockquote><p><code>chown &lt;username&gt;[:gruop] &lt;file|dir&gt;</code></p></blockquote><ul><li><code>-R</code>：递归处理，将指定目录下的所有文件及子目录一并处理</li></ul></li><li><p><code>chgrp</code>：用来变更文件或目录的所属群组</p></li><li><p><code>chmod</code>：用来变更文件或目录的权限</p><blockquote><img src="https://i.loli.net/2020/03/28/fiXQkrtNn8yZLWY.png" style="zoom:50%;" /><p>一共有10位，第<strong>1</strong>位代表文件类型，第<strong>2-4</strong>位表示所有者的读、写和执行权限，第<strong>5-7</strong>位表示同组用户的读、写和执行权限，第<strong>8-10</strong>位表示其他用户的读、写和执行权限。如果没有权限用<code>-</code>占位。如果开启 SELinux，则第<strong>11</strong>位会多一个<code>.</code>。</p><ul><li>文件类型<ul><li><code>d</code>：英语 directory 的缩写，表示<strong>目录</strong></li><li><code>l</code>：英文 link 的缩写，表示<strong>链接</strong></li><li><code>-</code>：文件</li></ul></li><li><code>r</code>：英语 read 的缩写，表示<strong>读</strong></li><li><code>w</code>：英文 write 的缩写，表示<strong>写</strong></li><li><code>x</code>：英语 execute 的缩写，表示<strong>执行&#x2F;运行</strong>。可以运行这个文件</li><li><code>.</code>：SELinux 的安全标签，如果第11位有<code>.</code>，表示启用了 SELinux</li></ul></blockquote><ul><li><p>用数字来分配权限</p><ul><li>没有权限：0</li><li>执行权限：1</li><li>写权限：2</li><li>写执行权限：3</li><li>读权限：4</li><li>读执行权限：5</li><li>读写权限：6</li><li>读写执行权限：7</li></ul></li><li><p>用字母来分配权限</p><ul><li>u：user 的缩写，是英语用户的意思。表示所有者</li><li>g：group的缩写，是英语群组的意思。表示群组用户</li><li>o：other的缩写，是英语其他的意思。表示其他用户</li><li>a：all的缩写，是英语所有的意思。表示所有用户</li><li>+：加号，表示添加权限</li><li>-：减号，表示去除权限</li><li>&#x3D;：等号，表示分配权限</li></ul><blockquote><p><code>chmod u+rx file</code>：文件 file 的所有者增加读和运行的权限</p><p><code>chmod g+r file</code>：文件 file 的群组其他用户增加读的权限</p><p><code>chmod o-r file</code>：文件 file 的其他用户移除读的权限</p><p><code>chmod g+r o-r file</code>：文件 file 的群组其他用户增加读的权限，其他用户移除读的权限</p><p><code>chmod go-r file</code>：文件 file 的群组其他用户和其他用户均移除读的权限</p><p><code>chmod +x file</code>：文件 file 的所有用户增加运行的权限</p><p><code>chmod u=rwx,g=r,0=- file</code>：文件 file 的所有者分配读，写和执行的权限；群组其他用户分配读的权限，不能写或执行；其他用户没有任何权限</p></blockquote></li></ul></li></ul><h2 id="正则表达式和数据操作"><a href="#正则表达式和数据操作" class="headerlink" title="正则表达式和数据操作"></a>正则表达式和数据操作</h2><h3 id="grep-筛选数据"><a href="#grep-筛选数据" class="headerlink" title="grep: 筛选数据"></a>grep: 筛选数据</h3><p><strong>grep</strong> （global search regular expression(RE) and print out the line，全面搜索正则表达式并把行打印出来）是一种强大的文本搜索工具，它能使用正则表达式搜索文本，并把匹配的行打印出来。用于过滤&#x2F;搜索的特定字符。可使用正则表达式能多种命令配合使用，使用上十分灵活。</p><ul><li><code>-i</code> <code>--ignore-case</code>：忽略字符大小写的差别。</li><li><code>-n</code>  <code>--line-number</code>：在显示符合范本样式的那一列之前，标示出该列的编号。</li><li><code>-v</code> <code>--revert-match</code>：反转查找。</li><li><code>-R/-r</code>  <code>--recursive</code>：在所有子目录和子文件中查找，可以用  <code>rgrep</code> 替代</li><li><code>-E</code> <code>--extended-regexp</code>：将范本样式为延伸的普通表示法来使用，意味着使用能使用扩展正则表达式。可以用 <code>egrep</code> 代替<ul><li><code>grep -E ^p /etc/profile</code></li><li><code>grep -E [at] /etc/profile</code></li><li><code>grep -E [0-4] /etc/profile</code></li><li><code>grep -E [a-zA-Z] /etc/profile</code></li></ul></li></ul><h4 id="规则表达式"><a href="#规则表达式" class="headerlink" title="规则表达式"></a>规则表达式</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">^    # 锚定行的开始 如：&#x27;^grep&#x27;匹配所有以grep开头的行。</span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">   <span class="comment"># 锚定行的结束 如：&#x27;grep$&#x27; 匹配所有以grep结尾的行。</span></span></span><br><span class="line">.    # 匹配一个非换行符的字符 如：&#x27;gr.p&#x27;匹配gr后接一个任意字符，然后是p。</span><br><span class="line">*    # 匹配零个或多个先前字符 如：&#x27;*grep&#x27;匹配所有一个或多个空格后紧跟grep的行。</span><br><span class="line">.*   # 一起用代表任意字符。</span><br><span class="line">[]   # 匹配一个指定范围内的字符，如&#x27;[Gg]rep&#x27;匹配Grep和grep。</span><br><span class="line">[^]  # 匹配一个不在指定范围内的字符，如：&#x27;[^A-FH-Z]rep&#x27;匹配不包含A-R和T-Z的一个字母开头，紧跟rep的行。</span><br><span class="line">\(..\)  # 标记匹配字符，如&#x27;\(love\)&#x27;，love被标记为1。</span><br><span class="line">\&lt;      # 锚定单词的开始，如:&#x27;\&lt;grep&#x27;匹配包含以grep开头的单词的行。</span><br><span class="line">\&gt;      # 锚定单词的结束，如&#x27;grep\&gt;&#x27;匹配包含以grep结尾的单词的行。</span><br><span class="line">x\&#123;m\&#125;  # 重复字符x，m次，如：&#x27;0\&#123;5\&#125;&#x27;匹配包含5个o的行。</span><br><span class="line">x\&#123;m,\&#125;   # 重复字符x,至少m次，如：&#x27;o\&#123;5,\&#125;&#x27;匹配至少有5个o的行。</span><br><span class="line">x\&#123;m,n\&#125;  # 重复字符x，至少m次，不多于n次，如：&#x27;o\&#123;5,10\&#125;&#x27;匹配5--10个o的行。</span><br><span class="line">\w    # 匹配文字和数字字符，也就是[A-Za-z0-9]，如：&#x27;G\w*p&#x27;匹配以G后跟零个或多个文字或数字字符，然后是p。</span><br><span class="line">\W    # \w的反置形式，匹配一个或多个非单词字符，如点号句号等。</span><br><span class="line">\b    # 单词锁定符，如: &#x27;\bgrep\b&#x27;只匹配grep。</span><br></pre></td></tr></table></figure><h3 id="sort-文件排序"><a href="#sort-文件排序" class="headerlink" title="sort 文件排序"></a>sort 文件排序</h3><p>对文本文件中所有行进行排序。</p><ul><li><code>-o</code>, <code>--output=FILE</code>：将结果写入FILE而不是标准输出。<code>sort -o name.txt name_sorted.txt</code></li><li><code>-r</code>, <code>--reverse</code>：将结果倒序排列。</li><li><code>-R</code>：<code>--random-sort</code> 随机排序，但分组相同的行。</li><li><code>-n</code>, <code>--numeric-sort</code>：根据数字排序。</li></ul><h4 id="主要用途"><a href="#主要用途" class="headerlink" title="主要用途"></a>主要用途</h4><ul><li>将所有输入文件的内容排序后并输出。</li><li>当没有文件或文件为<code>-</code>时，读取标准输入。</li></ul><h3 id="wc-文件统计"><a href="#wc-文件统计" class="headerlink" title="wc 文件统计"></a>wc 文件统计</h3><p>统计文件的行数、字数、字节数</p><blockquote><p><strong>wc命令</strong> 统计指定文件中的字节数、字数、行数，并将统计结果显示输出。利用wc指令我们可以计算文件的Byte数、字数或是列数，若不指定文件名称，或是所给予的文件名为“-”，则wc指令会从标准输入设备读取数据。wc同时也给出所指定文件的总统计数。</p></blockquote><ul><li><code>-c</code>： 统计字节数，或<code>--bytes</code>或<code>--chars</code>：只显示Bytes数</li><li><code>-l</code>：统计行数，或<code>--lines</code>：只显示列数</li><li><code>-m</code>：统计字符数。这个标志不能与 <code>-c</code> 标志一起使用</li><li><code>-w</code>：统计字数，或<code>--words</code>：只显示字数。一个字被定义为由空白、跳格或换行字符分隔的字符串</li><li><code>-L</code>：打印最长行的长度</li></ul><h3 id="uniq-删除文件中的重复内容"><a href="#uniq-删除文件中的重复内容" class="headerlink" title="uniq: 删除文件中的重复内容"></a>uniq: 删除文件中的重复内容</h3><p>显示或忽略重复的行。<code>uniq</code> 命令有点”呆“，只能将连续的重复行变为一行</p><ul><li><code>-c</code>, <code>--count</code>：在每行开头增加重复次数</li><li><code>-d</code>, <code>--repeated</code>：只显示重复行的值</li></ul><h4 id="主要用途-1"><a href="#主要用途-1" class="headerlink" title="主要用途"></a>主要用途</h4><ul><li>将输入文件（或标准输入）中邻近的重复行写入到输出文件（或标准输出）中。</li><li>当没有选项时，邻近的重复行将合并为一个。</li></ul><h3 id="cut-剪切文件的一部分内容"><a href="#cut-剪切文件的一部分内容" class="headerlink" title="cut: 剪切文件的一部分内容"></a>cut: 剪切文件的一部分内容</h3><p>连接文件并打印到标准输出设备上</p><ul><li><code>-c</code>：仅显示行中指定范围的字符，<code>cut -c 2-4 file</code></li><li><code>-d</code>：指定字段的分隔符，默认的字段分隔符为“TAB”。</li><li></li></ul><h2 id="输出重定向"><a href="#输出重定向" class="headerlink" title="输出重定向"></a>输出重定向</h2><blockquote><p>黑洞文件 <code>/dev/null</code>，此文件具有唯一的属性，它总是空的。它能使发送到 <code>/dev/null</code> 的任何数据作废</p></blockquote><h3 id="重定向到文件"><a href="#重定向到文件" class="headerlink" title="&gt; 重定向到文件"></a><code>&gt;</code> 重定向到文件</h3><ul><li>如果此文件不存在，则新建一个文件</li><li>如果此文件已经存在，那就会把文件内容覆盖掉，而且不会征求用户同意</li></ul><h3 id="重定向到文件末尾"><a href="#重定向到文件末尾" class="headerlink" title="&gt;&gt; 重定向到文件末尾"></a><code>&gt;&gt;</code> 重定向到文件末尾</h3><p><code>&gt;&gt;</code>的作用与<code>&gt;</code>是类似的，不过它不会像<code>&gt;</code>那么危险。它会将重定向的内容写入到文件末尾，起到追加的作用。如果文件不存在，则创建文件</p><h3 id="2-、2-重定向到标准错误输出"><a href="#2-、2-重定向到标准错误输出" class="headerlink" title="2&gt; 、2&gt;&gt;重定向到标准错误输出"></a><code>2&gt;</code> 、<code>2&gt;&gt;</code>重定向到标准错误输出</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">cat</span> not_exist_file.csv &gt; results.txt 2&gt; errors.log</span></span><br></pre></td></tr></table></figure><h3 id="2-1-组合符合"><a href="#2-1-组合符合" class="headerlink" title="2&gt;&amp;1 组合符合"></a><code>2&gt;&amp;1</code> 组合符合</h3><ul><li>将标准错误输出重定向到与标准输出相同的地方</li><li>覆盖：<code>cat not_exist_file_csv &gt; results.txt 2&gt;&amp;1</code></li><li>末尾追加：<code>cat not_exist_file_csv &gt;&gt; results.txt 2&gt;&amp;1</code></li></ul><h3 id="stdin、stdout、stderr"><a href="#stdin、stdout、stderr" class="headerlink" title="stdin、stdout、stderr"></a>stdin、stdout、stderr</h3><ul><li>从键盘向终端输入数据，这是标准输入，也就是 stdin</li><li>终端接收键盘输入的命令，会产生两种输出<ul><li>标准输出：stdout。指终端输出的信息（不包括错误信息）</li><li>标准错误输出：stderr。指终端输出的错误信息</li></ul></li></ul><h2 id="输入重定向"><a href="#输入重定向" class="headerlink" title="输入重定向"></a>输入重定向</h2><h3 id=""><a href="#" class="headerlink" title="&lt; 从文件中读取"></a><code>&lt;</code> 从文件中读取</h3><ul><li><p><code>cat notes.csv</code></p><ul><li><code>cat</code> 命令接受的输入是 notes.csv 这个文件名</li><li>那么它要先打开 notes.csv 文件</li><li>然后打印出文件内容</li></ul></li><li><p><code>cat &lt; notes.csv</code></p><ul><li><code>cat</code> 命令接受的输入直接是 notes.csv 这个文件的内容</li><li><code>cat</code> 命令只负责将其内容打印</li><li>打开文件并将文件内容传递给 <code>cat</code> 命令的工作则交给终端完成</li></ul></li></ul><h3 id="-1"><a href="#-1" class="headerlink" title="&lt;&lt; 从键盘读取"></a><code>&lt;&lt;</code> 从键盘读取</h3><ul><li><code>&lt;&lt;</code> 符号的作用是将键盘的输入重定向为某个命令的输入</li><li><code>sort -n &lt;&lt; END</code></li></ul><h2 id="管道符号"><a href="#管道符号" class="headerlink" title="| 管道符号"></a><code>|</code> 管道符号</h2><p>将一个命令的输出作为另一个命令的输入</p><ul><li>传说中的 ”管道符号“：<code>|</code></li><li><code>|</code>符号既然被称为”管道符“，其作用就是”建立命令管道“</li><li>管道也算是重定向流的一种</li><li><code>cut -d , -f 1 notes.csv | sort &gt; sorted_names.txt</code></li><li><code>du -h | sort -n | head</code></li><li><code>sudo grep log -Ir /var/log | cut -d : -f 1 | sort | uniq</code><ul><li><code>-I</code>：排除二进制文件</li><li><code>-r</code>：用于递归遍历</li></ul></li></ul><h2 id="进程和系统监测"><a href="#进程和系统监测" class="headerlink" title="进程和系统监测"></a>进程和系统监测</h2><ul><li><p><code>w</code>：显示目前登入系统的用户信息</p></li><li><p><code>uptime</code>：查看Linux系统负载信息</p><blockquote><p>能够打印系统总共运行了多长时间和系统的平均负载。uptime命令可以显示的信息显示依次为：现在时间、系统已经运行了多长时间、目前有多少登陆用户、系统在过去的1分钟、5分钟和15分钟内的平均负载。</p></blockquote></li><li><p><code>tload</code>：显示系统负载状况</p><blockquote><p>以图形化的方式输出当前系统的平均负载到指定的终端。假设不给予终端机编号，则会在执行tload指令的终端机显示负载情形</p></blockquote></li><li><p><code>who</code>：显示目前登录系统的用户信息</p><blockquote><p>显示目前登录系统的用户信息。执行who命令可得知目前有那些用户登入系统，单独执行who命令会列出登入帐号，使用的终端机，登入时间以及从何处登入或正在使用哪个X显示器。</p></blockquote></li><li><p><code>ps</code>：报告当前系统的进程状态 process status</p><ul><li>输出<ul><li>UID：运行进程的用户</li><li>PID：进程号，process identifier，每个进程有唯一的进程号</li><li>PPID：程序的父进程号，parent process ID</li><li>TTY：进程运行所在的终端</li><li>TIME：进程运行的时间</li><li>CMD：产生这个进程的程序名</li></ul></li><li>实践<ul><li><code>-ef</code>：列出所有用户在所有终端的所有进程，可以配合管道方便查看 <code>ps -ef | less</code></li><li><code>-efH</code>：按照乔木状列出进程</li><li><code>-u</code>：列出此用户运行的进程</li><li><code>-aux</code>：通过CPU和内存使用来过滤进程<ul><li><code>ps -aux --sort -pcpu | less</code>：根据CPU使用率降序排列</li><li><code>ps -aux --sort -pmem | less</code>：根据内存使用率降序排列</li></ul></li><li><code>ps -axjf</code> ，<code>pstree</code>：以树形结构显示进程</li></ul></li></ul><blockquote><p>用于报告当前系统的进程状态。可以搭配<code>kill</code>指令随时中断、删除不必要的程序。ps命令是最基本同时也是非常强大的进程查看命令，使用该命令可以确定有哪些进程正在运行和运行的状态、进程是否结束、进程有没有僵死、哪些进程占用了过多的资源等等，总之大部分信息都是可以通过执行该命令得到的。</p></blockquote></li><li><p><code>top</code>：显示或管理执行中的程序</p><ul><li>q键：退出</li><li>h键：显示帮助文档</li><li>B键：加粗某些信息</li><li>f&#x2F;F键：在进程列表中添加或删除某些列</li><li>u键：依照用户来过滤显示</li><li>k键：结束某个进程</li><li>s键：改变刷新页面的时间，默认地页面每个3秒刷新一次</li></ul></li><li><p><code>kill</code>：根据进程号结束一个进程</p><ul><li><code>-9</code>：强制结束进程</li></ul></li><li><p><code>killall</code>：根据进程名结束所有进程，可以结合<code>find</code></p></li></ul><h3 id="glances"><a href="#glances" class="headerlink" title="glances"></a>glances</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">apt install glances -y</span></span><br></pre></td></tr></table></figure><img src="https://i.loli.net/2020/03/29/mUqi9MRt6vT2HIK.png" style="zoom:25%;" /><h3 id="htop"><a href="#htop" class="headerlink" title="htop"></a>htop</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">apt install htop -y</span></span><br></pre></td></tr></table></figure><img src="https://i.loli.net/2020/03/29/jZbnwPI3BKq4lES.png" style="zoom:25%;" /><h2 id="管理前后台进程"><a href="#管理前后台进程" class="headerlink" title="管理前后台进程"></a>管理前后台进程</h2><img src="https://i.loli.net/2020/03/29/Y15XE8zCdVKDrJP.png" style="zoom:80%;" /><h3 id="符号：后台运行进程"><a href="#符号：后台运行进程" class="headerlink" title="&amp; 符号：后台运行进程"></a><code>&amp;</code> 符号：后台运行进程</h3><ul><li><code>cp file.txt file-copy.txt &amp;</code></li><li><code>find / -name &quot;*log&quot; &gt; output_find 2&gt;&amp;1 &amp;</code></li></ul><h3 id="nohup-使进程与终端分离"><a href="#nohup-使进程与终端分离" class="headerlink" title="nohup: 使进程与终端分离"></a><code>nohup</code>: 使进程与终端分离</h3><p>无论是否将 nohup 命令的输出重定向到终端，输出都将附加到当前目录的 nohup.out 文件中。hangup，挂起</p><p>如果当前目录的 nohup.out 文件不可写，输出重定向到<code>$HOME/nohup.out</code>文件中。如果没有文件能创建或打开以用于追加，那么 command 参数指定的命令不可调用。如果标准错误是一个终端，那么把指定的命令写给标准错误的所有输出作为标准输出重定向到相同的文件描述符。</p><h3 id="Ctrl-Z、bg-进程转为后台运行"><a href="#Ctrl-Z、bg-进程转为后台运行" class="headerlink" title="Ctrl + Z、bg 进程转为后台运行"></a>Ctrl + Z、bg 进程转为后台运行</h3><h3 id="jobs：显示后台进程状态"><a href="#jobs：显示后台进程状态" class="headerlink" title="jobs：显示后台进程状态"></a>jobs：显示后台进程状态</h3><h3 id="fg：使进程转为前台运行"><a href="#fg：使进程转为前台运行" class="headerlink" title="fg：使进程转为前台运行"></a>fg：使进程转为前台运行</h3><h2 id="文件的解压和压缩"><a href="#文件的解压和压缩" class="headerlink" title="文件的解压和压缩"></a>文件的解压和压缩</h2><ul><li><code>tar</code>：Linux下的归档使用工具，用来打包和备份<ul><li><code>-cvf</code>：创建一个 tar 归档，<code>tar -cvf a.tar a/</code><ul><li>c：create 的缩写，表示创建</li><li>v：verbose 的缩写，表示冗余。会显示操作细节</li><li>f：file 的缩写，表示文件，指定归档文件</li></ul></li><li><code>-tf</code>：显示归档里的内容，并不解开归</li><li><code>-xvf</code>：解开归档<ul><li>x：extract 的缩写，表示”提取，取出“</li></ul></li><li><code>-zcvf</code>：归档，然后用 gzip 来压缩归档</li><li><code>-zxvf</code>：解gzip压缩，然后解开归档</li><li><code>-jcvf</code>：归档，然后用 bzip2 来压缩归档</li><li><code>-jxvf</code>：解bzip2压缩，然后解开归档</li></ul></li><li><code>zcat</code>、<code>zmore</code>、<code>zless</code></li><li><code>bzcat</code>、<code>bzmore</code>、<code>bzless</code></li><li><code>unzip</code>、<code>unrar</code>：<code>apt install zip</code></li></ul><h2 id="定时和延时执行"><a href="#定时和延时执行" class="headerlink" title="定时和延时执行"></a>定时和延时执行</h2><h3 id="date：显示或设置系统时间与日期"><a href="#date：显示或设置系统时间与日期" class="headerlink" title="date：显示或设置系统时间与日期"></a>date：显示或设置系统时间与日期</h3><ul><li><code>date +&quot;%H&quot;</code>：11</li><li><code>date +&quot;%H:%M:%S&quot;</code>：11:15:51</li><li><code>date +&quot;%Y-%m-%d&quot;</code>：2020-03-29</li><li><code>date +&quot;%Y-%m-%d %H:%M:%S&quot;</code>：2020-03-29 11:15:51</li><li>修改系统时间：<code>date 10121430</code></li></ul><h3 id="at：在指定时间执行一个任务"><a href="#at：在指定时间执行一个任务" class="headerlink" title="at：在指定时间执行一个任务"></a>at：在指定时间执行一个任务</h3><p><strong>at命令</strong> 用于在指定时间执行命令。<code>at</code>允许使用一套相当复杂的指定时间的方法。它能够接受在当天的<code>hh:mm（小时:分钟）</code>式的时间指定。假如该时间已过去，那么就放在第二天执行。当然也能够使用<code>midnight</code>（深夜），<code>noon</code>（中午），<code>teatime</code>（饮茶时间，一般是下午4点）等比较模糊的词语来指定时间。用户还能够采用12小时计时制，即在时间后面加上AM（上午）或PM（下午）来说明是上午还是下午。 也能够指定命令执行的具体日期，指定格式为<code>month day（月 日）</code>或 <code>mm/dd/yy（月/日/年）</code>或<code>dd.mm.yy（日.月.年）</code>。指定的日期必须跟在指定时间的后面。</p><p>上面介绍的都是绝对计时法，其实还能够使用相对计时法，这对于安排不久就要执行的命令是很有好处的。指定格式为：<code>now + count time-units</code>，<code>now</code>就是当前时间，time-units 是时间单位，这里能够是<code>minutes</code>（分钟）、<code>hours</code>（小时）、<code>days</code>（天）、<code>weeks</code>（星期）。<code>count</code>是时间的数量，究竟是几天，还是几小时，等等。 更有一种计时方法就是直接使用<code>today</code>（今天）、<code>tomorrow</code>（明天）来指定完成命令的时间。</p><ul><li><code>at 22:10</code>，会提示让输入命令，以<code>&lt;EOT&gt;</code>结束输入</li><li><code>at 22:10 tomorrow</code>：明天 22:10 执行</li><li><code>at 22:10 03/30/2020</code>：在 2020年3月20号22:10执行</li><li><code>at now +10 minutes</code>：10分钟之后执行</li></ul><h3 id="atq：列出当前用户的at任务列表"><a href="#atq：列出当前用户的at任务列表" class="headerlink" title="atq：列出当前用户的at任务列表"></a>atq：列出当前用户的at任务列表</h3><ul><li>q是英语 queue 的首字母，表示“队列”</li></ul><p><strong>atq命令</strong> 显示系统中待执行的任务列表，也就是列出当前用户的at任务列表。</p><h3 id="atrm：删除待执行任务队列中的指定任务"><a href="#atrm：删除待执行任务队列中的指定任务" class="headerlink" title="atrm：删除待执行任务队列中的指定任务"></a>atrm：删除待执行任务队列中的指定任务</h3><p><strong>atrm命令</strong> 用于删除待执行任务队列中的指定任务。</p><h3 id="sleep：将目前动作延迟一段时间"><a href="#sleep：将目前动作延迟一段时间" class="headerlink" title="sleep：将目前动作延迟一段时间"></a>sleep：将目前动作延迟一段时间</h3><ul><li><code>touch file.txt;sleep 10;rm file.txt</code></li></ul><h3 id="、-、"><a href="#、-、" class="headerlink" title="&amp;&amp;、||、;"></a><code>&amp;&amp;</code>、<code>||</code>、<code>;</code></h3><ul><li><code>&amp;&amp;</code>：<code>&amp;&amp;</code> 号前的命令执行成功，才会执行后面的命令</li><li><code>||</code>：<code>||</code> 号前的命令执行失败，才会执行后面的命令</li><li><code>;</code>：不论分号前的命令执行成功与否，都执行分号后的命令</li></ul><h3 id="crontab-提交和管理用户的需要周期性执行的任务"><a href="#crontab-提交和管理用户的需要周期性执行的任务" class="headerlink" title="crontab 提交和管理用户的需要周期性执行的任务"></a>crontab 提交和管理用户的需要周期性执行的任务</h3><p><strong>crontab命令</strong> 被用来提交和管理用户的需要周期性执行的任务，与windows下的计划任务类似，当安装完成操作系统后，默认会安装此服务工具，并且会自动启动crond进程，crond进程每分钟会定期检查是否有要执行的任务，如果有要执行的任务，则自动执行该任务。</p><ul><li><code>-e</code>：编辑该用户的计时器设置；</li><li><code> -l</code>：列出该用户的计时器设置；</li><li><code> -r</code>：删除该用户的计时器设置；</li><li><code>-u &lt;用户名称&gt;</code>：指定要设定计时器的用户名称。</li></ul><blockquote><p> m h dom mon dow  command</p><ul><li><p>顺序口诀：分 时 日 月 周</p></li><li><p>m：minute 的缩写，表示”分钟“（0~59）</p></li><li><p>h：hour 的缩写，表示“小时”(0~23)</p></li><li><p>dom：day of month 的缩写，表示“一个月的哪一天”（0~31）</p></li><li><p>mon：month 的缩写，表示”月份“（1~12）</p></li><li><p>dow：day of week 的缩写，表示“星期几”(0~6,星期日是0)</p></li><li><p>command：英语“命令”的意思，表示需要定时执行的命令</p></li></ul></blockquote><p><strong>特殊字符：</strong></p><ul><li>星号（*）：代表所有可能的值，例如month字段如果是星号，则表示在满足其它字段的制约条件后每月都执行该命令操作。</li><li>逗号（,）：可以用逗号隔开的值指定一个列表范围，例如，“1,2,5,7,8,9”</li><li>中杠（-）：可以用整数之间的中杠表示一个整数范围，例如“2-6”表示“2,3,4,5,6”</li><li>正斜线（&#x2F;）：可以用正斜线指定时间的间隔频率，例如“0-23&#x2F;2”表示每两小时执行一次。同时正斜线可以和星号一起使用，例如*&#x2F;10，如果用在minute字段，表示每十分钟执行一次。</li></ul><h4 id="实例"><a href="#实例" class="headerlink" title="实例"></a>实例</h4><blockquote><p>一定要换行，不换行不会生效的<br>重启 cron 服务：<code>systemctl cron.service restart</code></p></blockquote><p><strong>每1分钟执行一次command</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">* * * * * command</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>每小时的第3和第15分钟执行</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">3,15 * * * * command</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>在上午8点到11点的第3和第15分钟执行</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">3,15 8-11 * * * command</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>每隔两天的上午8点到11点的第3和第15分钟执行</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">3,15 8-11 */2 * * command</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>每个星期一的上午8点到11点的第3和第15分钟执行</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">3,15 8-11 * * 1 command</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>每晚的21:30重启smb</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">30 21 * * * /etc/init.d/smb restart</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>每月1、10、22日的4 : 45重启smb</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">45 4 1,10,22 * * /etc/init.d/smb restart</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>每周六、周日的1:10重启smb</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">10 1 * * 6,0 /etc/init.d/smb restart</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>每天18 : 00至23 : 00之间每隔30分钟重启smb</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">*/30 18-23 * * * /etc/init.d/smb restart</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>每星期六的晚上11:00 pm重启smb</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">0 23 * * 6 /etc/init.d/smb restart</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>每一小时重启smb</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">* */1 * * * /etc/init.d/smb restart</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>晚上11点到早上7点之间，每隔一小时重启smb</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">* 23-7/1 * * * /etc/init.d/smb restart</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>每月的4号与每周一到周三的11点重启smb</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">0 11 4 * mon-wed /etc/init.d/smb restart</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>一月一号的4点重启smb</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">0 4 1 jan * /etc/init.d/smb restart</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>每小时执行<code>/etc/cron.hourly</code>目录内的脚本</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">01 * * * * root run-parts /etc/cron.hourly</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="SSH"><a href="#SSH" class="headerlink" title="SSH"></a>SSH</h2><ul><li>全局 SSH 客户端的配置：<code>/etc/ssh/ssh_config</code></li><li>用户 SSH 客户端的配置：<code>~/.ssh/config</code><ul><li>如果 <code>~/.ssh</code> 不存在，执行 <code>ssh localhost</code></li><li><code>nano config</code> 进行编辑</li></ul></li><li>SSH 服务端的配置：<code>/etc/ssh/sshd_config</code></li></ul><h3 id="服务端-config-文件的常用配置参数"><a href="#服务端-config-文件的常用配置参数" class="headerlink" title="服务端 config 文件的常用配置参数"></a>服务端 config 文件的常用配置参数</h3><ul><li>Port：sshd 服务端口号（默认是22）</li><li>PermitRootLogin：是否允许以 root 用户身份登录（默认是可以）</li><li>PasswordAuthentication：是否允许密码验证登录（默认是可以）</li><li>PubkeyAuthentication：是否允许公钥验证登录（默认是可以）</li><li>PermitEmptyPasswords：是否允许空密码登录（不安全。默认不可以）</li></ul><h3 id="客户端-config-文件的配置"><a href="#客户端-config-文件的配置" class="headerlink" title="客户端 config 文件的配置"></a>客户端 config 文件的配置</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">nano ~/.ssh/config</span></span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Host aliyun</span><br><span class="line">  HostName 47.98.152.68</span><br><span class="line">  Port 22</span><br><span class="line">  User root</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">ssh aliyun</span></span><br></pre></td></tr></table></figure><h3 id="配置免密码登录"><a href="#配置免密码登录" class="headerlink" title="配置免密码登录"></a>配置免密码登录</h3><ol><li><p>在客户机中生成密钥对（公钥和私钥）</p><blockquote><p><code>ssh-keygen -t rsa -C &quot;youngjuning@aliyun.com&quot;</code></p></blockquote><ul><li><p>在 <code>~/.ssh/</code> 目录下，会新生成两个文件</p><ul><li>id_rsa：私钥</li><li>id_rsa.pub：公钥</li></ul></li></ul></li><li><p><code>ssh-copy-id</code>：把本地的 ssh 公钥文件安装到远程主机对应的账户下</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">ssh-copy-id -i ~/.ssh/id_rsa.pub root@47.98.152.68</span></span><br></pre></td></tr></table></figure><blockquote><p><strong>ssh-copy-id命令</strong> 可以把本地主机的公钥复制到远程主机的<code>authorized_keys</code>文件上，<code>ssh-copy-id</code> 命令也会给远程主机的用户主目录（home）和<code>~/.ssh</code>, 和<code>~/.ssh/authorized_keys</code>设置合适的权限。</p><ul><li><code>-i</code>：指定公钥文件</li></ul></blockquote></li></ol><p><strong>设置免密码后仍想使用密码登录：</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no root@47.98.152.68</span></span><br></pre></td></tr></table></figure><p><strong>测试Github是否正确配置免密码登录：</strong></p><p><code>ssh -T git@github.com</code></p><h2 id="Linux-网络"><a href="#Linux-网络" class="headerlink" title="Linux 网络"></a>Linux 网络</h2><h3 id="wget：Linux系统下载文件工具"><a href="#wget：Linux系统下载文件工具" class="headerlink" title="wget：Linux系统下载文件工具"></a>wget：Linux系统下载文件工具</h3><p><strong>wget命令</strong> 用来从指定的URL下载文件。wget非常稳定，它在带宽很窄的情况下和不稳定网络中有很强的适应性，如果是由于网络的原因下载失败，wget会不断的尝试，直到整个文件下载完毕。如果是服务器打断下载过程，它会再次联到服务器上从停止的地方继续下载。这对从那些限定了链接时间的服务器上下载大文件非常有用。</p><p>wget支持HTTP，HTTPS和FTP协议，可以使用HTTP代理。所谓的自动下载是指，wget可以在用户退出系统的之后在后台执行。这意味这你可以登录系统，启动一个wget下载任务，然后退出系统，wget将在后台执行直到任务完成，相对于其它大部分浏览器在下载大量数据时需要用户一直的参与，这省去了极大的麻烦。</p><p>用于从网络上下载资源，没有指定目录，下载资源回默认为当前目录。wget虽然功能强大，但是使用起来还是比较简单：</p><ol><li><strong>支持断点下传功能</strong> 这一点，也是网络蚂蚁和FlashGet当年最大的卖点，现在，Wget也可以使用此功能，那些网络不是太好的用户可以放心了；</li><li><strong>同时支持FTP和HTTP下载方式</strong> 尽管现在大部分软件可以使用HTTP方式下载，但是，有些时候，仍然需要使用FTP方式下载软件；</li><li><strong>支持代理服务器</strong> 对安全强度很高的系统而言，一般不会将自己的系统直接暴露在互联网上，所以，支持代理是下载软件必须有的功能；</li><li><strong>设置方便简单</strong> 可能，习惯图形界面的用户已经不是太习惯命令行了，但是，命令行在设置上其实有更多的优点，最少，鼠标可以少点很多次，也不要担心是否错点鼠标；</li><li><strong>程序小，完全免费</strong> 程序小可以考虑不计，因为现在的硬盘实在太大了；完全免费就不得不考虑了，即使网络上有很多所谓的免费软件，但是，这些软件的广告却不是我们喜欢的。</li></ol><h3 id="scp：加密的方式在本地主机和远程主机之间复制文件"><a href="#scp：加密的方式在本地主机和远程主机之间复制文件" class="headerlink" title="scp：加密的方式在本地主机和远程主机之间复制文件"></a>scp：加密的方式在本地主机和远程主机之间复制文件</h3><p><strong>scp命令</strong> 用于在Linux下进行远程拷贝文件的命令，和它类似的命令有cp，不过cp只是在本机进行拷贝不能跨服务器，而且scp传输是加密的。可能会稍微影响一下速度。当你服务器硬盘变为只读read only system时，用scp可以帮你把文件移出来。另外，scp还非常不占资源，不会提高多少系统负荷，在这一点上，rsync就远远不及它了。虽然 rsync比scp会快一点，但当小文件众多的情况下，rsync会导致硬盘I&#x2F;O非常高，而scp基本不影响系统正常使用。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ scp file.txt root@192.168.2.195</span><br></pre></td></tr></table></figure><h3 id="netstat-ntulp-grep-9001"><a href="#netstat-ntulp-grep-9001" class="headerlink" title="netstat -ntulp | grep 9001"></a>netstat -ntulp | grep 9001</h3><p>查看端口占用情况</p><h3 id="rsync：远程数据同步工具"><a href="#rsync：远程数据同步工具" class="headerlink" title="rsync：远程数据同步工具"></a>rsync：远程数据同步工具</h3><p><strong>rsync命令</strong> 是一个远程数据同步工具，可通过LAN&#x2F;WAN快速同步多台主机间的文件。rsync使用所谓的“rsync算法”来使本地和远程两个主机之间的文件达到同步，这个算法只传送两个文件的不同部分，而不是每次都整份传送，因此速度相当快。 rsync是一个功能非常强大的工具，其命令也有很多功能特色选项，我们下面就对它的选项一一进行分析说明。</p><ul><li>默认地，rsync 在同步时并不会删除目标目录的文件。例如，你的源目录（被同步目录）中删除了一个文件，但是用 rsync 同步时，它并不会删除同步目录中的相同文件。<code>rsync -arv --delete</code> 可以删除</li></ul><p><strong>用 rsync 备份到同一台电脑的其他目录：</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">rsync -arv Images/ backups</span></span><br></pre></td></tr></table></figure><ul><li><code>-a</code>：保留文件的所有信息，包括权限，修改日期，等等。a 是 archive 的缩写，是<strong>归档</strong>的意思</li><li><code>-r</code>：递归调用。表示子目录的所有文件也都包括。r 是 recursive 的缩写，是<strong>递归</strong>的意思</li><li><code>-v</code> ：冗余模式。输出详细操作信息。v 是 verbose 的缩写，是<strong>冗余的</strong>意思</li></ul><p><strong>用 rsync 备份到同步本地文件到服务器：</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">rsync -arvz --progress --delete ~/Desktop/blog root@47.98.152.68:/home/yangjunning/</span></span><br></pre></td></tr></table></figure><h2 id="备份到七牛云"><a href="#备份到七牛云" class="headerlink" title="备份到七牛云"></a>备份到七牛云</h2><h3 id="安装命令行工具-qshell"><a href="#安装命令行工具-qshell" class="headerlink" title="安装命令行工具(qshell)"></a>安装命令行工具(qshell)</h3><ol><li>进入家目录：<code>cd ~</code></li><li>下载压缩包：<code>wget http://devtools.qiniu.com/qshell-linux-x86-v2.4.1.zip</code></li><li>解压压缩包：<code>unzip ~/qshell-linux-x86-v2.4.1.zip</code></li><li>任何位置运行：<code>mv ~/qshell-linux-x86-v2.4.1 /usr/local/bin/qshell</code></li><li>权限：<code>chmod +x /usr/local/bin/qshell</code></li><li>删除压缩包：<code>rm -rf ~/qshell-linux-x86-v2.4.1.zip</code></li></ol><h3 id="密钥设置"><a href="#密钥设置" class="headerlink" title="密钥设置"></a>密钥设置</h3><p>需要鉴权的命令都需要依赖七牛账号下的 <code>AccessKey</code> 和 <code>SecretKey</code>。所以这类命令运行之前，需要使用 <code>account</code> 命令来设置下 <code>AccessKey</code> ，<code>SecretKey</code> 。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ qshell account -- ak sk name</span><br></pre></td></tr></table></figure><blockquote><p>注意：<code>ak</code>、<code>sk</code> 在七牛云<strong>控制台</strong> -&gt; <strong>个人中心</strong> - &gt; <strong>密钥管理</strong>内。</p></blockquote><p>可以连续使用 <code>qshell account</code> 添加账号<code>ak</code>, <code>sk</code>, <code>name</code>信息，qshell会保存这些账号的信息， 可以使用<code>qshell user</code>命令列举账号信息，在各个账号之间切换, 删除账号等</p><h3 id="账户管理"><a href="#账户管理" class="headerlink" title="账户管理"></a>账户管理</h3><p>使用qshell user子命令可以用来管理记录的多账户信息。</p><ol><li><code>qshell user ls</code>可以列举账户下所有的账户信息</li><li><code>qshell user cu</code> 可以用来切换账户</li><li><code>qshell user cu</code> 不携带的话会切换到最近的上个账户；比如我在A账户做完操作后，使用<code>qshell user cu B</code>到了B 账户，那么使用<code>qshell user cu</code>可以切回到A账户</li></ol><h3 id="qupload2"><a href="#qupload2" class="headerlink" title="qupload2"></a>qupload2</h3><p>同步数据到七牛空间， 带同步进度信息，和数据上传完整性检查（命令式），详情请查看<a href="http://github.com/qiniu/qshell/blob/master/docs/qupload2.md">文档</a></p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ qshell qupload2 --src-dir=/root --bucket=aliyun-server-backup</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/ef588191ce47/</id>
    <link href="https://blog.zisheng.pro/posts/ef588191ce47/"/>
    <published>2020-03-28T12:39:42.000Z</published>
    <summary>本文主要讲解了写给前端的 Linux 实战教程，简单易懂，又有实战操作，对你有一定的参考价值，需要的朋友可以学习下。</summary>
    <title>写给前端的 Linux 实战教程</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="前端工程化" scheme="https://blog.zisheng.pro/tags/%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%8C%96/"/>
    <category term="规范" scheme="https://blog.zisheng.pro/tags/%E8%A7%84%E8%8C%83/"/>
    <category term="前端规范化" scheme="https://blog.zisheng.pro/tags/%E5%89%8D%E7%AB%AF%E8%A7%84%E8%8C%83%E5%8C%96/"/>
    <category term="eslint" scheme="https://blog.zisheng.pro/tags/eslint/"/>
    <category term="editorconfig" scheme="https://blog.zisheng.pro/tags/editorconfig/"/>
    <category term="npm script" scheme="https://blog.zisheng.pro/tags/npm-script/"/>
    <category term="husky" scheme="https://blog.zisheng.pro/tags/husky/"/>
    <category term="lint-staged" scheme="https://blog.zisheng.pro/tags/lint-staged/"/>
    <category term="prettier" scheme="https://blog.zisheng.pro/tags/prettier/"/>
    <category term="standard-version" scheme="https://blog.zisheng.pro/tags/standard-version/"/>
    <category term="Conventional Commits" scheme="https://blog.zisheng.pro/tags/Conventional-Commits/"/>
    <content>
      <![CDATA[<p>本文以 React Native 项目为示例介绍了前端规范化开发涉及的工具及知识，但是所涉及内容具有通用性，建议读者点赞收藏(^▽^)。</p><blockquote><p>另外安利一下 <a href="https://github.com/sigmayun/react-native-template-typescript">@sigmayun&#x2F;react-native-template-typescript</a>，这是一个基于 typescript 的 react native 开箱即用的模板。本文所涉及的大部分内容都集成到了该模板中。</p><ul><li><a href="https://github.com/sishuguojixuefu/eslint-config">sishuguojixuefu&#x2F;eslint-config</a>：ESLint config for sishuguojixuefu</li><li><a href="https://github.com/youngjuning/conventional-commits-demo">conventional-commits-demo</a>：本文实践的配套代码库</li></ul></blockquote><h2 id="gitconfig"><a href="#gitconfig" class="headerlink" title="gitconfig"></a>gitconfig</h2><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 初始化设置</span></span><br><span class="line">$ git config --global user.name <span class="string">&#x27;your_name&#x27;</span></span><br><span class="line">$ git config --global user.email <span class="string">&#x27;your_email@aliyun.com&#x27;</span></span><br><span class="line"><span class="comment"># 提高命令输出的可读性</span></span><br><span class="line">$ git config --global color.ui auto</span><br><span class="line"><span class="comment"># git 记住用户名和密码</span></span><br><span class="line">$ git config --global credential.helper store</span><br><span class="line"><span class="comment"># core.autocrlf</span></span><br><span class="line">$ git config --global core.autocrlf input</span><br></pre></td></tr></table></figure><p>以上配置内容可以在 <code>~/.gitconfig</code> 文件中找到。</p><h2 id="npm-scripts"><a href="#npm-scripts" class="headerlink" title="npm scripts"></a>npm scripts</h2><p><code>package.json</code> 文件的 scripts 字段是定义可执行脚本用的，可以利用它来实现简单的工作流。如果不是大型的项目，不建议使用 gulp，npm scripts 一样可以完成工作流设计的任务。下面是 React Native 项目默认的 scripts，如今我们可以把使用和扩展 scripts 看做是前端基本功。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;android&quot;</span><span class="punctuation">:</span> <span class="string">&quot;react-native run-android&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;ios&quot;</span><span class="punctuation">:</span> <span class="string">&quot;react-native run-ios&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;start&quot;</span><span class="punctuation">:</span> <span class="string">&quot;react-native start&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;test&quot;</span><span class="punctuation">:</span> <span class="string">&quot;jest&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;lint&quot;</span><span class="punctuation">:</span> <span class="string">&quot;eslint .&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="使用-EditorConfig-实现跨编辑器代码风格统一"><a href="#使用-EditorConfig-实现跨编辑器代码风格统一" class="headerlink" title="使用 EditorConfig 实现跨编辑器代码风格统一"></a>使用 EditorConfig 实现跨编辑器代码风格统一</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"># EditorConfig is awesome: http://EditorConfig.org</span><br><span class="line"></span><br><span class="line"># top-most EditorConfig file</span><br><span class="line">root = true</span><br><span class="line"></span><br><span class="line"># Unix-style newlines with a newline ending every file</span><br><span class="line">[*]</span><br><span class="line">indent_style = space</span><br><span class="line">indent_size = 2</span><br><span class="line">end_of_line = lf</span><br><span class="line">charset = utf-8</span><br><span class="line">trim_trailing_whitespace = true</span><br><span class="line">insert_final_newline = true</span><br><span class="line"></span><br><span class="line">[*.md]</span><br><span class="line">trim_trailing_whitespace = false</span><br><span class="line"></span><br><span class="line">[*.gradle]</span><br><span class="line">indent_size = 4</span><br><span class="line"></span><br><span class="line">[BUCK]</span><br><span class="line">indent_size = 4</span><br></pre></td></tr></table></figure><h2 id="使用ESLint规范代码"><a href="#使用ESLint规范代码" class="headerlink" title="使用ESLint规范代码"></a>使用ESLint规范代码</h2><blockquote><p>作者自己团队的 ESLint 方案是基于 eslint-config-airbnb 扩展的，有兴趣可以体验一下 <a href="https://github.com/sishuguojixuefu/eslint-config">@sishuguojixuefu&#x2F;eslint-config</a></p></blockquote><h3 id="安装依赖"><a href="#安装依赖" class="headerlink" title="安装依赖"></a>安装依赖</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 安装 eslint-config-airbnb 及其 peerDependencies</span></span><br><span class="line">$ yarn global add install-peerdeps</span><br><span class="line">$ install-peerdeps -D eslint-config-airbnb</span><br><span class="line"><span class="comment"># 支持 react-native</span></span><br><span class="line">$ yarn add -D eslint-plugin-react-native</span><br><span class="line"><span class="comment"># 支持 typescript</span></span><br><span class="line">$ yarn add -D @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-import-resolver-typescript typescript</span><br><span class="line"><span class="comment"># 支持 prettier</span></span><br><span class="line">$ yarn add -D prettier eslint-plugin-prettier eslint-config-prettier</span><br><span class="line"><span class="comment"># 其他</span></span><br><span class="line">$ yarn add -D eslint-plugin-promise eslint-plugin-you-dont-need-lodash-underscore eslint-plugin-you-dont-need-momentjs</span><br></pre></td></tr></table></figure><h3 id="package-json"><a href="#package-json" class="headerlink" title="package.json"></a>package.json</h3><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">   <span class="attr">&quot;eslintConfig&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;extends&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;airbnb&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;airbnb/hooks&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;plugin:react-native/all&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;plugin:@typescript-eslint/recommended&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;plugin:promise/recommended&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;plugin:you-dont-need-lodash-underscore/compatible&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;plugin:you-dont-need-momentjs/recommended&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;plugin:prettier/recommended&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;prettier&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;prettier/react&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;prettier/@typescript-eslint&quot;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;env&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;react-native/react-native&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;parser&quot;</span><span class="punctuation">:</span> <span class="string">&quot;@typescript-eslint/parser&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;parserOptions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;ecmaFeatures&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;jsx&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;settings&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;import/extensions&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;.js&quot;</span><span class="punctuation">,</span> <span class="string">&quot;.ts&quot;</span><span class="punctuation">,</span> <span class="string">&quot;.tsx&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;import/resolver&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;typescript&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;alwaysTryTypes&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="prettierrc-js"><a href="#prettierrc-js" class="headerlink" title=".prettierrc.js"></a>.prettierrc.js</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">printWidth</span>: <span class="number">150</span>, <span class="comment">// 一行的字符数，如果超过会进行换行，默认为80</span></span><br><span class="line">  <span class="attr">tabWidth</span>: <span class="number">2</span>, <span class="comment">// tab缩进大小,默认为2</span></span><br><span class="line">  <span class="attr">useTabs</span>: <span class="literal">false</span>, <span class="comment">// 使用tab缩进，默认false</span></span><br><span class="line">  <span class="attr">semi</span>: <span class="literal">false</span>, <span class="comment">// 使用分号, 默认true</span></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 行尾逗号,默认none,可选 none|es5|all</span></span><br><span class="line"><span class="comment">   * es5 包括es5中的数组、对象</span></span><br><span class="line"><span class="comment">   * all 包括函数对象等所有可选</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="attr">trailingComma</span>: <span class="string">&#x27;es5&#x27;</span>,</span><br><span class="line">  <span class="attr">singleQuote</span>: <span class="literal">true</span>, <span class="comment">// 使用单引号, 默认false(在jsx中配置无效, 默认都是双引号)</span></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 对象中的空格 默认true</span></span><br><span class="line"><span class="comment">   * true: &#123; foo: bar &#125;</span></span><br><span class="line"><span class="comment">   * false: &#123;foo: bar&#125;</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="attr">bracketSpacing</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * JSX标签闭合位置 默认false</span></span><br><span class="line"><span class="comment">   * false:</span></span><br><span class="line"><span class="comment">   * &lt;div</span></span><br><span class="line"><span class="comment">   *  className=&quot;&quot;</span></span><br><span class="line"><span class="comment">   *  style=&#123;&#123;&#125;&#125;</span></span><br><span class="line"><span class="comment">   * &gt;</span></span><br><span class="line"><span class="comment">   * true:</span></span><br><span class="line"><span class="comment">   * &lt;div</span></span><br><span class="line"><span class="comment">   *  className=&quot;&quot;</span></span><br><span class="line"><span class="comment">   * style=&#123;&#123;&#125;&#125; &gt;</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="attr">jsxBracketSameLine</span>: <span class="literal">false</span>,</span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 箭头函数参数括号 默认avoid 可选 avoid| always</span></span><br><span class="line"><span class="comment">   * avoid 能省略括号的时候就省略 例如x =&gt; x</span></span><br><span class="line"><span class="comment">   * always 总是有括号</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="attr">arrowParens</span>: <span class="string">&#x27;avoid&#x27;</span>,</span><br><span class="line">  <span class="attr">vueIndentScriptAndStyle</span>: <span class="literal">false</span>, <span class="comment">// vue 文件 script 和 style 标签缩进，默认false</span></span><br><span class="line">  <span class="attr">endOfLine</span>: <span class="string">&#x27;lf&#x27;</span>, <span class="comment">// 强制使用 unix 风格的换行符</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="插件介绍"><a href="#插件介绍" class="headerlink" title="插件介绍"></a>插件介绍</h3><ul><li>eslint-config-airbnb: 该软件包提供 Airbnb 的 .eslintrc 作为可扩展的共享配置</li><li>eslint-plugin-react-native: 针对 ESLint 的 React Native 特定 linting 规则</li><li>@typescript-eslint&#x2F;parser: 将 TypeScript 转换为 ESTree，使 eslint 可以识别</li><li>@typescript-eslint&#x2F;eslint-plugin: 一个包含一堆特定于 TypeScript 的 ESLint 规则的插件</li><li>eslint-import-resolver-typescript: 给 eslint-plugin-import 添加 typescript 支持的插件</li><li>eslint-plugin-prettier: 以 ESLint 插件的形式运行 prettier</li><li>eslint-config-prettier: 关闭所有不必要或可能与 prettier 的规则冲突的 ESLint 规则。一定要放到最后。</li></ul><h3 id="vscode-eslint-plugin-config"><a href="#vscode-eslint-plugin-config" class="headerlink" title="vscode eslint plugin config"></a>vscode eslint plugin config</h3><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;editor.codeActionsOnSave&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;source.fixAll.eslint&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;eslint.lintTask.enable&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;eslint.packageManager&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;eslint.alwaysShowStatus&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;eslint.validate&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">  <span class="string">&quot;javascript&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="string">&quot;javascriptreact&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="string">&quot;typescript&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="string">&quot;typescriptreact&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="string">&quot;vue&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="string">&quot;html&quot;</span></span><br><span class="line"><span class="punctuation">]</span></span><br></pre></td></tr></table></figure><h2 id="用-husky-和-lint-staged-构建超溜的代码检查工作流"><a href="#用-husky-和-lint-staged-构建超溜的代码检查工作流" class="headerlink" title="用 husky 和 lint-staged 构建超溜的代码检查工作流"></a>用 husky 和 lint-staged 构建超溜的代码检查工作流</h2><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add -D husky</span><br></pre></td></tr></table></figure><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;husky&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;hooks&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;pre-commit&quot;</span><span class="punctuation">:</span> <span class="string">&quot;tsc --noEmit &amp;&amp; lint-staged&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;lint-staged&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;**/*.&#123;js,jsx,ts,tsx&#125;&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;eslint --fix&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;git add&quot;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;**/*.&#123;md,json&#125;&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;prettier --write&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;git add&quot;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="Conventional-Commits-约定式提交规范"><a href="#Conventional-Commits-约定式提交规范" class="headerlink" title="Conventional Commits 约定式提交规范"></a>Conventional Commits 约定式提交规范</h2><p><a href="https://www.conventionalcommits.org/zh-hans/v1.0.0-beta.4/">Conventional Commits</a> 是一种用于给提交信息增加人机可读含义的规范。约定式提交规范是一种基于消息的轻量级约定。它提供了一组用于创建清晰的提交历史的简单规则；这使得编写基于规范的自动化工具变得更容易。这个约定与 <a href="http://semver.org/">SemVer</a> 相吻合，在提交信息中描述新特性、bug 修复和破坏性变更。</p><p>提交说明的结构如下所示：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&lt;类型&gt;([可选的作用域]): &lt;描述&gt;</span><br><span class="line"></span><br><span class="line">[可选的正文]</span><br><span class="line"></span><br><span class="line">[可选的脚注]</span><br></pre></td></tr></table></figure><h3 id="类型"><a href="#类型" class="headerlink" title="类型"></a>类型</h3><ul><li><code>build:</code>: 影响构建系统或外部依赖关系的更改（示例范围：gulp、broccoli、NPM）。</li><li><code>chore:</code>:  其他不修改<code>src</code>或<code>test</code>文件。</li><li><code>ci:</code>: 更改持续集成文件和脚本（示例范围：Travis、Circle、BrowserStack、SauceLabs）。</li><li><code>docs:</code>: 只是更改文档。</li><li><code>feat:</code>:  类型为 <code>feat</code> 的提交表示在代码库中新增了一个功能（这和语义化版本中的 <a href="https://semver.org/lang/zh-CN/#%E6%91%98%E8%A6%81"><code>MINOR</code></a> 相对应）。</li><li><code>fix:</code>：类型为 <code>fix</code> 的 提交表示在代码库中修复了一个 bug （这和语义化版本中的 <a href="https://semver.org/lang/zh-CN/#%E6%91%98%E8%A6%81"><code>PATCH</code></a> 相对应）。</li><li><code>improvement:</code>: 用于对当前实现进行改进而没有添加新功能或修复错误的提交。</li><li><code>perf:</code>: 改进性能的代码更改。</li><li><code>refactor:</code>: 代码重构，既不修复错误也不添加功能。</li><li><code>revert:</code>: commit 回退。</li><li><code>style:</code>: 不影响代码含义的变化（空白、格式化、缺少分号等）。</li><li><code>test:</code>: 添加确实测试或更正现有的测试。</li></ul><h3 id="范围"><a href="#范围" class="headerlink" title="范围"></a>范围</h3><p>可以为提交类型添加一个围在圆括号内的作用域，以为其提供额外的上下文信息。例如 <code>feat(parser): adds ability to parse arrays.</code>。</p><h3 id="BREAKING-CHANGE"><a href="#BREAKING-CHANGE" class="headerlink" title="BREAKING CHANGE"></a>BREAKING CHANGE</h3><p>在可选的正文或脚注的起始位置带有 <code>BREAKING CHANGE:</code> 的提交，表示引入了破坏性 API 变更（这和语义化版本中的 <a href="https://semver.org/lang/zh-CN/#%E6%91%98%E8%A6%81"><code>MAJOR</code></a> 相对应）。 破坏性变更可以是任意 <em>类型</em> 提交的一部分。</p><h3 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h3><h4 id="包含了描述以及正文内有破坏性变更的提交说明"><a href="#包含了描述以及正文内有破坏性变更的提交说明" class="headerlink" title="包含了描述以及正文内有破坏性变更的提交说明"></a>包含了描述以及正文内有破坏性变更的提交说明</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">feat: allow provided config object to extend other configs</span><br><span class="line"></span><br><span class="line">BREAKING CHANGE: `extends` key in config file is now used for extending other config files</span><br></pre></td></tr></table></figure><h4 id="包含了可选的-字符以提醒注意破坏性变更的提交说明"><a href="#包含了可选的-字符以提醒注意破坏性变更的提交说明" class="headerlink" title="包含了可选的 ! 字符以提醒注意破坏性变更的提交说明"></a>包含了可选的 <code>!</code> 字符以提醒注意破坏性变更的提交说明</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">chore!: drop Node 6 from testing matrix</span><br><span class="line"></span><br><span class="line">BREAKING CHANGE: dropping Node 6 which hits end of life in April</span><br></pre></td></tr></table></figure><h4 id="不包含正文的提交说明"><a href="#不包含正文的提交说明" class="headerlink" title="不包含正文的提交说明"></a>不包含正文的提交说明</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docs: correct spelling of CHANGELOG</span><br></pre></td></tr></table></figure><h4 id="包含作用域的提交说明"><a href="#包含作用域的提交说明" class="headerlink" title="包含作用域的提交说明"></a>包含作用域的提交说明</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">feat(lang): add polish language</span><br></pre></td></tr></table></figure><h4 id="为-fix-编写的提交说明，包含（可选的）-issue-编号"><a href="#为-fix-编写的提交说明，包含（可选的）-issue-编号" class="headerlink" title="为 fix 编写的提交说明，包含（可选的） issue 编号"></a>为 fix 编写的提交说明，包含（可选的） issue 编号</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">fix: correct minor typos in code</span><br><span class="line"></span><br><span class="line">see the issue for details on the typos fixed</span><br><span class="line"></span><br><span class="line">closes issue #12</span><br></pre></td></tr></table></figure><h3 id="约定式提交规范"><a href="#约定式提交规范" class="headerlink" title="约定式提交规范"></a>约定式提交规范</h3><ol><li>每个提交都<strong>必须</strong>使用类型字段前缀，它由一个名词组成，诸如<code>feat</code>或<code>fix</code>，其后接一个<strong>可选的</strong>作用域字段，以及一个<strong>必要的</strong>冒号（英文半角）和空格。</li><li>当一个提交为应用或类库实现了新特性时，<strong>必须</strong>使用<code>feat</code>类型。</li><li>当一个提交为应用修复 bug 时，<strong>必须</strong>使用<code>fix</code>类型。</li><li>作用域字段可以跟随在类型字段后面。作用有<strong>必须</strong>是一个描述某部分代码的名词，并用圆括号包围，例如：<code>fix(parser): </code></li><li>描述字段<strong>必须</strong>紧接在类型&#x2F;作用域前缀的空格之后。描述指的是对代码变更的简短总结，例如：<code>fix:array parsing issue when multiplejspaces were contained in string</code>。</li><li>在简短描述之后，<strong>可以</strong>编写更长的提交正文，为代码变更提供额外的上下文信息。正文<strong>必须</strong>起始于描述字段结束的一个空行后。</li><li>在正文结束的一个空行之后，<strong>可以</strong>编写一行或或多行脚注。脚注<strong>必须</strong>包含关于提交的元信息，例如：关联的合并请求、Reviewer、破坏性变更、每条元信息一行。</li><li>破坏性变更<strong>必须</strong>标示在正文区域最开始处，或脚注区域中某一行的开始。一个破坏性变更<strong>必须</strong>包含大写的文本<code>BREAKING CHANGE</code>，后面紧跟冒号和空格。</li><li>在<code>BREAKING CHANGE:</code>之后<strong>必须</strong>提供描述，以描述对 API 的变更。例如：<code>BREAKING CHANGE: environment variables now take precedence over config files</code>。</li><li>在提交说明中，<strong>可以</strong>使用<code>feat</code>和<code>fix</code>之外的类型。</li><li>工具的实现<strong>必须不</strong>区分大小写地解析构成约定式提交的信息单元，只有<code>BREAKING CHANGE</code>  <strong>必须</strong>是大写的。</li><li><strong>可以</strong>在类型&#x2F;作用域前缀之后，<code>:</code>之前，附加<code>!</code>字符，以进一步提醒注意破坏性变更。当有<code>!</code>前缀时，正文或脚注内必须包含<code>BREAKING CHANGE: description</code></li></ol><h3 id="为什么使用约定式提交"><a href="#为什么使用约定式提交" class="headerlink" title="为什么使用约定式提交"></a>为什么使用约定式提交</h3><ul><li>自动化生产 CHANGELOG。</li><li>基于提交的类型，自动决定语义化的版本变更。</li><li>向同事、公众与其他利益关系者传达变化的性质。</li><li>触发构建和部署流程。</li><li>让人们探索一个更加结构化的提交历史，以便降低对你的项目作出贡献的难度。</li></ul><h2 id="commitlint"><a href="#commitlint" class="headerlink" title="commitlint"></a>commitlint</h2><p>commitlint检查您的提交消息是否符合<a href="https://conventionalcommits.org/">conventional commit format</a>。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">yarn add -D @commitlint/cli @commitlint/config-conventional @commitlint/prompt-cli</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">yarn add -D husky lint-staged</span></span><br></pre></td></tr></table></figure><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// package.json</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;commit&quot;</span><span class="punctuation">:</span> <span class="string">&quot;commit&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;commitlint&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;extends&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;@commitlint/config-conventional&quot;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;husky&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;hooks&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;commit-msg&quot;</span><span class="punctuation">:</span> <span class="string">&quot;commitlint -E HUSKY_GIT_PARAMS&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="standard-version"><a href="#standard-version" class="headerlink" title="standard-version"></a>standard-version</h2><p><a href="https://link.zhihu.com/?target=https://github.com/conventional-changelog/standard-version">standard-version</a> 是一款遵循<a href="https://link.zhihu.com/?target=https://semver.org/">语义化版本（ semver）</a>和 <a href="https://link.zhihu.com/?target=https://conventionalcommits.org/">commit message 标准规范</a> 的版本和 changlog 自动化工具。通常情况线下，我们会在 master 分支进行如下的版本发布操作：</p><ol><li><code>git pull origin master</code></li><li>根据 <code>package.json</code> 中的 <code>version</code> 更新版本号，更新 CHANGELOG</li><li><code>git add .</code></li><li><code>git commit</code></li><li><code>git tag</code> 打版本操作</li><li><code>git push --follow-tags origin master &amp;&amp; npm publish</code>：push 版本 tag 和 master 分支到仓库并发布</li></ol><p>其中 <strong>2，3，4，5</strong> 是 standard-version 工具会自动完成的工作，配合本地的 shell 脚本，则可以自动完成一系列版本发布的工作了。</p><h3 id="安装-使用"><a href="#安装-使用" class="headerlink" title="安装 &amp; 使用"></a>安装 &amp; 使用</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">yarn add -D standard-version</span></span><br></pre></td></tr></table></figure><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// package.json</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;release&quot;</span><span class="punctuation">:</span> <span class="string">&quot;standard-version&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><ul><li>First Release：<code>yarn release --first-release</code></li><li>Cutting Release：<code>yarn release</code></li><li>Release as a Pre-Release：<code>yarn release --prerelease</code> or <code>yarn release --prerelease alpha</code></li><li>Release as a Target Type Imperatively (<code>npm version</code>-like)：<code>yarn release --release-as minor</code> or <code>yarn release --release-as 1.1.0</code>，可以合并 <code>--prerelease</code>以此方便发布实验性特性</li><li>Prevent Git Hooks：<code>yarn release --no-verify</code></li></ul><h2 id="资源参考"><a href="#资源参考" class="headerlink" title="资源参考"></a>资源参考</h2><ul><li><a href="%5Bhttps://git-scm.com/book/zh/v2/%E8%87%AA%E5%AE%9A%E4%B9%89-Git-Git-%E9%92%A9%E5%AD%90%5D(https://git-scm.com/book/zh/v2/%E8%87%AA%E5%AE%9A%E4%B9%89-Git-Git-%E9%92%A9%E5%AD%90)">Git 钩子</a></li><li><a href="https://juejin.im/post/5ce607946fb9a07ef5620e5b">前端自动化工作流中的hooks</a></li><li><a href="https://juejin.im/post/5bd2debfe51d457abc710b57">规范你的 commit message 并且根据 commit 自动生成 CHANGELOG.md</a></li><li><a href="https://zhuanlan.zhihu.com/p/51894196">git commit 、CHANGELOG 和版本发布的标准自动化</a></li><li><a href="https://juejin.im/post/5afc5242f265da0b7f44bee4">优雅的提交你的 Git Commit Message</a></li><li><a href="https://www.conventionalcommits.org/zh-hans/v1.0.0-beta.4/">Conventional Commits</a>：约定式提交，一种用于给提交信息增加人机可读含义的规范</li><li><a href="https://github.com/conventional-changelog/standard-version">standard-version</a>：<a href="https://link.zhihu.com/?target=https://github.com/conventional-changelog/standard-version">standard-version</a> 是一款遵循<a href="https://link.zhihu.com/?target=https://semver.org/">语义化版本（ semver）</a>和 <a href="https://link.zhihu.com/?target=https://conventionalcommits.org/">commit message 标准规范</a> 的版本和 changlog 自动化工具</li><li><a href="https://github.com/conventional-changelog/commitlint">commitlint</a>：检查您的提交消息是否符合<a href="https://conventionalcommits.org/">conventional commit format</a></li><li><a href="https://editorconfig.org/">EditorConfig</a>：EditorConfig有助于维护跨多个编辑器和IDE从事同一项目的多个开发人员的一致编码风格。</li><li><a href="https://eslint.org/">ESLint</a>：查找并修复JavaScript代码中的问题</li><li><a href="https://prettier.io/">Prettier</a>：更漂亮、固执的代码格式化</li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/c26ea6288229/</id>
    <link href="https://blog.zisheng.pro/posts/c26ea6288229/"/>
    <published>2020-03-24T14:53:11.000Z</published>
    <summary>本文以 React Native 项目为示例介绍了前端规范化开发涉及的工具及知识，但是所涉及内容具有通用性，建议读者点赞收藏(^▽^)。</summary>
    <title>前端规范化开发实践</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React-Native/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/tags/React-Native/"/>
    <category term="amap" scheme="https://blog.zisheng.pro/tags/amap/"/>
    <category term="高德地图" scheme="https://blog.zisheng.pro/tags/%E9%AB%98%E5%BE%B7%E5%9C%B0%E5%9B%BE/"/>
    <category term="定位" scheme="https://blog.zisheng.pro/tags/%E5%AE%9A%E4%BD%8D/"/>
    <category term="react-native-amap-geolocation" scheme="https://blog.zisheng.pro/tags/react-native-amap-geolocation/"/>
    <category term="react-native" scheme="https://blog.zisheng.pro/tags/react-native/"/>
    <content>
      <![CDATA[<p>react-native-amap-geolocation 这款插件接入了高德地图定位模块。支持 Android + iOS，提供尽可能完善的原生接口， 同时提供符合 Web 标准的 Geolocation API。</p><blockquote><p>本文基于 react-native 0.60 以上版本！</p></blockquote><h2 id="获取高德-App-Key"><a href="#获取高德-App-Key" class="headerlink" title="获取高德 App Key"></a>获取高德 App Key</h2><p>为了使用高德 SDK，你需要准备高德 App Key，获取方法参考高德地图 SDK 官方文档：</p><ul><li><p><a href="https://lbs.amap.com/api/android-location-sdk/guide/create-project/get-key">获取 Android App Key</a></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;an:key-debug&quot;</span><span class="punctuation">:</span> <span class="string">&quot;keytool -list -v -keystore ./android/app/debug.keystore&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;an:key-release&quot;</span><span class="punctuation">:</span> <span class="string">&quot;keytool -v -list -keystore ./android/app/release.keystore&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></li><li><p><a href="https://lbs.amap.com/api/ios-location-sdk/guide/create-project/get-key">获取 iOS App Key</a></p><blockquote><p>ios 只需要 <code>Bundle Identifier</code> 即可</p></blockquote></li></ul><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add react-native-amap-geolocation</span><br></pre></td></tr></table></figure><h2 id="ios-配置"><a href="#ios-配置" class="headerlink" title="ios 配置"></a>ios 配置</h2><h3 id="下载和安装高德IOS-SDK"><a href="#下载和安装高德IOS-SDK" class="headerlink" title="下载和安装高德IOS SDK"></a>下载和安装高德IOS SDK</h3><ol><li>下载<a href="https://links.jianshu.com/go?to=https://a.amap.com/lbs/static/zip/AMap_iOS_Foundation_Lib_V1.4.3.zip">基础SDK(含IDFA)</a></li><li>下载<a href="https://links.jianshu.com/go?to=https://a.amap.com/lbs/static/zip/AMap_iOS_Loc_Lib_V2.6.2.zip">定位SDK</a></li><li>将下载的 <code>AMapFoundationKit.framework</code> 和 <code>AMapLocationKit.framework</code> 以及 项目自带的<code>ExternalAccessory.framework</code> 添加到 <code>Build Phases</code> ➜ <code>Link Binary With Libraries</code>。</li></ol><h3 id="配置权限"><a href="#配置权限" class="headerlink" title="配置权限"></a>配置权限</h3><ol><li><p>添加以下权限到 <code>Info.plist</code> 文件中：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">key</span>&gt;</span>NSLocationWhenInUseUsageDescription<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">string</span>&gt;</span><span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">key</span>&gt;</span>NSLocationAlwaysUsageDescription<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">string</span>&gt;</span><span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br></pre></td></tr></table></figure></li><li><p>ATS设置:  <code>Target</code>➜ <code>Info</code> ➜ <code>App Transport Security Setting</code> 展开后的 <code>Allow Arbitrary Loads</code> 设置为 <code>YES</code></p></li></ol><p><strong>如果要支持后台持续定位，还需要配置以下：</strong></p><ol><li><p>打开xcode，点击项目</p></li><li><p>选择 <code>Signing&amp;Capabilities</code>,把 <code>Background Modes</code> 打开为 <code>ON</code>，然后打勾 <code>Location updates</code></p><img src="https://i.loli.net/2020/03/18/ljTXqS9AboycPNk.png" style="zoom: 67%;" /></li></ol><h2 id="Android"><a href="#Android" class="headerlink" title="Android"></a>Android</h2><p>react-native 为 0.6x 的话，<code>yarn add react-native-amap-geolocation</code> 就完事了，一步到位不需要过多配置。</p><h2 id="基本用法"><a href="#基本用法" class="headerlink" title="基本用法"></a>基本用法</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">PermissionsAndroid</span> &#125; <span class="keyword">from</span> <span class="string">&quot;react-native&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; init, <span class="title class_">Geolocation</span> &#125; <span class="keyword">from</span> <span class="string">&quot;react-native-amap-geolocation&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 对于 Android 需要自行根据需要申请权限</span></span><br><span class="line"><span class="keyword">await</span> <span class="title class_">PermissionsAndroid</span>.<span class="title function_">request</span>(<span class="title class_">PermissionsAndroid</span>.<span class="property">PERMISSIONS</span>.<span class="property">ACCESS_COARSE_LOCATION</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用自己申请的高德 App Key 进行初始化</span></span><br><span class="line"><span class="keyword">await</span> <span class="title function_">init</span>(&#123;</span><br><span class="line">  <span class="attr">ios</span>: <span class="string">&quot;9bd6c82e77583020a73ef1af59d0c759&quot;</span>,</span><br><span class="line">  <span class="attr">android</span>: <span class="string">&quot;043b24fe18785f33c491705ffe5b6935&quot;</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="title class_">Geolocation</span>.<span class="title function_">getCurrentPosition</span>(<span class="function">(<span class="params">&#123; coords &#125;</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(coords);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="更多用法"><a href="#更多用法" class="headerlink" title="更多用法"></a>更多用法</h2><p>该项目除了提供符合 Web 标准的 Geolocation API，同时为了最大程度的发挥高德定位 SDK 的功能， 会尽可能提供与原生 SDK 一致的接口封装。由于 iOS 和 Android SDK 提供的接口并不一致， 于是最终实现的接口大部分是并不通用的。这在接口文档或文档注释有注明， 比如 <code>@platform android</code> 表示该接口仅用于 Android。</p><p>以下是一些常用接口的用法说明以及示例代码，更多接口的具体用法请参考<a href="https://qiuxiang.github.io/react-native-amap-geolocation/#/">接口文档</a>。</p><h2 id="直接使用原生接口"><a href="#直接使用原生接口" class="headerlink" title="直接使用原生接口"></a>直接使用原生接口</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; init, addLocationListener, start, stop &#125; <span class="keyword">from</span> <span class="string">&quot;react-native-amap-geolocation&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 添加定位监听函数</span></span><br><span class="line"><span class="title function_">addLocationListener</span>(<span class="function"><span class="params">location</span> =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(location));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 开始连续定位</span></span><br><span class="line"><span class="title function_">start</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在不需要的时候停止定位</span></span><br><span class="line"><span class="title function_">stop</span>();</span><br></pre></td></tr></table></figure><h2 id="逆地理编码"><a href="#逆地理编码" class="headerlink" title="逆地理编码"></a>逆地理编码</h2><p>Android 默认返回逆地理编码，而 iOS 需要手动设置。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; setLocatingWithReGeocode, setNeedAddress &#125; <span class="keyword">from</span> <span class="string">&quot;react-native-amap-geolocation&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// android</span></span><br><span class="line"><span class="title function_">setNeedAddress</span>(<span class="literal">true</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// ios</span></span><br><span class="line"><span class="title function_">setLocatingWithReGeocode</span>(<span class="literal">true</span>);</span><br></pre></td></tr></table></figure><h2 id="定位回调频率限制"><a href="#定位回调频率限制" class="headerlink" title="定位回调频率限制"></a>定位回调频率限制</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="built_in">setInterval</span>, setDistanceFilter &#125; <span class="keyword">from</span> <span class="string">&quot;react-native-amap-geolocation&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// android，5 秒请求一次定位</span></span><br><span class="line"><span class="built_in">setInterval</span>(<span class="number">5000</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// ios，设备移动超过 10 米才会更新位置信息</span></span><br><span class="line"><span class="title function_">setDistanceFilter</span>(<span class="number">10</span>);</span><br></pre></td></tr></table></figure><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://lbs.amap.com/api">高德地图API</a></li><li><a href="https://www.jianshu.com/p/f87316635ff6">react-native-amap-geolocation安装与使用</a></li><li><a href="https://qiuxiang.github.io/react-native-amap-geolocation/">react-native-amap-geolocation 使用指南</a></li><li>[<a href="https://qiuxiang.github.io/react-native-amap-geolocation/api/index.html">react-native-amap-geolocation 接口文档</a></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/6ab9356b577c/</id>
    <link href="https://blog.zisheng.pro/posts/6ab9356b577c/"/>
    <published>2020-03-17T08:05:44.000Z</published>
    <summary>react-native-amap-geolocation 这款插件接入了高德地图定位模块。支持 Android + iOS，提供尽可能完善的原生接口， 同时提供符合 Web 标准的 Geolocation API。</summary>
    <title>使用 react-native-amap-geolocation 接入高德地图定位</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="漫谈" scheme="https://blog.zisheng.pro/categories/%E6%BC%AB%E8%B0%88/"/>
    <category term="程序员" scheme="https://blog.zisheng.pro/tags/%E7%A8%8B%E5%BA%8F%E5%91%98/"/>
    <category term="Mac" scheme="https://blog.zisheng.pro/tags/Mac/"/>
    <category term="开发环境" scheme="https://blog.zisheng.pro/tags/%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83/"/>
    <category term="Mac 环境" scheme="https://blog.zisheng.pro/tags/Mac-%E7%8E%AF%E5%A2%83/"/>
    <category term="程序员 Mac 环境" scheme="https://blog.zisheng.pro/tags/%E7%A8%8B%E5%BA%8F%E5%91%98-Mac-%E7%8E%AF%E5%A2%83/"/>
    <content>
      <![CDATA[<h2 id="Xcode-Command-Line-Tools"><a href="#Xcode-Command-Line-Tools" class="headerlink" title="Xcode Command Line Tools"></a>Xcode Command Line Tools</h2><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ xcode-select --install</span><br></pre></td></tr></table></figure><h2 id="Homebrew"><a href="#Homebrew" class="headerlink" title="Homebrew"></a>Homebrew</h2><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/bin/zsh -c <span class="string">&quot;<span class="subst">$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)</span>&quot;</span></span><br></pre></td></tr></table></figure><h2 id="Git"><a href="#Git" class="headerlink" title="Git"></a>Git</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1、初始化设置</span></span><br><span class="line">git config --global user.name <span class="string">&#x27;紫升&#x27;</span></span><br><span class="line">git config --global user.email <span class="string">&#x27;luozhu2021@gmail.com&#x27;</span></span><br><span class="line"><span class="comment"># 2、将 `color.ui` 设置为 `auto` 可以让命令的输出拥有更高的可读性。</span></span><br><span class="line">git config --global color.ui auto</span><br><span class="line"><span class="comment"># 3、忽略大小写</span></span><br><span class="line">git config --global core.ignorecase <span class="literal">false</span></span><br><span class="line"><span class="comment"># 4、core.autocrlf</span></span><br><span class="line">git config --global core.autocrlf input</span><br><span class="line"><span class="comment"># 5、输出到terminal而不是vim</span></span><br><span class="line">git config --global pager.branch <span class="literal">false</span></span><br></pre></td></tr></table></figure><h2 id="Node"><a href="#Node" class="headerlink" title="Node"></a>Node</h2><p>Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时。我们使用 <code>n</code> 模块来维护 Node 的版本：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ brew install n</span><br></pre></td></tr></table></figure><h3 id="安装指定版本"><a href="#安装指定版本" class="headerlink" title="安装指定版本"></a>安装指定版本</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 安装最新的稳定版</span></span><br><span class="line">$ sudo n lts</span><br><span class="line"><span class="comment"># 安装最新版</span></span><br><span class="line">$ sudo n latest</span><br><span class="line"><span class="comment"># 安装指定版本</span></span><br><span class="line">$ sudo n 10.16.0</span><br></pre></td></tr></table></figure><h3 id="删除命令"><a href="#删除命令" class="headerlink" title="删除命令"></a>删除命令</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 删除指定版本</span></span><br><span class="line">$ n <span class="built_in">rm</span> 12.10.0</span><br><span class="line"><span class="comment"># 除去当前版本以外的所有缓存版本</span></span><br><span class="line">$ n prune</span><br></pre></td></tr></table></figure><h3 id="安装-yarn-和-pnpm"><a href="#安装-yarn-和-pnpm" class="headerlink" title="安装 yarn 和 pnpm"></a>安装 yarn 和 pnpm</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ brew install yarn pnpm -g</span><br></pre></td></tr></table></figure><h3 id="更新-npm"><a href="#更新-npm" class="headerlink" title="更新 npm"></a>更新 npm</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">sudo npm install -g npm</span></span><br></pre></td></tr></table></figure><h3 id="nrm-管理-registry"><a href="#nrm-管理-registry" class="headerlink" title="nrm 管理 registry"></a>nrm 管理 registry</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">sudo npm install -g nrm</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">列出可用的代理</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">nrm <span class="built_in">ls</span></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">添加私有代理</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">nrm add &lt;name&gt; &lt;url&gt; [home]</span></span><br></pre></td></tr></table></figure><h3 id="安装-npkill"><a href="#安装-npkill" class="headerlink" title="安装 npkill"></a>安装 npkill</h3><p>轻松查找并删除旧的和沉重的 <code>node_modules</code> 文件夹</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ npm install -g npkill</span><br><span class="line"><span class="comment"># 在要搜索的根目录执行，比如 cd Desktop</span></span><br><span class="line">$ npkill</span><br></pre></td></tr></table></figure><h3 id="清理所有-node-进程"><a href="#清理所有-node-进程" class="headerlink" title="清理所有 node 进程"></a>清理所有 node 进程</h3><p>node 进程异常退出不会杀死 node 进程，久而久之 CPU 就负担不了了，这个时候 pkill 命令就可以代替活动监视器。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ pkill node</span><br><span class="line">$ pkill esbuild</span><br></pre></td></tr></table></figure><h3 id="权限不足"><a href="#权限不足" class="headerlink" title="权限不足"></a>权限不足</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ sudo <span class="built_in">chmod</span> -R 777 /usr/local/lib/node_modules</span><br><span class="line">$ sudo <span class="built_in">chmod</span> -R 777 /usr/local/bin/</span><br><span class="line">$ sudo <span class="built_in">chmod</span> -R 777 ~/.npm</span><br></pre></td></tr></table></figure><h2 id="VS-Code"><a href="#VS-Code" class="headerlink" title="VS Code"></a>VS Code</h2><ul><li>下载 <a href="https://code.visualstudio.com/">Visual Studio Code</a></li><li>安装 <a href="https://marketplace.visualstudio.com/items?itemName=youngjuning.vscode-luozhu-pack">vscode-luozhu-pack</a> 扩展包插件</li></ul><h2 id="iTerm2"><a href="#iTerm2" class="headerlink" title="iTerm2"></a>iTerm2</h2><p>点击 <a href="https://iterm2.com/downloads/stable/latest">https://iterm2.com/downloads/stable/latest</a> 下载最新版 iTerm2。</p><h2 id="ohmyzsh"><a href="#ohmyzsh" class="headerlink" title="ohmyzsh"></a>ohmyzsh</h2><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 切换 shell 为 zsh</span></span><br><span class="line">$ chsh -s /bin/zsh</span><br><span class="line"><span class="comment"># 通过 curl 安装 ohmyzsh</span></span><br><span class="line">$ sh -c <span class="string">&quot;<span class="subst">$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)</span>&quot;</span></span><br></pre></td></tr></table></figure><h3 id="插件"><a href="#插件" class="headerlink" title="插件"></a>插件</h3><ul><li><a href="https://github.com/zsh-users/zsh-autosuggestions/blob/master/INSTALL.md#oh-my-zsh">zsh-autosuggestions</a></li><li><a href="https://github.com/zsh-users/zsh-syntax-highlighting/blob/master/INSTALL.md">zsh-syntax-highlighting</a></li></ul><h3 id="主题"><a href="#主题" class="headerlink" title="主题"></a>主题</h3><p>ohmyzsh 主题我只用 ys。</p><h2 id="SwitchHosts"><a href="#SwitchHosts" class="headerlink" title="SwitchHosts"></a>SwitchHosts</h2><p>快速切换 hosts，点击 <a href="https://github.com/oldj/SwitchHosts/releases">https://github.com/oldj/SwitchHosts/releases</a> 去下载。</p><h2 id="Docker"><a href="#Docker" class="headerlink" title="Docker"></a>Docker</h2><blockquote><p>参考 <a href="https://zhuanlan.zhihu.com/p/1899756565264827657">Mac安装Docker（Orbstack）并使用Portainer管理</a></p></blockquote><ul><li><a href="https://orbstack.dev/download">Orbstack</a>：Docker Desktop 替代方案，实现光速开发。</li><li><a href="https://www.portainer.cn/">Portainer</a>：Docker 可视化管理面板</li></ul><h2 id="提效工具"><a href="#提效工具" class="headerlink" title="提效工具"></a>提效工具</h2><ul><li><a href="https://www.alfredapp.com/">Alfred</a>：Mac 上最强大的效率工具</li><li><a href="https://monica.im/">Monica</a>：人工智能助手</li><li><a href="https://getkap.co/">Kap</a>：Mac 上轻量 GIF 录屏小工具</li><li><a href="https://zh.snipaste.com/">Snipaste</a>：截图+贴图</li><li><a href="https://github.com/siddharthvaddem/openscreen">openscreen</a>：Screen Studio 的理想替代方案。</li><li><a href="https://www.u-tools.cn/index.html">uTools</a>：AI 时代的轻工具平台</li></ul><h2 id="v8"><a href="#v8" class="headerlink" title="v8"></a>v8</h2><blockquote><p>不要使用 <code>brew install v8</code>，因为可用命令是不完整的。</p></blockquote><h3 id="预备条件"><a href="#预备条件" class="headerlink" title="预备条件"></a>预备条件</h3><ul><li>Install Xcode (Avaliable on the Mac App Store)</li><li>Install Xcode Command Line Tools (Preferences &gt; Downloads)</li><li>Install <a href="https://www.chromium.org/developers/how-tos/install-depot-tools">depot_tools</a><ul><li><code>cd ~ &amp;&amp; git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git</code></li><li><code>sudo nano ~/.zshrc</code>(zsh) or <code>sudo nano ~/.bash_profile</code>(bash)</li><li>Add <code>export PATH=~/depot_tools:&quot;$PATH&quot;</code> (it’s important that depot_tools comes first here)</li><li><code>source ~/.zshrc</code></li><li>From the directory you want to install V8 into, run <code>gclient</code></li></ul></li></ul><h3 id="Build-V8"><a href="#Build-V8" class="headerlink" title="Build V8"></a>Build V8</h3><ul><li><code>fetch v8</code></li><li><code>cd ~/v8</code></li><li><code>gclient sync</code></li><li><code>tools/dev/v8gen.py x64.optdebug</code></li><li><code>ninja -C out.gn/x64.optdebug</code> (prepare for lots of fan noise)</li></ul><p>I’d also recommend adding these to your <code>.zshrc</code>:</p><ul><li><code>sudo nano ~/.zshrc</code></li><li>Add <code>alias d8=~/v8/out.gn/x64.optdebug/d8</code></li><li>Add <code>alias tick-processor=~/v8/tools/mac-tick-processor</code></li><li>Add <code>export D8_PATH=&quot;~/v8/out.gn/x64.optdebug&quot;</code></li><li><code>source ~/.zshrc</code></li></ul><blockquote><p>使用 Demo 请参考 <a href="https://gist.github.com/kevincennis/0cd2138c78a07412ef21#d8-shell-examples">d8-shell-examples</a></p></blockquote><h2 id="MySql"><a href="#MySql" class="headerlink" title="MySql"></a>MySql</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">brew install mysql</span></span><br></pre></td></tr></table></figure><h3 id="修改配置文件"><a href="#修改配置文件" class="headerlink" title="修改配置文件"></a>修改配置文件</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">code /usr/local/etc/my.cnf</span></span><br></pre></td></tr></table></figure><h3 id="启动服务"><a href="#启动服务" class="headerlink" title="启动服务"></a>启动服务</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">brew services start mysql</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">重启</span></span><br><span class="line">brew services restart mysql</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">停止</span></span><br><span class="line">brew services stop mysql</span><br></pre></td></tr></table></figure><blockquote><p>Or, if you don’t want&#x2F;need a background service you can just run: <code>mysql.server start</code></p></blockquote><h3 id="安全设置"><a href="#安全设置" class="headerlink" title="安全设置"></a>安全设置</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">mysql_secure_installation</span></span><br></pre></td></tr></table></figure><ol><li>建立密码验证插件</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">Securing the MySQL server deployment.</span><br><span class="line"></span><br><span class="line">Connecting to MySQL using a blank password.</span><br><span class="line"></span><br><span class="line">VALIDATE PASSWORD PLUGIN can be used to test passwords and improve security. It checks the strength of password and allows the users to set only those passwords which are secure enough. Would you like to setup VALIDATE PASSWORD plugin?</span><br><span class="line"></span><br><span class="line">Press y|Y for Yes, any other key for No: y</span><br></pre></td></tr></table></figure><ol start="2"><li>选择密码规则</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">There are three levels of password validation policy:</span><br><span class="line"></span><br><span class="line">LOW    Length &gt;= 8</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">长度大于等于8</span></span><br><span class="line">MEDIUM Length &gt;= 8, numeric, mixed case, and special characters</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">长度大于等于8，数字、大小写字母、特殊符号</span></span><br><span class="line">STRONG Length &gt;= 8, numeric, mixed case, special characters and dictionary file</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">长度大于等于8，数字、大小写字母、特殊符号和字典文件（慎选！）</span></span><br><span class="line"></span><br><span class="line">Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: 1</span><br><span class="line">Please set the password for root here.</span><br><span class="line"></span><br><span class="line">New password: （输入你的密码）</span><br><span class="line">Re-enter new password: （再次输入你的密码）</span><br></pre></td></tr></table></figure><ol start="3"><li>创建符合规则的新密码</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Estimated strength of the password: 50 #密码强度</span><br><span class="line">Do you wish to continue with the password provided?(Press y|Y for Yes, any other key for No) : y</span><br></pre></td></tr></table></figure><ol start="4"><li>删除匿名用户</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">By default, a MySQL installation has an anonymous user, allowing anyone to log into MySQL without having to have a user account created for them. This is intended only for testing, and to make the installation go a bit smoother.</span><br><span class="line">You should remove them before moving into a production environment.</span><br><span class="line"></span><br><span class="line">Remove anonymous users? (Press y|Y for Yes, any other key for No) : y</span><br><span class="line">Success.</span><br></pre></td></tr></table></figure><ol start="5"><li>禁止远程登录</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Normally, root should only be allowed to connect from &#x27;localhost&#x27;. This ensures that someone cannot guess at the root password from the network.</span><br><span class="line"></span><br><span class="line">Disallow root login remotely? (Press y|Y for Yes, any other key for No) : y</span><br><span class="line">Success.</span><br></pre></td></tr></table></figure><ol start="6"><li>删除测试数据表</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">By default, MySQL comes with a database named &#x27;test&#x27; that anyone can access. This is also intended only for testing, and should be removed before moving into a production environment.</span><br><span class="line"></span><br><span class="line">Remove test database and access to it? (Press y|Y for Yes, any other key for No) : y</span><br><span class="line">- Dropping test database...</span><br><span class="line">Success.</span><br><span class="line"></span><br><span class="line">- Removing privileges on test database...</span><br><span class="line">Success.</span><br></pre></td></tr></table></figure><ol start="7"><li>Done</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">Reloading the privilege tables will ensure that all changes made so far will take effect immediately.</span><br><span class="line"></span><br><span class="line">Reload privilege tables now? (Press y|Y for Yes, any other key for No) : y</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">是否重新加载权限表</span></span><br><span class="line">Success.</span><br><span class="line"></span><br><span class="line">All done!</span><br></pre></td></tr></table></figure><p><strong>Your password does not satisfy the current policy requirements：</strong></p><p>如果你在选择密码规则的时候不小心选择了 2，也就是数字、大小写字母、特殊符号和字典文件的组合。这时你会发现 <code>mysql_secure_installation</code>不会再给你机会重新设置了。手动微笑，mmp。方法还是有的：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SHOW VARIABLES LIKE &#x27;validate_password%&#x27;;</span><br></pre></td></tr></table></figure><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/68541c4633b944d69276a3da8f453406~tplv-k3u1fbpfcp-zoom-1.image" alt="紫升"></p><p>使用命令 <code>mysql -u root</code> 登陆，执行：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">set global validate_password.policy=0;</span><br><span class="line">set global validate_password.length=4;</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">将密码规则设置为LOW，就可以使用纯数字纯字母密码</span></span><br></pre></td></tr></table></figure><h3 id="登陆"><a href="#登陆" class="headerlink" title="登陆"></a>登陆</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">mysql -u root -p</span></span><br></pre></td></tr></table></figure><h2 id="mongodb"><a href="#mongodb" class="headerlink" title="mongodb"></a>mongodb</h2><blockquote><p>参考: <a href="https://www.runoob.com/mongodb/mongodb-osx-install.html">Mac OSX 平台安装 MongoDB</a>、<a href="https://www.32e.top/system/mac/article-87.html">Mac 下安装 MongoDB 数据库-启动-停止-开启验证-登陆</a>、<a href="https://uizph.com/article/5db177e4a9f13d7f535810c5">【环境搭建：二】Mac 安装、配置 MongoDB</a>、<a href="https://www.jianshu.com/p/f5afc6488f9e">MongoDB 的用户创建更新及删除</a>、<a href="https://cloud.tencent.com/developer/article/1446551">MongoDB 用户名密码登录 认证登陆</a></p></blockquote><h3 id="下载安装"><a href="#下载安装" class="headerlink" title="下载安装"></a>下载安装</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ brew install mongodb/brew/mongodb-community</span><br><span class="line">$ mongod -version</span><br></pre></td></tr></table></figure><h3 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h3><p><strong>启动 mongo</strong>：</p><ol><li>新建 dbpath</li></ol><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ sudo <span class="built_in">mkdir</span> ~/data/db</span><br><span class="line">$ sudo <span class="built_in">mkdir</span> ~/data/log</span><br></pre></td></tr></table></figure><ol start="2"><li>启动</li></ol><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sudo mongod --dbpath ~/data/db --fork --logpath ~/data/log/mongo.log</span><br></pre></td></tr></table></figure><blockquote><p>注意：Mac OS 10.15.1 版本之后， <code>/data/db</code> 文件夹消失了，重新创建文件夹提示 <code>mkdir: /data/db: Read-only file system</code>，解决办法也可以是 <code>sudo mkdir ~/data/db &amp;&amp; sudo mongodb --dbpath ~/data/db</code></p></blockquote><p><strong>设置验证和用户名密码</strong>：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">$ mongo</span><br><span class="line"><span class="comment"># 创建超级管理员</span></span><br><span class="line">&gt; db.createUser(&#123; user: <span class="string">&quot;root&quot;</span> , <span class="built_in">pwd</span>: <span class="string">&quot;123456&quot;</span>, roles: [<span class="string">&quot;root&quot;</span>]&#125;);</span><br><span class="line">Successfully added user: &#123;</span><br><span class="line">   <span class="string">&quot;user&quot;</span> : <span class="string">&quot;root&quot;</span>,</span><br><span class="line">   <span class="string">&quot;roles&quot;</span> : [<span class="string">&quot;root&quot;</span>]</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment"># 尝试使用上面创建的用户信息进行连接。</span></span><br><span class="line">&gt; db.auth(<span class="string">&quot;root&quot;</span>,<span class="string">&quot;123456&quot;</span>)</span><br><span class="line">1</span><br><span class="line"><span class="comment"># 创建一个名为 admin，密码为 123456 的用户。</span></span><br><span class="line">&gt; db.createUser(&#123; user: <span class="string">&quot;admin&quot;</span>, <span class="built_in">pwd</span>: <span class="string">&quot;123456&quot;</span>, roles:[<span class="string">&quot;userAdminAnyDatabase&quot;</span>, <span class="string">&quot;dbAdminAnyDatabase&quot;</span>, <span class="string">&quot;readWriteAnyDatabase&quot;</span>]&#125;);</span><br><span class="line">Successfully added user: &#123;</span><br><span class="line">   <span class="string">&quot;user&quot;</span>: <span class="string">&quot;admin&quot;</span>,</span><br><span class="line">   <span class="string">&quot;roles&quot;</span>: [</span><br><span class="line">   &#123;</span><br><span class="line">      <span class="string">&quot;role&quot;</span>: <span class="string">&quot;userAdminAnyDatabase&quot;</span>,</span><br><span class="line">      <span class="string">&quot;db&quot;</span>: <span class="string">&quot;admin&quot;</span></span><br><span class="line">   &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment"># 尝试使用上面创建的用户信息进行连接。</span></span><br><span class="line">&gt; db.auth(<span class="string">&quot;admin&quot;</span>,<span class="string">&quot;123456&quot;</span>)</span><br><span class="line">1</span><br></pre></td></tr></table></figure><p><strong>开启验证模式登录</strong>：</p><p>开启 mongod 时，指定 <code>--auth</code> 参数即可以验证模式打开：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sudo mongod --dbpath ~/data/db --fork --logpath ~/data/log/mongo.log --auth</span><br></pre></td></tr></table></figure><p><strong>登录时验证</strong>：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">mongo 127.0.0.1:27017/admin -u admin -p 123456</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">等价于</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">mongo --port 27017 -u <span class="string">&quot;adminUser&quot;</span> -p <span class="string">&quot;adminPass&quot;</span> --authenticationDatabase <span class="string">&quot;admin&quot;</span></span></span><br></pre></td></tr></table></figure><p><strong>登录后验证</strong>：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">mongo</span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">use admin</span></span><br><span class="line"><span class="meta prompt_">&gt; </span><span class="language-bash">ab.auth(<span class="string">&quot;admin&quot;</span>,<span class="string">&quot;123456&quot;</span>)</span></span><br></pre></td></tr></table></figure><h3 id="退出-mongodb"><a href="#退出-mongodb" class="headerlink" title="退出 mongodb"></a>退出 mongodb</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 先停止 mongod 服务</span></span><br><span class="line">$ use admin;</span><br><span class="line">$ db.shutdownServer();</span><br><span class="line"></span><br><span class="line"><span class="comment"># 然后退出 mongo</span></span><br><span class="line">$ <span class="built_in">exit</span>;</span><br></pre></td></tr></table></figure><h2 id="更多"><a href="#更多" class="headerlink" title="更多"></a>更多</h2><h3 id="natapp"><a href="#natapp" class="headerlink" title="natapp"></a>natapp</h3><p>开启您的内网穿透之旅,调试微信的利器，请至<a href="https://natapp.cn/">官网</a>下载。</p><h3 id="设置-SSH-Key"><a href="#设置-SSH-Key" class="headerlink" title="设置 SSH Key"></a>设置 SSH Key</h3><p>在用户主目录下，看看有没有 <code>.ssh</code> 目录，如果有，再看看这个目录下有没有 <code>id_rsa</code> 和 <code>id_rsa.pub</code> 这两个文件，如果已经有了，可直接跳到下一步。如果没有，打开 Shell（Windows 下打开 Git Bash），创建 SSH Key：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">$ ssh-keygen -t rsa -C <span class="string">&quot;luozhu2021@gmail.com&quot;</span></span><br><span class="line"><span class="comment"># 添加到系统 keychain</span></span><br><span class="line">ssh-add --apple-use-keychain ~/.ssh/id_rsa</span><br><span class="line"><span class="comment"># 添加 public key 到 github</span></span><br><span class="line">gh auth login</span><br><span class="line">gh ssh-key add ~/.ssh/id_rsa.pub -t id_rsa</span><br></pre></td></tr></table></figure><h3 id="vim"><a href="#vim" class="headerlink" title="vim"></a>vim</h3><blockquote><p>建议使用：<a href="https://github.com/amix/vimrc">https://github.com/amix/vimrc</a></p></blockquote><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git <span class="built_in">clone</span> --depth=1 https://github.com/amix/vimrc.git ~/.vim_runtime</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">sh ~/.vim_runtime/install_awesome_vimrc.sh</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">echo</span> <span class="string">&quot;set number&quot;</span> &gt;&gt; ~/.vimrc</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">echo</span> <span class="string">&quot;set showcmd&quot;</span> &gt;&gt; ~/.vimrc</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">source</span> ~/.vimrc</span></span><br></pre></td></tr></table></figure><blockquote><p>本文首发于「<a href="https://blog.zisheng.pro/">紫升的博客</a>」，同步于「<a href="https://juejin.cn/user/325111174662855">掘金专栏</a>」。</p></blockquote>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/da1fab34e822/</id>
    <link href="https://blog.zisheng.pro/posts/da1fab34e822/"/>
    <published>2020-03-04T17:06:42.000Z</published>
    <summary>本文介绍了程序员常用的 Mac 环境配置，作为记录分享给大家。</summary>
    <title>程序员的 Mac 开发环境</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React/"/>
    <category term="antd" scheme="https://blog.zisheng.pro/tags/antd/"/>
    <category term="UI 框架" scheme="https://blog.zisheng.pro/tags/UI-%E6%A1%86%E6%9E%B6/"/>
    <category term="Ant Design" scheme="https://blog.zisheng.pro/tags/Ant-Design/"/>
    <category term="babel-plugin-import" scheme="https://blog.zisheng.pro/tags/babel-plugin-import/"/>
    <category term="babel" scheme="https://blog.zisheng.pro/tags/babel/"/>
    <content>
      <![CDATA[<p><code>antd</code> 是基于 Ant Design 设计体系的 React UI 组件库，主要用于研发企业级中后台产品。</p><blockquote><p>文章可参考源码：<a href="https://github.com/youngjuning/antd-with-ts-demo">antd-with-ts-demo</a></p></blockquote><h2 id="Ant-Design-设计体系"><a href="#Ant-Design-设计体系" class="headerlink" title="Ant Design 设计体系"></a>Ant Design 设计体系</h2><p>以笔者的经验来看，Ant Design 设计体系下的产品设计理念、使用方式、底层技术、周边工具都保持着高度一致，工具不是越多越好，有一套好用顺手的就行，UI 框架千千万，你不可能都学一遍。Ant Design 无疑能够减少你的学习成本。</p><ul><li>设计<ul><li>设计价值观</li><li>全局样式</li><li>设计模式</li></ul></li><li>组件库<ul><li><a href="https://ant.design/docs/react/introduce-cn">Ant Design of React</a>: 基于 Ant Design 设计体系的 React UI 组件库，主要用于研发企业级中后台产品。</li><li><a href="https://mobile.ant.design/docs/react/introduce-cn">Ant Design Mobile of React</a>: <code>antd-mobile</code> 是 Ant Design 的移动规范的 React 实现，服务于蚂蚁及口碑无线业务。</li><li><a href="https://rn.mobile.ant.design/docs/react/introduce-cn">Ant Design Mobile RN of React</a>: <code>@ant-design/react-native</code> 是 <a href="http://ant.design/">Ant Design</a> 的移动规范的 React 实现，服务于蚂蚁及口碑无线业务。</li><li><a href="https://ng.ant.design/docs/introduce/zh">Ant Design of Angular</a>: 这里是 Ant Design 的 Angular 实现，开发和服务于企业级后台产品。</li><li><a href="https://ng.mobile.ant.design/#/docs/introduce/zh">Ant Design Mobile of Angular</a>: 这里是 <strong>Ant Design</strong> 移动规范的 <strong>Angular</strong> 实现，服务于阿里巴巴集团数据无线业务。</li><li><a href="https://www.antdv.com/docs/vue/introduce-cn/">Ant Design of Vue</a>: 这里是 Ant Design 的 Vue 实现，开发和服务于企业级后台产品。</li></ul></li><li><a href="https://ant.design/components/icon-cn/">Icons</a>: 一整套优质的图标集</li><li><a href="https://antv.vision/zh">AntV</a>: AntV 是蚂蚁金服全新一代数据可视化解决方案，致力于提供一套简单方便、专业可靠、无限可能的数据可视化最佳实践。</li><li><a href="https://pro.ant.design/index-cn">Ant Design Pro</a>: 开箱即用的中台前端&#x2F;设计解决方案<ul><li><a href="http://dvajs.com/">dva</a>: 一个基于 Redux 的 轻量级数据流方案，概念来自 elm，支持 side effects、热替换、动态加载、react-native、SSR 等，已在生产环境广泛应用。</li><li><a href="http://umijs.org/">umi</a> : 一个可插拔的企业级 react 应用框架。umi 以路由为基础的，支持<a href="https://umijs.org/zh/guide/router.html">类 next.js 的约定式路由</a>，以及各种进阶的路由功能，并以此进行功能扩展，比如<a href="https://umijs.org/zh/plugin/umi-plugin-react.html#dynamicimport">支持路由级的按需加载</a>。然后配以完善的<a href="https://umijs.org/zh/plugin/">插件体系</a>，覆盖从源码到构建产物的每个生命周期，支持各种功能扩展和业务需求，同时提供 <a href="https://umijs.org/zh/guide/umi-ui.html">Umi UI</a> 通过可视化辅助编程（VAP）提高开发体验和研发效率。</li></ul></li></ul><p>从上面的体系中可以看出，Ant Design of React 可以说是整个 Ant Design 设计体系的核心产品，想要学习 Ant Design Pro，首先就要先熟悉 Ant Design of React。</p><h2 id="流行趋势"><a href="#流行趋势" class="headerlink" title="流行趋势"></a>流行趋势</h2><h3 id="npm-下载量"><a href="#npm-下载量" class="headerlink" title="npm 下载量"></a>npm 下载量</h3><p>如果拿 antd 和 element-ui、iview 这些老牌 Vue.js UI 框架对比，遥遥领先啊有没有：</p><p><img src="https://user-gold-cdn.xitu.io/2020/3/4/170a4939e1919973?w=1103&h=458&f=png&s=59582" alt="紫升"></p><p>如果拿 ant-design-vue 来和 element-ui、iview这些老牌 vue UI框架对比，也是很有竞争力的：</p><p><img src="https://user-gold-cdn.xitu.io/2020/3/4/170a4939e21c45c6?w=1115&h=454&f=png&s=76675" alt="紫升"></p><h3 id="GitHub-Star"><a href="#GitHub-Star" class="headerlink" title="GitHub Star"></a>GitHub Star</h3><p><img src="https://user-gold-cdn.xitu.io/2020/3/4/170a4939e23e1d0b?w=1119&h=207&f=png&s=36028" alt="紫升"></p><h2 id="特性"><a href="#特性" class="headerlink" title="特性"></a>特性</h2><ul><li>🌈 提炼自企业级中后台产品的交互语言和视觉风格。</li><li>📦 开箱即用的高质量 React 组件。</li><li>🛡 使用 TypeScript 开发，提供完整的类型定义文件。</li><li>⚙️ 全链路开发和设计工具体系。</li><li>🌍 数十个国际化语言支持。</li><li>🎨 深入每个细节的主题定制能力。</li></ul><h2 id="支持环境"><a href="#支持环境" class="headerlink" title="支持环境"></a>支持环境</h2><ul><li>现代浏览器和 IE11 及以上（需要 <a href="https://ant.design/docs/react/getting-started-cn#%E5%85%BC%E5%AE%B9%E6%80%A7">polyfills</a>）。</li><li>支持服务端渲染。<ul><li><a href="https://nextjs.frontendx.cn/">Next.js</a>: <strong>Next.js</strong> 是一个轻量级的 React 服务端渲染应用框架。</li></ul></li><li><a href="https://electronjs.org/">Electron</a>：使用 JavaScript，HTML 和 CSS 构建跨平台的桌面应用程序<ul><li><a href="https://github.com/electron-react-boilerplate/electron-react-boilerplate">electron-react-boilerplate</a>: 可扩展的跨平台应用程序的基础</li></ul></li></ul><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">yarn add antd</span></span><br></pre></td></tr></table></figure><h2 id="按需加载"><a href="#按需加载" class="headerlink" title="按需加载"></a>按需加载</h2><p>Antd 系列的 UI 组件库都需要引入 <a href="https://github.com/ant-design/babel-plugin-import">babel-plugin-import</a> 库来实现懒加载</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// .babelrc or babel-loader option</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;plugins&quot;</span>: [</span><br><span class="line">    [<span class="string">&quot;import&quot;</span>, &#123;</span><br><span class="line">      <span class="string">&quot;libraryName&quot;</span>: <span class="string">&quot;antd&quot;</span>,</span><br><span class="line">      <span class="string">&quot;libraryDirectory&quot;</span>: <span class="string">&quot;es&quot;</span>,</span><br><span class="line">      <span class="string">&quot;style&quot;</span>: <span class="string">&quot;css&quot;</span> <span class="comment">// `style: true` 会加载 less 文件</span></span><br><span class="line">    &#125;]</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后只需从 antd 引入模块即可，无需单独引入样式。等同于下面手动引入的方式。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// babel-plugin-import 会帮助你加载 JS 和 CSS</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">DatePicker</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;antd&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="快速上手"><a href="#快速上手" class="headerlink" title="快速上手"></a>快速上手</h2><blockquote><p>在开始之前，推荐先学习 <a href="http://reactjs.org/">React</a> 和 <a href="http://babeljs.io/docs/learn-es2015/">ES2015</a>，并正确安装和配置了 <a href="https://nodejs.org/">Node.js</a> v8 或以上。官方指南假设你已了解关于 HTML、CSS 和 JavaScript 的中级知识，并且已经完全掌握了 React 全家桶的正确开发方式。如果你刚开始学习前端或者 React，将 UI 框架作为你的第一步可能不是最好的主意。</p></blockquote><h3 id="1-创建一个-codesanbox"><a href="#1-创建一个-codesanbox" class="headerlink" title="1. 创建一个 codesanbox"></a>1. 创建一个 codesanbox</h3><p>访问 <a href="http://u.ant.design/codesandbox-repro">http://u.ant.design/codesandbox-repro</a> 创建一个 codesandbox 的在线示例，别忘了保存以创建一个新的实例。</p><h3 id="2-使用组件"><a href="#2-使用组件" class="headerlink" title="2. 使用组件"></a>2. 使用组件</h3><p>直接用下面的代码替换 <code>index.js</code> 的内容，用 React 的方式直接使用 antd 组件。</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">ReactDOM</span> <span class="keyword">from</span> <span class="string">&#x27;react-dom&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">ConfigProvider</span>, <span class="title class_">DatePicker</span>, message &#125; <span class="keyword">from</span> <span class="string">&#x27;antd&#x27;</span>;</span><br><span class="line"><span class="comment">// 由于 antd 组件的默认文案是英文，所以需要修改为中文</span></span><br><span class="line"><span class="keyword">import</span> zhCN <span class="keyword">from</span> <span class="string">&#x27;antd/es/locale/zh_CN&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> moment <span class="keyword">from</span> <span class="string">&#x27;moment&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">&#x27;moment/locale/zh-cn&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">&#x27;antd/dist/antd.css&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">&#x27;./index.css&#x27;</span>;</span><br><span class="line"></span><br><span class="line">moment.<span class="title function_">locale</span>(<span class="string">&#x27;zh-cn&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">App</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">date</span>: <span class="literal">null</span>,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  handleChange = <span class="function"><span class="params">date</span> =&gt;</span> &#123;</span><br><span class="line">    message.<span class="title function_">info</span>(<span class="string">`您选择的日期是: <span class="subst">$&#123;date ? date.format(<span class="string">&#x27;YYYY-MM-DD&#x27;</span>) : <span class="string">&#x27;未选择&#x27;</span>&#125;</span>`</span>);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123; date &#125;);</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; date &#125; = <span class="variable language_">this</span>.<span class="property">state</span>;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">ConfigProvider</span> <span class="attr">locale</span>=<span class="string">&#123;zhCN&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">width:</span> <span class="attr">400</span>, <span class="attr">margin:</span> &#x27;<span class="attr">100px</span> <span class="attr">auto</span>&#x27; &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">DatePicker</span> <span class="attr">onChange</span>=<span class="string">&#123;this.handleChange&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">div</span> <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">marginTop:</span> <span class="attr">20</span> &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">            当前日期：&#123;date ? date.format(&#x27;YYYY-MM-DD&#x27;) : &#x27;未选择&#x27;&#125;</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">ConfigProvider</span>&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">ReactDOM</span>.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">App</span> /&gt;</span></span>, <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;root&#x27;</span>));</span><br></pre></td></tr></table></figure><h3 id="3-探索更多组件用法"><a href="#3-探索更多组件用法" class="headerlink" title="3. 探索更多组件用法"></a>3. 探索更多组件用法</h3><p>你可以在左侧菜单查看组件列表，比如 <a href="https://ant.design/components/alert-cn/">Alert</a> 组件，组件文档中提供了各类演示，最下方有组件 API 文档可以查阅。在代码演示部分找到第一个例子，点击右下角的图标展开代码。</p><p>然后依照演示代码的写法，在之前的 codesandbox 里修改 <code>index.js</code>，首先在 <code>import</code> 内引入 Alert 组件：</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="deletion">- import &#123; ConfigProvider, DatePicker, message &#125; from &#x27;antd&#x27;;</span></span><br><span class="line"><span class="addition">+ import &#123; ConfigProvider, DatePicker, message, Alert &#125; from &#x27;antd&#x27;;</span></span><br></pre></td></tr></table></figure><p>然后在 <code>render</code> 内添加相应的 jsx 代码：</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">  &lt;DatePicker onChange=&#123;value =&gt; this.handleChange(value)&#125; /&gt;</span><br><span class="line">  &lt;div style=&#123;&#123; marginTop: 20 &#125;&#125;&gt;</span><br><span class="line"><span class="deletion">-   当前日期：&#123;date ? date.format(&#x27;YYYY-MM-DD&#x27;) : &#x27;未选择&#x27;&#125;</span></span><br><span class="line"><span class="addition">+   &lt;Alert message=&#123;`当前日期：$&#123;date ? date.format(&#x27;YYYY-MM-DD&#x27;) : &#x27;未选择&#x27;&#125;`&#125; type=&quot;success&quot; /&gt;</span></span><br><span class="line">  &lt;/div&gt;</span><br></pre></td></tr></table></figure><p>好的，现在你已经会使用基本的 antd 组件了，你可以在这个例子中继续探索其他组件的用法。如果你遇到组件的 bug，也推荐建一个可重现的 codesandbox 来报告 bug。</p><h3 id="4-下一步"><a href="#4-下一步" class="headerlink" title="4. 下一步#"></a>4. 下一步<a href="https://ant.design/docs/react/getting-started-cn#4.-%E4%B8%8B%E4%B8%80%E6%AD%A5">#</a></h3><p>实际项目开发中，你会需要构建、调试、代理、打包部署等一系列工程化的需求。您可以阅读后面的文档或者使用以下脚手架和范例：</p><ul><li><a href="http://pro.ant.design/">Ant Design Pro</a></li><li><a href="https://github.com/zuiidea/antd-admin">antd-admin</a></li><li><a href="https://github.com/d2-projects/d2-admin">d2-admin</a></li><li>更多脚手架可以查看 <a href="http://scaffold.ant.design/">脚手架市场</a></li></ul><h2 id="使用-Day-js-替换-momentjs-优化打包大小"><a href="#使用-Day-js-替换-momentjs-优化打包大小" class="headerlink" title="使用 Day.js 替换 momentjs 优化打包大小"></a>使用 Day.js 替换 momentjs 优化打包大小</h2><p>你可以使用 <a href="https://github.com/ant-design/antd-dayjs-webpack-plugin">antd-dayjs-webpack-plugin</a> 插件用 Day.js 替换 momentjs 来大幅减小打包大小。这需要更新 webpack 的配置文件如下：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack-config.js</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">AntdDayjsWebpackPlugin</span> <span class="keyword">from</span> <span class="string">&#x27;antd-dayjs-webpack-plugin&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  <span class="attr">plugins</span>: [<span class="keyword">new</span> <span class="title class_">AntdDayjsWebpackPlugin</span>()],</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="在-TypeScript-中使用"><a href="#在-TypeScript-中使用" class="headerlink" title="在 TypeScript 中使用"></a>在 TypeScript 中使用</h2><p>使用 <code>create-react-app</code> 一步步地创建一个 TypeScript 项目，并引入 antd。</p><h3 id="安装和初始化"><a href="#安装和初始化" class="headerlink" title="安装和初始化"></a>安装和初始化</h3><p>创建 <a href="https://github.com/facebook/create-react-app/tree/master/packages/cra-template-typescript">cra-template-typescript</a> 项目：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">npx create-react-app my-app --template typescript</span></span><br></pre></td></tr></table></figure><p>然后我们进入项目并启动。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">cd</span> antd-demo-ts</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">yarn start</span></span><br></pre></td></tr></table></figure><p>此时浏览器会访问 <a href="http://localhost:3000/">http://localhost:3000/</a> ，看到 <code>Welcome to React</code> 的界面就算成功了。</p><h3 id="引入-antd"><a href="#引入-antd" class="headerlink" title="引入 antd"></a>引入 antd</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">yarn add antd</span></span><br></pre></td></tr></table></figure><h3 id="自定义-create-react-app-配置"><a href="#自定义-create-react-app-配置" class="headerlink" title="自定义 create-react-app 配置"></a>自定义 create-react-app 配置</h3><p>我们需要对 create-react-app 的默认配置进行自定义，这里我们使用 <a href="https://github.com/timarney/react-app-rewired">react-app-rewired</a> （一个对 create-react-app 进行自定义配置的社区解决方案）。</p><p>引入 react-app-rewired 并修改 package.json 里的启动配置。由于新的 <a href="https://github.com/timarney/react-app-rewired#alternatives">react-app-rewired@2.x</a> 版本的关系，你还需要安装 <a href="https://github.com/arackaf/customize-cra">customize-cra</a>。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">yarn add react-app-rewired customize-cra -D</span></span><br></pre></td></tr></table></figure><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">/* package.json */</span><br><span class="line">&quot;scripts&quot;: &#123;</span><br><span class="line"><span class="deletion">-   &quot;start&quot;: &quot;react-scripts start&quot;,</span></span><br><span class="line"><span class="addition">+   &quot;start&quot;: &quot;react-app-rewired start&quot;,</span></span><br><span class="line"><span class="deletion">-   &quot;build&quot;: &quot;react-scripts build&quot;,</span></span><br><span class="line"><span class="addition">+   &quot;build&quot;: &quot;react-app-rewired build&quot;,</span></span><br><span class="line"><span class="deletion">-   &quot;test&quot;: &quot;react-scripts test&quot;,</span></span><br><span class="line"><span class="addition">+   &quot;test&quot;: &quot;react-app-rewired test&quot;,</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在项目根目录创建一个 <code>config-overrides.js</code> 用于修改默认配置:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = <span class="keyword">function</span> <span class="title function_">override</span>(<span class="params">config, env</span>) &#123;</span><br><span class="line">  <span class="comment">// do stuff with the webpack config...</span></span><br><span class="line">  <span class="keyword">return</span> config;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="使用-babel-plugin-import"><a href="#使用-babel-plugin-import" class="headerlink" title="使用 babel-plugin-import"></a>使用 babel-plugin-import</h3><p><a href="https://github.com/ant-design/babel-plugin-import">babel-plugin-import</a> 是一个用于按需加载组件代码和样式的 babel 插件（<a href="https://ant.design/docs/react/getting-started-cn#%E6%8C%89%E9%9C%80%E5%8A%A0%E8%BD%BD">原理</a>），现在我们尝试安装它并修改 <code>config-overrides.js</code> 文件。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add babel-plugin-import -D</span><br></pre></td></tr></table></figure><p>替换 <code>config-overrides.js</code> 文件内容：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; override, fixBabelImports &#125; = <span class="built_in">require</span>(<span class="string">&#x27;customize-cra&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = <span class="title function_">override</span>(</span><br><span class="line">  <span class="title function_">fixBabelImports</span>(<span class="string">&#x27;import&#x27;</span>, &#123;</span><br><span class="line">    <span class="attr">libraryName</span>: <span class="string">&#x27;antd&#x27;</span>,</span><br><span class="line">    <span class="attr">libraryDirectory</span>: <span class="string">&#x27;es&#x27;</span>,</span><br><span class="line">    <span class="attr">style</span>: <span class="string">&#x27;css&#x27;</span>, <span class="comment">// `style: true` 会加载 less 文件</span></span><br><span class="line">  &#125;),</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h3 id="使用-antd"><a href="#使用-antd" class="headerlink" title="使用 antd"></a>使用 antd</h3><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/App.tsxe</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Button</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;antd&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">&#x27;./App.css&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">&quot;App&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Button</span> <span class="attr">type</span>=<span class="string">&quot;primary&quot;</span>&gt;</span>Button<span class="tag">&lt;/<span class="name">Button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure><p>运行 <code>yarn start</code> 访问页面，antd 组件的 js 和 css 代码都会按需加载，你在控制台也不会看到这样的<a href="https://zos.alipayobjects.com/rmsportal/vgcHJRVZFmPjAawwVoXK.png">警告信息</a>。关于按需加载的原理和其他方式可以阅读<a href="https://ant.design/docs/react/getting-started-cn#%E6%8C%89%E9%9C%80%E5%8A%A0%E8%BD%BD">这里</a>。</p><h3 id="自定义主题"><a href="#自定义主题" class="headerlink" title="自定义主题"></a>自定义主题</h3><p>按照 <a href="https://ant.design/docs/react/customize-theme-cn">配置主题</a> 的要求，自定义主题需要用到 less 变量覆盖功能。我们可以引入 <code>customize-cra</code> 中提供的 less 相关的函数 <a href="https://github.com/arackaf/customize-cra#addlessloaderloaderoptions">addLessLoader</a> 来帮助加载 less 样式，同时修改 <code>config-overrides.js</code> 文件如下。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">yarn add less less-loader -D</span></span><br></pre></td></tr></table></figure><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="deletion">- const &#123; override, fixBabelImports &#125; = require(&#x27;customize-cra&#x27;);</span></span><br><span class="line"><span class="addition">+ const &#123; override, fixBabelImports, addLessLoader &#125; = require(&#x27;customize-cra&#x27;);</span></span><br><span class="line"></span><br><span class="line">module.exports = override(</span><br><span class="line">  fixBabelImports(&#x27;import&#x27;, &#123;</span><br><span class="line">    libraryName: &#x27;antd&#x27;,</span><br><span class="line">    libraryDirectory: &#x27;es&#x27;,</span><br><span class="line"><span class="deletion">-   style: &#x27;css&#x27;,</span></span><br><span class="line"><span class="addition">+   style: true,</span></span><br><span class="line">  &#125;),</span><br><span class="line"><span class="addition">+ addLessLoader(&#123;</span></span><br><span class="line"><span class="addition">+   javascriptEnabled: true,</span></span><br><span class="line"><span class="addition">+   modifyVars: &#123; &#x27;@primary-color&#x27;: &#x27;#1DA57A&#x27; &#125;,</span></span><br><span class="line"><span class="addition">+ &#125;),</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>这里利用了 <a href="https://github.com/webpack/less-loader#less-options">less-loader</a> 的 <code>modifyVars</code> 来进行主题配置，变量和其他配置方式可以参考 <a href="https://ant.design/docs/react/customize-theme-cn">配置主题</a> 文档。</p><p>修改后重启 <code>yarn start</code>，如果看到一个绿色的按钮就说明配置成功了。</p><h3 id="使用-Day-js-替换-momentjs-优化打包大小-1"><a href="#使用-Day-js-替换-momentjs-优化打包大小-1" class="headerlink" title="使用 Day.js 替换 momentjs 优化打包大小"></a>使用 Day.js 替换 momentjs 优化打包大小</h3><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="addition">+ const AntdDayjsWebpackPlugin = require(&#x27;antd-dayjs-webpack-plugin&#x27;);</span></span><br><span class="line"><span class="deletion">- const &#123; override, fixBabelImports, addLessLoader &#125; = require(&#x27;customize-cra&#x27;);</span></span><br><span class="line"><span class="addition">+ const &#123; override, fixBabelImports, addLessLoader, addWebpackPlugin &#125; = require(&#x27;customize-cra&#x27;);</span></span><br><span class="line"></span><br><span class="line">module.exports = override(</span><br><span class="line">  fixBabelImports(&#x27;import&#x27;, &#123;</span><br><span class="line">    libraryName: &#x27;antd&#x27;,</span><br><span class="line">    libraryDirectory: &#x27;es&#x27;,</span><br><span class="line">    style: true,</span><br><span class="line">  &#125;),</span><br><span class="line">  addLessLoader(&#123;</span><br><span class="line">    javascriptEnabled: true,</span><br><span class="line">    modifyVars: &#123; &#x27;@primary-color&#x27;: &#x27;#1DA57A&#x27; &#125;,</span><br><span class="line">  &#125;),</span><br><span class="line"><span class="addition">+  addWebpackPlugin(new AntdDayjsWebpackPlugin()),</span></span><br><span class="line">);</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="decorators"><a href="#decorators" class="headerlink" title="decorators"></a>decorators</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">yarn add -D @babel/plugin-proposal-decorators</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; addDecoratorsLegacy &#125; = <span class="built_in">require</span>(<span class="string">&#x27;customize-cra&#x27;</span>);</span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = <span class="title function_">override</span>(</span><br><span class="line">...</span><br><span class="line">  <span class="title function_">addDecoratorsLegacy</span>(),</span><br><span class="line">  ...</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h3 id="配置-Babel-插件"><a href="#配置-Babel-插件" class="headerlink" title="配置 Babel 插件"></a>配置 Babel 插件</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">module.exports = override(</span><br><span class="line">  ...,</span><br><span class="line">  ...addBabelPresets(</span><br><span class="line">    [</span><br><span class="line">      &quot;@babel/preset-env&quot;,</span><br><span class="line">      &#123;</span><br><span class="line">        targets: &#123;</span><br><span class="line">          browsers: [&quot;&gt; 1%&quot;, &quot;last 2 versions&quot;],</span><br><span class="line">          ie: 9</span><br><span class="line">        &#125;,</span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  )</span><br><span class="line">  ...</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h3 id="允许使用-babelrc-js-文件进行Babel配置。"><a href="#允许使用-babelrc-js-文件进行Babel配置。" class="headerlink" title="允许使用 .babelrc.js 文件进行Babel配置。"></a>允许使用 .babelrc.js 文件进行Babel配置。</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// config-overrides.js</span></span><br><span class="line"><span class="keyword">const</span> &#123; useBabelRc &#125; = <span class="built_in">require</span>(<span class="string">&#x27;customize-cra&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = <span class="title function_">override</span>(</span><br><span class="line">  ...</span><br><span class="line">  <span class="comment">// 允许使用 .babelrc.js 文件进行Babel配置。</span></span><br><span class="line">  <span class="title function_">useBabelRc</span>()</span><br><span class="line">  ...</span><br><span class="line">);</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">yarn add @babel/preset-env -D</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// .babelrc.js</span></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">presets</span>: [</span><br><span class="line">    [</span><br><span class="line">      <span class="string">&quot;@babel/preset-env&quot;</span>, <span class="comment">//兼容ie9</span></span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">targets</span>: &#123;</span><br><span class="line">          <span class="attr">ie</span>: <span class="string">&quot;9&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  ],</span><br><span class="line">  <span class="attr">plugins</span>: [<span class="string">&quot;@babel/plugin-proposal-decorators&quot;</span>] <span class="comment">// 可以用来替换 addDecoratorsLegacy</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/4f0002889f48/</id>
    <link href="https://blog.zisheng.pro/posts/4f0002889f48/"/>
    <published>2020-03-03T20:17:10.000Z</published>
    <summary>以笔者的经验来看，Ant Design 设计体系下的产品设计理念、使用方式、底层技术、周边工具都保持着高度一致，工具不是越多越好，有一套好用顺手的就行，UI 框架千千万，你不可能都学一遍。Ant Design 无疑能够减少你的学习成本。</summary>
    <title>Antd 快速上手教程</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="Node.js" scheme="https://blog.zisheng.pro/tags/Node-js/"/>
    <category term="Nest.js" scheme="https://blog.zisheng.pro/tags/Nest-js/"/>
    <category term="server" scheme="https://blog.zisheng.pro/tags/server/"/>
    <content>
      <![CDATA[<h2 id="Nest-js-是什么？"><a href="#Nest-js-是什么？" class="headerlink" title="Nest.js 是什么？"></a>Nest.js 是什么？</h2><p>​Nest 是一个用于构建高效，可扩展的 <a href="http://nodejs.cn/">Node.js</a> 服务器端应用程序的框架。它使用渐进式 JavaScript，内置并完全支持 <a href="https://www.tslang.cn/">TypeScript</a>（但仍然允许开发人员使用纯 JavaScript 编写代码）并结合了 <code>OOP</code>（面向对象编程），<code>FP</code>（函数式编程）和<code> FRP</code>（函数式响应编程）的元素。</p><p>​在底层，Nest使用强大的 HTTP Server 框架，如 Express（默认）和 Fastify。Nest 在这些框架之上提供了一定程度的抽象，同时也将其 API 直接暴露给开发人员。这样可以轻松使用每个平台的无数第三方模块。</p><h2 id="Nest-js-解决了什么问题？"><a href="#Nest-js-解决了什么问题？" class="headerlink" title="Nest.js 解决了什么问题？"></a>Nest.js 解决了什么问题？</h2><p>​近年来，感谢 Node.js，JavaScript 已成为前端和后端应用程序的网络“通用语言”。这产生了令人敬畏的项目，如 <a href="https://angular.cn/">Angular</a>，React 和 Vue，它们提高了开发人员的工作效率，并能够构建快速，可测试和可扩展的前端应用程序。然而，虽然 Node（和服务器端 JavaScript ）存在大量优秀的库，帮助器和工具，但它们都没有有效地解决主要问题 - 架构。</p><p>​Nest 提供了一个开箱即用的应用程序架构，允许开发人员和团队创建高度可测试，可扩展，松散耦合且易于维护的应用程序。</p><h2 id="流行趋势"><a href="#流行趋势" class="headerlink" title="流行趋势"></a>流行趋势</h2><h3 id="npm下载量"><a href="#npm下载量" class="headerlink" title="npm下载量"></a>npm下载量</h3><img src="https://i.loli.net/2020/03/02/yFtbpaICvz9mlEJ.png" style="zoom:60%;" /><h3 id="star趋势"><a href="#star趋势" class="headerlink" title="star趋势"></a>star趋势</h3><img src="https://i.loli.net/2020/03/02/eC8Nitx2Y4QcKwL.png" style="zoom:60%;" /><h2 id="建立新项目"><a href="#建立新项目" class="headerlink" title="建立新项目"></a>建立新项目</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">npm i -g @nestjs/cli</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">nest new MyNest</span></span><br></pre></td></tr></table></figure><img src="https://i.loli.net/2020/03/02/vyuW5OdrCiSHNPe.png" style="zoom: 67%;" /><h2 id="核心文件"><a href="#核心文件" class="headerlink" title="核心文件"></a>核心文件</h2><p>将创建 <code>project</code> 目录， 安装node模块和一些其他样板文件，并将创建一个 <code>src</code> 目录，目录中包含几个核心文件。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">src</span><br><span class="line">├── app.controller.ts // 带有单个路由的基本控制器示例。</span><br><span class="line">├── app.module.ts // 应用程序的根模块。</span><br><span class="line">└── main.ts // 应用程序入口文件。它使用 NestFactory 用来创建 Nest 应用实例。</span><br></pre></td></tr></table></figure><p><code>main.ts</code> 包含一个异步函数，它负责<strong>引导</strong>我们的应用程序：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">NestFactory</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@nestjs/core&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">ApplicationModule</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;./app.module&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">bootstrap</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> app = <span class="keyword">await</span> <span class="title class_">NestFactory</span>.<span class="title function_">create</span>(<span class="title class_">ApplicationModule</span>);</span><br><span class="line">  <span class="keyword">await</span> app.<span class="title function_">listen</span>(<span class="number">3000</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">bootstrap</span>();</span><br></pre></td></tr></table></figure><p>要创建一个 Nest 应用程序，我们使用了 <code>NestFactory</code> 核心类。<code>NestFactory</code> 暴露了一些静态方法用于创建应用实例。<code>create()</code> 方法返回一个实现 <code>INestApplication</code> 接口的对象，并提供一组可用的方法。在上面的 <code>main.ts</code> 示例中，我们只是启动了 HTTP 服务器，它允许应用程序等待入站 HTTP 请求。</p><h2 id="运行应用程序"><a href="#运行应用程序" class="headerlink" title="运行应用程序"></a>运行应用程序</h2><p>安装过程完成后，您可以在系统命令提示符下运行以下命令，以启动应用程序监听入站 HTTP 请求：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">npm run start</span></span><br></pre></td></tr></table></figure><p>此命令在 <code>src</code> 目录中的 <code>main.ts</code> 文件中定义的端口上启动 HTTP 服务器。在应用程序运行时, 打开浏览器并访问 <code>http://localhost:3000/</code>。 你应该看到 <code>Hello world!</code> 信息。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/f9e39267a7ed/</id>
    <link href="https://blog.zisheng.pro/posts/f9e39267a7ed/"/>
    <published>2020-03-01T20:05:36.000Z</published>
    <summary>
      <![CDATA[<h2 id="Nest-js-是什么？"><a href="#Nest-js-是什么？" class="headerlink" title="Nest.js 是什么？"></a>Nest.js 是什么？</h2><p>​	Nest 是一个用于构建高效，可扩展的 <a href=]]>
    </summary>
    <title>Nest.js 快速上手</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React-Native/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/tags/React-Native/"/>
    <category term="掘金" scheme="https://blog.zisheng.pro/tags/%E6%8E%98%E9%87%91/"/>
    <content>
      <![CDATA[<ol><li><p>下载<a href="http://hao.ziticq.com/">免费商用字体</a></p><blockquote><p>法棍体：链接:<a href="https://pan.baidu.com/s/1M-CDEqsJ06qj-42uRyF0WA">https://pan.baidu.com/s/1M-CDEqsJ06qj-42uRyF0WA</a>  密码:83er</p></blockquote></li><li><p>使用 FontLab 查看字体的 <code>fontFamily</code></p><blockquote><p>FontLab 破解版 Mac：</p><p>链接:<a href="https://pan.baidu.com/s/1Wb3xgB9mJqF4YGX0xXddMg">https://pan.baidu.com/s/1Wb3xgB9mJqF4YGX0xXddMg</a>  密码:1cfb</p></blockquote><img src="https://i.loli.net/2020/03/01/m9A5CK2cZMYswBF.png"  style="zoom:40%;" /></li><li><p>将字体放到放在项目根目录：</p></li><li><p>在根目录新建 <code>react-naitve.config.js</code>:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">project</span>: &#123;</span><br><span class="line">    <span class="attr">ios</span>: &#123;&#125;,</span><br><span class="line">    <span class="attr">android</span>: &#123;&#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">assets</span>: [<span class="string">&#x27;./fonts&#x27;</span>],</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>在根目录执行 <code>react-native link</code> 命令</p></li><li><p>使用字体：</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 全局</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">TextRender</span> = <span class="title class_">Text</span>.<span class="property">render</span></span><br><span class="line"><span class="title class_">Text</span>.<span class="property">render</span> = <span class="function">(<span class="params">...args</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> originText = <span class="title class_">TextRender</span>.<span class="title function_">apply</span>(<span class="variable language_">this</span>, args)</span><br><span class="line">  <span class="keyword">const</span> &#123; style &#125; = originText.<span class="property">props</span></span><br><span class="line">  <span class="keyword">return</span> <span class="title class_">React</span>.<span class="title function_">cloneElement</span>(originText, &#123;</span><br><span class="line">    <span class="attr">allowFontScaling</span>: <span class="literal">false</span>,</span><br><span class="line">    <span class="attr">style</span>: [&#123; <span class="attr">fontFamily</span>: <span class="string">&#x27;Baguette&#x27;</span> &#125;, style],</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 局部</span></span><br><span class="line">&lt;<span class="title class_">Text</span> style=&#123;&#123; <span class="attr">fontSize</span>: <span class="number">50</span>, <span class="attr">fontFamily</span>: <span class="string">&#x27;Baguette&#x27;</span> &#125;&#125;&gt; 法棍体🥖 &lt;/<span class="title class_">Text</span>&gt;</span><br></pre></td></tr></table></figure></li><li><p>效果：</p><img src="https://i.loli.net/2020/03/01/6YZlpi3ar87LKQd.jpg" alt="WechatIMG1166.jpeg" style="zoom:50%;" /></li></ol><h2 id="附录"><a href="#附录" class="headerlink" title="附录"></a>附录</h2><ul><li><a href="https://github.com/react-native-training/react-native-fonts">React Native 中开箱即用的字体</a></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/78713277e253/</id>
    <link href="https://blog.zisheng.pro/posts/78713277e253/"/>
    <published>2020-02-29T08:52:39.000Z</published>
    <summary>本文主要讲解了如何在 React Native 中使用自定义字体，以及如何在 iOS 和 Android 中使用自定义字体。</summary>
    <title>玩转 React Native 自定义字体</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React-Native/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/tags/React-Native/"/>
    <category term="架构" scheme="https://blog.zisheng.pro/tags/%E6%9E%B6%E6%9E%84/"/>
    <category term="最佳实践" scheme="https://blog.zisheng.pro/tags/%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/"/>
    <content>
      <![CDATA[<p><img src="https://i.loli.net/2020/02/25/dLMXty7iYKnVk35.png" alt="React Native 集成 CodePush 指南"></p><p>目前现存的热更新方案有腾讯的 <a href="https://bugly.qq.com/v2/products/upgrade">Bugly 应用升级</a>、React Native 中文网的 <a href="https://update.reactnative.cn/home">Pushy</a>、微软的 <a href="https://bre.is/r3Y9hJvB">CodePush</a> 和用来搭建私服的 <a href="https://github.com/lisong/code-push-server">code-push-server</a>。</p><p>本文分享的是基于微软 AppCenter 的 CodePush 服务实现热更新，这个比较有代表性，也方便各位读者大大实践。当然鉴于国内的网络环境，后期会发布一篇如何基于 <code>code-push-server</code> 实现热更新功能。</p><h2 id="环境"><a href="#环境" class="headerlink" title="环境"></a>环境</h2><ul><li>Xcode：Version 11.3.1 (11C504)</li><li>react-native：0.61.5</li><li>react-native-code-push: 6.1.0</li><li>appcenter-cli：2.3.3</li></ul><h2 id="CodePush介绍"><a href="#CodePush介绍" class="headerlink" title="CodePush介绍"></a>CodePush介绍</h2><p>CodePush 是一个 App Center 云服务，使 Apache Cordova 和 React Native 开发人员可以将移动应用程序更新直接部署到其用户的设备上。它充当中央存储库的角色，开发人员可以将某些更新（例如JS，HTML，CSS和图像更改）发布到该存储库，并且应用程序可以（使用提供的客户端SDK）从中查询更新。这使你可以与最终用户建立更具确定性和直接的参与度模型，同时解决错误和&#x2F;或添加一些小的功能，这些功能不需要你重建二进制文件和&#x2F;或通过任何公共应用商店重新分发二进制文件。默认情况下，在 App Center 上创建的所有 React Native 应用程序都启用了 CodePush。</p><blockquote><p>注意：对于Android设备，CodePush仅在兼容TLS 1.2的设备上运行</p></blockquote><h3 id="1-安装-App-Center-CLI"><a href="#1-安装-App-Center-CLI" class="headerlink" title="1.安装 App Center CLI"></a>1.安装 App Center CLI</h3><p>你可以使用 App Center CLI 管理 CodePush 的大多数功能。要安装 CLI，请打开终端窗口或命令提示符并执行以下命令：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install -g appcenter-cli</span><br></pre></td></tr></table></figure><p>成功安装 App Center CLI 后，执行<code>appcenter login</code>命令为你的 App Center 帐户详细信息配置 CLI：</p><p><img src="https://i.loli.net/2020/02/26/TwJyWsBOK5trHmj.png" alt="React Native 集成 CodePush 指南"></p><h3 id="2-应用管理"><a href="#2-应用管理" class="headerlink" title="2.应用管理"></a>2.应用管理</h3><p>部署更新之前，必须使用以下命令使用 App Center 创建应用：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">appcenter apps create -d &lt;appDisplayName&gt; -o &lt;operatingSystem&gt; -p &lt;platform&gt;</span><br></pre></td></tr></table></figure><p>如果您的应用同时针对 Android 和 iOS，我们强烈建议您使用 CodePush 创建单独的应用。每个平台一个。这样，您可以分别管理和发布更新，从长远来看，这会使事情变得更简单。大多数人只是在应用名称后缀<code>-Android</code>和<code>-iOS</code>。例如：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">appcenter apps create -d MyApp-Android -o Android -p React-Native</span><br><span class="line">appcenter apps create -d MyApp-iOS -o iOS -p React-Native</span><br></pre></td></tr></table></figure><blockquote><p>注意：在 Android 和 iOS 上使用相同的应用程序可能会导致安装异常，因为为 iOS 生成的 CodePush 更新包将具有与为 Android 生成的更新不同的内容。</p></blockquote><blockquote><p>通过 <code>appcenter apps list</code> 可以查看所有的应用。</p></blockquote><blockquote><p>在App中心CLI的一个重要的新功能是设置一个应用程序的能力<strong>当前应用程序</strong>使用<code>appcenter apps set-current &lt;ownerName&gt;/&lt;appName&gt;</code>。通过将一个应用程序设置为当前应用程序，您无需<code>-a</code>在其他CLI命令中使用该标志。例如，<code>appcenter codepush deployment list -a &lt;ownerName&gt;/&lt;appName&gt;</code>可以将命令缩短<code>appcenter codepush deployment list</code>为设置当前应用程序的时间。您可以使用来检查哪个应用程序被设置为您帐户的当前应用程序<code>appcenter apps get-current</code>。设置当前应用程序可以缩短大多数CLI命令的键入时间。</p></blockquote><p>使用 <code>code-push-cli</code>，应用程序会自动进行两次部署（<code>Staging</code>和 <code>Production</code>）。在 App Center 中，你必须使用以下命令自行创建它们：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">appcenter codepush deployment add -a &lt;ownerName&gt;/&lt;appName&gt; Staging</span><br><span class="line">appcenter codepush deployment add -a &lt;ownerName&gt;/&lt;appName&gt; Production</span><br></pre></td></tr></table></figure><p>创建部署后，您可以使用来访问两个部署的部署密钥<code>appcenter codepush deployment list --displayKeys</code>，您可以开始通过它们各自的SDK（用于<a href="https://docs.microsoft.com/en-us/appcenter/distribution/codepush/cordova">Cordova</a>和<a href="https://docs.microsoft.com/en-us/appcenter/distribution/codepush/react-native">React Native的</a>详细信息）来配置移动客户端。</p><h3 id="3-修改versionNam"><a href="#3-修改versionNam" class="headerlink" title="3.修改versionNam"></a>3.修改versionNam</h3><p>在 <code> android/app/build.gradle</code> 中有个 <code>android.defaultConfig.versionName</code> 属性（在 <code>ios/**/Info.plist</code> 是 <code>&lt;key&gt;CFBundleShortVersionString&lt;/key&gt;</code> 属性 ）；我们需要把应用版本改成 <code>1.0.0</code>（默认<code>1.0</code>，但是 <code>codepush</code> 需要三位数）</p><h3 id="3-发布应用更新"><a href="#3-发布应用更新" class="headerlink" title="3.发布应用更新"></a>3.发布应用更新</h3><p>更改应用程序的代码或资产后，请按照以下说明使用 App Center CLI 将更新发布到App Center。</p><p>执行 App Center CLI <code>release-react </code> 命令以捆绑应用程序的代码和资产文件，然后将它们作为新版本发布到 App Center 服务器。例如：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">appcenter codepush release-react -a &lt;ownerName&gt;/&lt;appName&gt; -d Staging -t 1.0.0 -m  --development false --description &lt;description&gt;</span><br></pre></td></tr></table></figure><ul><li><code>[-a|--app &lt;ownerName&gt;/&lt;appName&gt;]</code>:  指定应用</li><li><code>[-d|--deployment-name &lt;deploymentName&gt;</code>]:  此参数指定要将更新发布到的部署。它默认为<code>Staging</code>，但是当您准备部署到<code>Production</code>或您自己的自定义部署之一时，只需显式设置此参数即可。</li><li><code>[-t|--target-binary-version &lt;targetBinaryVersion&gt;]</code>:  指定要更新的应用的原生版本</li><li><code>[-m|--mandatory]</code>:  是强制更新，默认 <code>false</code></li><li><code>[--development]</code>: 此参数指定是否生成未缩小的开发JS包。如果未指定，则默认为<code>false</code>禁用警告并缩小包的位置。</li><li><code>[--description &lt;description&gt;</code>]:  此参数为部署提供了可选的“更改日志”。该值将往返传送给客户端，以便在检测到更新时，您的应用可以选择将其显示给最终用户（例如，通过“新功能”对话框）。该字符串接受诸如<code>\n</code>和的控制字符，<code>\t</code>因此您可以在描述中包括空格格式，以提高可读性。</li></ul><blockquote><p>CodePush客户端支持差异更新，因此，即使您在每次更新中释放JS捆绑包和资产，最终用户也只会实际下载他们需要的文件。该服务会自动处理此问题，因此您可以专注于创建出色的应用程序，而我们会担心优化最终用户的下载。</p></blockquote><h2 id="React-Native-Client-SDK"><a href="#React-Native-Client-SDK" class="headerlink" title="React Native Client SDK"></a>React Native Client SDK</h2><p>该插件为 CodePush 服务提供了客户端集成，使你可以轻松地向你的 React Native 应用添加动态更新体验。</p><blockquote><p>注意：以下配置均基于 react-native 0.60 版本。</p></blockquote><h3 id="它是如何工作的？"><a href="#它是如何工作的？" class="headerlink" title="它是如何工作的？"></a>它是如何工作的？</h3><p>React Native 应用程序由 JavaScript 文件和任何相关的图片组成，它们由打包程序 <a href="https://facebook.github.io/metro/">metro</a> 捆绑在一起, 并作为特定于平台的二进制文件（<code>.ipa</code> 或 <code>.apk</code> 文件）的一部分进行分发。发行该应用程序时，更新 JavaScript 代码（例如进行错误修复，添加新功能）或更新图片资源要求你重新编译并重新分发整个二进制文件，其中包括与商店相关的所有时间。</p><p>通过使你的 JavaScript 和图片资源与您发布到 CodePush 服务器的更新同步，CodePush 插件可帮助你立即在最终用户面前获得产品改进。</p><p>为了确保您的最终用户始终拥有你的应用程序的正常运行版本，CodePush 插件会维护先前更新的副本，因此，如果您不小心推送了包含崩溃的更新，它可以自动回滚。这样，你可以放心，新发现的发行版不会导致用户被阻塞。</p><blockquote><p>注意：任何涉及本机代码的产品更改（例如，修改<code>AppDelegate.m</code>&#x2F;<code>MainActivity.java</code>、添加 <code>ttf</code> 或添加原生插件）都无法通过 CodePush 分发，因此必须通过相应的商店进行更新。</p></blockquote><h3 id="支持的React-Native平台"><a href="#支持的React-Native平台" class="headerlink" title="支持的React Native平台"></a>支持的React Native平台</h3><ul><li>iOS（7以上）</li><li>Android（4.1以上）</li><li>Windows（UWP）</li></ul><h3 id="安装-react-native-code-push"><a href="#安装-react-native-code-push" class="headerlink" title="安装 react-native-code-push"></a>安装 react-native-code-push</h3><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn add react-native-code-push</span><br></pre></td></tr></table></figure><p>与其他所有React Native插件一样，iOS 和 Android 的集成体验也有所不同，因此请根据您的应用目标平台执行以下设置步骤。请注意，如果您同时针对两个平台，建议为每个平台创建单独的 CodePush 应用程序。</p><blockquote><p>本指南假定您已使用该<code>react-native init</code>命令初始化React Native项目。</p></blockquote><h3 id="iOS设置"><a href="#iOS设置" class="headerlink" title="iOS设置"></a>iOS设置</h3><p>获得 CodePush 插件后，必须将其集成到 React Native 应用程序的 Xcode 项目中并正确配置。</p><ol><li><p>运行<code>cd ios &amp;&amp; pod install &amp;&amp; cd ..</code>以安装所有必需的CocoaPods依赖项。</p></li><li><p>打开<code>AppDelegate.m</code>文件，并为CodePush标头添加导入语句：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">#import &lt;CodePush/CodePush.h&gt;</span><br></pre></td></tr></table></figure></li><li><p>查找以下代码行，该代码为生产版本的网桥设置源URL：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">return [[NSBundle mainBundle] URLForResource:@&quot;main&quot; withExtension:@&quot;jsbundle&quot;];</span><br></pre></td></tr></table></figure></li><li><p>用以下行替换它：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">return [CodePush bundleURL];</span><br></pre></td></tr></table></figure><p>此更改将你的应用配置为始终加载应用JS捆绑包的最新版本。在首次启动时，这将与使用该应用程序编译的文件相对应。但是，在通过CodePush推送更新后，这将返回最近安装的更新的位置。</p><p>通常，您只想使用 CodePush 来解决发行版本中的 JS 包位置，因此，我们建议使用<code>DEBUG</code>预处理器宏在是否使用打包程序服务器和 CodePush 之间进行动态切换。这样可以更轻松地确保您在生产中获得所需的正确行为，同时仍可以在调试时使用Chrome开发工具，实时重新加载等。</p><p>你的<code>sourceURLForBridge</code>方法应如下所示：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge</span><br><span class="line">&#123;</span><br><span class="line">  #if DEBUG</span><br><span class="line">    return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@&quot;index&quot; fallbackResource:nil];</span><br><span class="line">  #else</span><br><span class="line">    return [CodePush bundleURL];</span><br><span class="line">  #endif</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>将部署密钥添加到<code>Info.plist</code>：</p><p>为了让 CodePush 运行时知道应该针对哪个部署查询更新，请打开你的应用的 <code>Info.plist</code> 文件，并添加一个名为<code>CodePushDeploymentKey</code>的新条目，其值是你要配置的应用的 <code>Staging Deployment Key</code>。</p><p>你可以通过 <code>appcenter codepush deployment list -k</code> 来检索这个值（该 <code>-k</code> 标志是必需的，因为默认情况下不会显示键），然后复制相对应的 <code>Deployment Key</code> 即可。</p><p><img src="https://cloud.githubusercontent.com/assets/116461/11601733/13011d5e-9a8a-11e5-9ce2-b100498ffb34.png" alt="React Native 集成 CodePush 指南"></p><p>为了有效利用与 CodePush 应用程序一起创建的 <code>Staging</code> 和 <code>Production</code> 部署，请在实际将你的应用程序对 CodePush 的使用移入生产环境之前，进行<a href="#%E5%A4%9A%E9%83%A8%E7%BD%B2%E6%B5%8B%E8%AF%95">多部署测试</a>的配置。</p><blockquote><p>如果您需要动态使用其他部署，还可以使用<a href="#%E5%8A%A8%E6%80%81%E9%83%A8%E7%BD%B2%E5%88%86%E9%85%8D">动态部署分配</a>在JS代码中覆盖部署密钥</p></blockquote></li></ol><h3 id="Android设置"><a href="#Android设置" class="headerlink" title="Android设置"></a>Android设置</h3><p>为了将CodePush集成到您的Android项目中，请执行以下步骤：</p><ol><li><p>在<code>android/app/build.gradle</code>文件中，将文件<code>codepush.gradle</code>添加为下面的其他构建任务定义：</p><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">apply <span class="attr">from:</span> <span class="string">&quot;../../node_modules/react-native-code-push/android/codepush.gradle&quot;</span></span><br><span class="line">...</span><br></pre></td></tr></table></figure></li><li><p><code>MainApplication.java</code>通过以下更改更新文件以使用 CodePush：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line"><span class="comment">// 1. 导入插件的类</span></span><br><span class="line"><span class="keyword">import</span> com.microsoft.codepush.react.CodePush;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MainApplication</span> <span class="keyword">extends</span> <span class="title class_">Application</span> <span class="keyword">implements</span> <span class="title class_">ReactApplication</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">ReactNativeHost</span> <span class="variable">mReactNativeHost</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ReactNativeHost</span>(<span class="built_in">this</span>) &#123;</span><br><span class="line">        ...</span><br><span class="line">        <span class="comment">// 2. 重写 getJSBundleFile 方法，每次 app 启动的时候让 CodePush 运行时决定从哪里加载 JS bundle</span></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">protected</span> String <span class="title function_">getJSBundleFile</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> CodePush.getJSBundleFile();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>将部署密钥添加到<code>strings.xml</code>：</p><p>为了让 CodePush 运行时知道它应该查询哪些部署更新，请打开您的应用程序的 <code>string.xml</code> 文件，并添加一个名为 <code>CodePushDeploymentKey</code> 的新字符串，它的值是应用的 <code>Staging</code> 部署。你可以通过 <code>appcenter deployment list &lt;ownerName&gt;/&lt;appName&gt; -k</code> 获取该值。</p><p><img src="https://cloud.githubusercontent.com/assets/116461/11601733/13011d5e-9a8a-11e5-9ce2-b100498ffb34.png" alt="React Native 集成 CodePush 指南"></p><p>您<code>strings.xml</code>应该看起来像这样：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">resources</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">string</span> <span class="attr">name</span>=<span class="string">&quot;app_name&quot;</span>&gt;</span>AppName<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">string</span> <span class="attr">moduleConfig</span>=<span class="string">&quot;true&quot;</span> <span class="attr">name</span>=<span class="string">&quot;CodePushDeploymentKey&quot;</span>&gt;</span>DeploymentKey<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">resources</span>&gt;</span></span><br></pre></td></tr></table></figure><p>为了有效利用与CodePush应用程序一起创建的<code>Staging</code>和<code>Production</code>部署，请在实际将您的应用程序对CodePush的使用移入生产环境之前，请参考下面的<a href="#%E5%A4%9A%E9%83%A8%E7%BD%B2%E6%B5%8B%E8%AF%95">多部署测试</a>文档。</p><blockquote><p>如果您需要动态使用其他部署，还可以使用<a href="#%E5%8A%A8%E6%80%81%E9%83%A8%E7%BD%B2%E5%88%86%E9%85%8D">动态部署分配</a>在JS代码中覆盖部署密钥</p></blockquote></li></ol><h3 id="使用插件"><a href="#使用插件" class="headerlink" title="使用插件"></a>使用插件</h3><p>下载并链接了 CodePush 插件，并且为你的应用程序询问 CodePush 从何处获取正确的 JS bundle 包后，剩下的唯一一件事就是向你的应用程序添加必要的代码，以控制以下策略：</p><ol><li>什么时候（多久）检查一次更新？（例如，应用程序启动，在设置页面中单击按钮或按固定时间间隔定期进行）</li><li>当有可用更新时，如何将其呈现给最终用户？</li></ol><p>最简单的方式是 <code>CodePush-ify</code> 应用程序的根组件。为此，您可以选择一下两个选项之一：</p><ul><li><p>选项1：将您的根组件与 codePush 高阶组件包装在一起：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> codePush <span class="keyword">from</span> <span class="string">&#x27;react-native-code-push&#x27;</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">App</span> = (<span class="params"></span>) =&gt; &#123;&#125;</span><br><span class="line"><span class="title class_">App</span> = <span class="title function_">codePush</span>(<span class="title class_">App</span>)</span><br></pre></td></tr></table></figure></li><li><p>选项2：使用 <a href="https://github.com/wycats/javascript-decorators">ES7装饰器 </a>语法：</p><blockquote><p>Babel 6.x 尚不支持装饰器。您可能需要通过安装和使用 <a href="https://github.com/skevy/babel-preset-react-native-stage-0#babel-preset-react-native-stage-0">babel-preset-react-native-stage-0</a> 来启用装饰器。</p></blockquote><blockquote><p>Babel 7.x 支持装饰器语法。你可以使用 <a href="https://babeljs.io/docs/en/next/babel-plugin-proposal-decorators.html">@babel&#x2F;plugin-proposal-decorators</a> 来启用装饰器。</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> codePush <span class="keyword">from</span> <span class="string">&quot;react-native-code-push&quot;</span></span><br><span class="line"></span><br><span class="line">@codePush</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MyApp</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Component</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul><p>默认情况下，CodePush 将在每次启动应用程序时检查更新。如果有可用更新，它将在下一次重新启动应用程序时（由最终用户或操作系统明确显示）以静默方式下载并安装，从而确保最终用户获得最少的侵入性体验。如果必须使用可用的更新，则将立即安装该更新，以确保最终用户尽快获得它。</p><p>如果您希望应用程序更快地发现更新，则还可以选择每次应用程序从后台恢复时与 CodePush 服务器同步。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> codePushOptions = &#123; <span class="attr">checkFrequency</span>: codePush.<span class="property">CheckFrequency</span>.<span class="property">ON_APP_RESUME</span> &#125;</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">App</span> = (<span class="params"></span>) =&gt; &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title function_">codePush</span>(codePushOptios)(<span class="title class_">App</span>)</span><br></pre></td></tr></table></figure><p>另外，如果您想对检查发生的时间进行细粒度的控制（例如按按钮或定时器间隔），则可以使用 <a href="https://bre.is/dPuwHWre">CodePush.sync()</a> 随时进行调用，还可以通过 <code>SyncOptions</code> 通过 <code>CheckFrequency.MANUAL</code> 来关闭 CodePush 的自动检查功能:</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">View</span>, <span class="title class_">StyleSheet</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;react-native&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> codePush <span class="keyword">from</span> <span class="string">&#x27;react-native-code-push&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">AwesomeButton</span> <span class="keyword">from</span> <span class="string">&#x27;react-native-really-awesome-button&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> codePushOptions = &#123; <span class="attr">checkFrequency</span>: codePush.<span class="property">CheckFrequency</span>.<span class="property">MANUAL</span> &#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">App</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">checkForUpdate</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    codePush.<span class="title function_">sync</span>(&#123;</span><br><span class="line">      <span class="attr">updateDialog</span>: <span class="literal">true</span>,</span><br><span class="line">      <span class="attr">installMode</span>: codePush.<span class="property">InstallMode</span>.<span class="property">IMMEDIATE</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">clear</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    codePush.<span class="title function_">clearUpdates</span>();</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">View</span> <span class="attr">style</span>=<span class="string">&#123;styles.container&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">AwesomeButton</span> <span class="attr">type</span>=<span class="string">&quot;secondary&quot;</span> <span class="attr">onPress</span>=<span class="string">&#123;checkForUpdate&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        检查更新</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">AwesomeButton</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">AwesomeButton</span> <span class="attr">type</span>=<span class="string">&quot;secondary&quot;</span> <span class="attr">onPress</span>=<span class="string">&#123;clear&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        清除更新</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">AwesomeButton</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">View</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> styles = <span class="title class_">StyleSheet</span>.<span class="title function_">create</span>(&#123;</span><br><span class="line">  <span class="attr">container</span>: &#123;</span><br><span class="line">    <span class="attr">flex</span>: <span class="number">1</span>,</span><br><span class="line">    <span class="attr">justifyContent</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">    <span class="attr">alignItems</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 注意：这是可选的，完全可以不使用 codePush 这里包装</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title function_">codePush</span>(codePushOptions)(<span class="title class_">App</span>);</span><br></pre></td></tr></table></figure><p>如果你想要显示一个更新确认弹窗（一个主动安装）。配置何时安装可用更新（例如强制立即重启）或以任何其他方式自定义更新体验，请参阅 <a href="https://docs.microsoft.com/en-us/appcenter/distribution/codepush/react-native#api-reference">codepush</a> API参考以获取有关一下信息：如何调整此默认行为。</p><h3 id="应用商店规则"><a href="#应用商店规则" class="headerlink" title="应用商店规则"></a>应用商店规则</h3><ul><li><p>苹果App允许使用热更新<a href="https://developer.apple.com/programs/ios/information/iOS_Program_Information_4_3_15.pdf">Apple’s developer agreement</a>, 为了不影响用户体验，规定必须使用静默更新。</p></li><li><p>Google Play不能使用静默更新，必须弹框告知用户App有更新。</p></li><li><p>中国的android市场必须采用静默更新（如果弹框提示，App会被“请上传最新版本的二进制应用包”原因驳回）。</p></li></ul><h2 id="多部署测试"><a href="#多部署测试" class="headerlink" title="多部署测试"></a>多部署测试</h2><p>在入门文档中，我们说明了如何使用特定的部署密钥配置 CodePush 插件。但是，为了有效地测试发型版，至关重要的是，在首次创建 CodePush 应用程序（或你可能已经创建的任何自定义部署）时，利用我们建议进行的 <code>Staging</code> 和 <code>Production</code> 部署。</p><blockquote><p>我们的客户端回滚功能可以帮助您在安装导致崩溃的版本后解除对用户的阻止，服务器端的回滚（例如<code>appcenter codepush rollback</code>）使您可以防止其他用户在发现错误的版本后再安装它。但是，如果可以从一开始就防止广泛发布错误更新，那显然更好。</p></blockquote><p>利用<code>Staging</code>和<code>Production</code>部署，您可以实现类似于以下的工作流程（随意定制！）：</p><ol><li><code>Staging</code> 使用 <code>appcenter codepush release-react</code> 命令将 CodePush 更新发布到您的部署中（如果你需要更多的控制权可以使用 <code>appcenter codepush release</code> ）</li><li>构建应用程序的 staging<code>/</code>beta&#96; 版本，从服务器同步更新，并验证其是否按预期工作</li><li>使用以下命令将测试的发行版从 <code>Staging</code> 升级到 <code>Prouction</code>: <code>appcenter codepush promote -a &lt;ownerName&gt;/&lt;appName&gt; -s Staging -d Production</code></li><li>构建应用程序的 <code>production</code>&#x2F;<code>release</code>，从服务其同步更新并验证其是否按预期工作</li></ol><blockquote><p>如果您想采取更为谨慎的方法，甚至可以选择在“＃3”中执行 <strong>分阶段推出</strong>，这使您可以减轻更新带来的额外潜在风险（例如，＃2中的测试是否接触了所有可能的设备），仅使一定比例的用户可以使用生产更新（例如<code>code-push promote -a / -s Staging -d Production -r 20%</code>）。然后，在等待了一段合理的时间以查看是否有崩溃报告或客户反馈后，您可以通过运行将其扩展到整个受众<code>appcenter codepush patch -a / Production -r 100%</code>。</p></blockquote><h3 id="安卓系统"><a href="#安卓系统" class="headerlink" title="安卓系统"></a>安卓系统</h3><p>在<a href="https://google.github.io/android-gradle-dsl/current/index.html">Android Gradle plugin </a>允许您定义自定义配置设置，每个“构建类型”（如调试，发布）。此机制使您可以轻松地使用 CodePush 部署密钥配置调试版本，而发行版本也可以配置为使用 CodePush 生产部署密钥。</p><p>提醒一下，您可以通过<code>appcenter codepush deployment list  -k</code>从终端运行来检索这些键。</p><p>要进行设置，请执行以下步骤：</p><ol><li><p>打开项目的应用程序级别<code>build.gradle</code>文件（例如标准 React Native 项目中的 <code>android/app/build.gradle</code>）</p></li><li><p>查找此<code>android { buildTypes {} }</code>部分，并<code>resValue</code>为您<code>debug</code>和<code>release</code>构建类型定义条目，分别引用您的密钥<code>Staging</code>和<code>Production</code>部署密钥。</p> <figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">android &#123;</span><br><span class="line">  ...</span><br><span class="line">  buildTypes &#123;</span><br><span class="line">    debug &#123;</span><br><span class="line">      signingConfig signingConfigs.debug</span><br><span class="line">      <span class="comment">// Note: CodePush updates should not be tested in Debug mode as they are overriden by the RN packager. However, because CodePush checks for updates in all modes, we must supply a key.</span></span><br><span class="line">      resValue <span class="string">&quot;string&quot;</span>, <span class="string">&quot;CodePushDeploymentKey&quot;</span>, <span class="string">&#x27;&quot;&quot;&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">    release &#123;</span><br><span class="line">      <span class="comment">// Caution! In production, you need to generate your own keystore file.</span></span><br><span class="line">      <span class="comment">// see https://facebook.github.io/react-native/docs/signed-apk-android.</span></span><br><span class="line">      signingConfig signingConfigs.release</span><br><span class="line">      minifyEnabled enableProguardInReleaseBuilds</span><br><span class="line">      proguardFiles getDefaultProguardFile(<span class="string">&quot;proguard-android.txt&quot;</span>), <span class="string">&quot;proguard-rules.pro&quot;</span></span><br><span class="line">      resValue <span class="string">&quot;string&quot;</span>, <span class="string">&quot;CodePushDeploymentKey&quot;</span>, <span class="string">&#x27;&quot;&quot;&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// <span class="doctag">NOTE:</span> The naming convention for releaseStaging is significant due to http://t.cn/EAnyAzi</span></span><br><span class="line">    releaseStaging.initWith(release)</span><br><span class="line">    releaseStaging &#123;</span><br><span class="line">        resValue <span class="string">&quot;string&quot;</span>, <span class="string">&quot;CodePushDeploymentKey&quot;</span>, <span class="string">&#x27;&quot;&quot;&#x27;</span></span><br><span class="line">        <span class="comment">// Note: It is a good idea to provide matchingFallbacks for the new buildType you create to prevent build issues（http://t.cn/EAex4XH）</span></span><br><span class="line">        <span class="comment">// Add the following line if not already there</span></span><br><span class="line">        matchingFallbacks = [<span class="string">&#x27;release&#x27;</span>]</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><blockquote><p>如果要在构建过程中配置部署密钥，请记住从<code>strings.xml</code>中删除密钥。</p></blockquote><blockquote><p><code>releaseStaging</code>由于<a href="https://github.com/facebook/react-native/blob/e083f9a139b3f8c5552528f8f8018529ef3193b9/react.gradle#L79">此行</a>，的命名约定，这不能改。</p></blockquote><h3 id="iOS"><a href="#iOS" class="headerlink" title="iOS"></a>iOS</h3><blockquote><p>该部分适用于 Xcode 11</p></blockquote><p>Xcode 允许你为每个<strong>配置</strong> (如 <code>debug</code>, <code>release</code>) 自定义构建设置，然后可以将其引用为 <code>Info.plist</code> 文件中的键值（如 <code>CodePushDeploymentKey</code> 设置）。此机制是您可以轻松地进行构建配置以生成二进制文件，这些二进制文件被配置为与不同的 CodePush 部署同步。</p><p>要进行设置，请执行以下步骤：</p><ol><li><p>打开您的 Xcode 项目，然后在 <code>Project navigator</code> 窗口中选择您的项目</p></li><li><p>确保已选择 <code>PROJECT</code> 节点，而不是 <code>TARGETS</code></p></li><li><p>选择 <code>Info</code> 标签</p></li><li><p>点击 <code>+</code> 的内部按钮<code>Duplicate &quot;Release&quot; Configuration</code><br>  <img src="https://docs.microsoft.com/en-us/appcenter/distribution/codepush/images/rn-ios-8.png" alt="React Native 集成 CodePush 指南"></p></li><li><p>将新配置命名为 <code>Staging</code>（或您喜欢的任何名称）</p></li><li><p>选择 <code>Build Settings</code> 选项卡</p></li><li><p>单击工具栏上的 <code>+</code> 按钮，创建一个名为  <code>CONFIGURATION_BUILD_DIR</code> 的 <code>User-Defined Setting</code>, 使用相同的 <code>per-configuration</code> 配置。</p></li></ol><p>  <img src="https://i.stack.imgur.com/Sodu3.png" alt="React Native 集成 CodePush 指南"></p><blockquote><p>注意：每次创建这个 Xcode 都会崩溃，只能先把值写入之后，在 <code>project.pbxproj</code> 中新建。</p></blockquote><ol start="8"><li><p>点击工具栏的 <code>+</code>  并选择 <code>Add User-Defined Setting</code></p><p><img src="https://docs.microsoft.com/en-us/appcenter/distribution/codepush/images/rn-ios-10.png" alt="React Native 集成 CodePush 指南"></p></li><li><p>将此新设置命名为<code>CodePushDeploymentKey</code>，展开它，然后为 <code>Staging </code>配置指定您的 <code>Staging</code> 部署密钥，为 <code>Release</code> 配置指定您的 <code>Production</code> 部署密钥。</p><p><img src="https://docs.microsoft.com/en-us/appcenter/distribution/codepush/images/rn-ios-11.png" alt="React Native 集成 CodePush 指南"></p><blockquote><p>提醒一下，您可以通过<code>appcenter codepush deployment list -a &lt;ownerName&gt;/&lt;appName&gt; --displayKeys</code>从终端运行来检索这些键。</p></blockquote></li><li><p>打开项目的 <code>Info.plist</code> 文件，然后将<code>CodePushDeploymentKey</code>条目的值更改为<code>$(CODEPUSH_KEY)</code></p><p><img src="https://docs.microsoft.com/en-us/appcenter/distribution/codepush/images/rn-ios-12.png" alt="React Native 集成 CodePush 指南"></p></li></ol><p>就是这样了，现在当你运行或构建你的App，你的 <code>Staging</code> 包将自动同步你的 <code>Staging</code> 部署，你的 <code>Release</code> 包将自动同步你的 <code>Production</code> 部署。</p><blockquote><p>注意：如果你遇到 <code>ld: library not found for ...</code> 错误信息，请看一下这个 <a href="https://github.com/Microsoft/react-native-code-push/issues/426">issuse</a></p></blockquote><p>此外，如果你想给他们不同的名称和&#x2F;或图标，你可以修改<code>Product Bundle Identifier</code>，<code>Product Name</code>以及<code>Asset Catalog App Icon Set Name</code></p><h2 id="动态部署分配"><a href="#动态部署分配" class="headerlink" title="动态部署分配"></a>动态部署分配</h2><p>上一节说明了如何利用多个<code>CodePush</code>部署，以便在更新发布给用户之前，有效地测试您的更新内容。 但是，由于该工作流静态地将部署分配嵌入到实际二进制文件中，因此<code>临时构建</code>和<code>生产构建</code>只会同步该部署的更新内容。</p><p>在许多情况下，这是足够的，因为您只希望您的团队，客户，利益相关者等与您的预生产版本同步，因此，他们只需要知道如何与该版本同步构建。</p><p>但是，如果你希望能够进行 <code>A / B</code> 测试，或者为某些用户提供应用程序的早期访问权限，那么能够在运行时将特定用户（或受众）动态地置于特定部署中将非常有用。</p><p>为了实现此工作流程，你需要做的就是指定调用该<code>codePush</code>方法时希望当前用户与之同步的部署密钥。指定后，此密钥将覆盖应用程序的 <code>Info.plist</code>（iOS）或 <code>MainActivity.java</code>（Android）文件中提供的“默认”密钥。这允许您生成临时或生产构建，也可以根据需要动态“重定向”。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Imagine that &quot;userProfile&quot; is a prop that this component received</span></span><br><span class="line"><span class="comment">// which includes the deployment key that the current user should use.</span></span><br><span class="line">codePush.<span class="title function_">sync</span>(&#123; <span class="attr">deploymentKey</span>: userProfile.<span class="property">CODEPUSH_KEY</span> &#125;);</span><br></pre></td></tr></table></figure><p>有了这样的变化后，现在只需选择应用程序如何为当前用户配置正确的部署密钥。 在实践中，通常有两种解决方案：</p><ol><li>将更改部署的功能开放给用户。例如，您的设置页面可能会有一个切换按钮以启用“测试版”的访问权限。 如果您不在乎预生产更新的内容被得知，并且您的某些用户可能希望根据自己的意愿选择使用最新（并且可能有错误）的更新（有点像Chrome渠道）。 但是，此解决方案将决策权交给您的用户，这无法帮助您透明地执行 <code>A / B</code> 测试。</li><li>使用额外的元数据注释用户的服务器端配置文件，标明与其同步的部署。 默认情况下，您的应用只能使用二进制嵌入密钥，但在用户通过身份验证后，您的服务器可以选择将其“重定向”到其他部署，这样您就可以根据需要逐步将某些用户或组放置在不同的部署中。您甚至可以选择将服务器响应存储在本地存储中，以使其成为新的默认值。 如何将密钥与用户的配置文件一起存储完全取决于您的身份验证解决方案（例如 <code>Auth0</code>，<code>Firebase</code>，自定义<code>DB</code> + <code>REST API</code>），但这通常非常简单。</li></ol><blockquote><p>注意：如果需要，您还可以实施混合解决方案，允许最终用户在不同部署之间切换，同时还允许您的服务器覆盖该决策。 这样，您就拥有了“部署解决方案”的层次结构，可确保您的应用程序能够自行更新，用户可以通过获得最新内容的访问权限来获得最新体验，但您也有能力根据需要对用户进行 <code>A / B</code> 测试。</p></blockquote><p>由于我们建议将<code>Staging</code>部署用于更新的预发布测试（如上一节中所述），因此使用该部署对用户执行 <code>A / B</code> 测试并不一定有意义，与此相反，你应该允许早期访问（如上面选项1中所述）。因此，我们建议充分利用自定义应用程序部署，以便您可以按用户需求对用户进行细分。例如，您可以创建长期甚至是一次性的部署，向其发布应用程序的变体，然后将某些用户放入其中，以查看其参与度。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">// <span class="comment">#1) Create your new deployment to hold releases of a specific app variant</span></span><br><span class="line">appcenter codepush deployment add -a &lt;ownerName&gt;/&lt;appName&gt; test-variant-one</span><br><span class="line"></span><br><span class="line">// <span class="comment">#2) Target any new releases at that custom deployment</span></span><br><span class="line">appcenter codepush release-react -a &lt;ownerName&gt;/&lt;appName&gt; -d test-variant-one</span><br></pre></td></tr></table></figure><blockquote><p>注意：从一个部署“切换”到另一个部署的用户数，被纳入到部署中的“安装度量”中报告的总用户数。例如，如果您的<code>Production</code> 部署当前报告的用户总数为1，但您将该用户动态切换为 <code>Staging</code> 部署，则 <code>Production</code> 部署将报告 0个总用户，而 <code>Staging</code> 部署将报告1（刚刚切换的用户）。 即使在使用基于运行时的部署重定向解决方案的情况下，这种行为可以让你准确地跟踪您的版本使用情况。</p></blockquote><h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><blockquote><p>源码：<a href="https://github.com/youngjuning/AppCenterCodePushDemo">https://github.com/youngjuning/AppCenterCodePushDemo</a></p></blockquote><h3 id="App-js"><a href="#App-js" class="headerlink" title="App.js"></a>App.js</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span>, &#123;useEffect&#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">View</span>, <span class="title class_">StyleSheet</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;react-native&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> codePush <span class="keyword">from</span> <span class="string">&#x27;react-native-code-push&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">AwesomeButton</span> <span class="keyword">from</span> <span class="string">&#x27;react-native-really-awesome-button&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;codePushSync, checkForUpdate&#125; <span class="keyword">from</span> <span class="string">&#x27;./CodePushUtils&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">App</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">getUpdateMetadata</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> running = <span class="keyword">await</span> codePush.<span class="title function_">getUpdateMetadata</span>(</span><br><span class="line">      codePush.<span class="property">UpdateState</span>.<span class="property">RUNNING</span>,</span><br><span class="line">    );</span><br><span class="line">    <span class="keyword">const</span> pending = <span class="keyword">await</span> codePush.<span class="title function_">getUpdateMetadata</span>(</span><br><span class="line">      codePush.<span class="property">UpdateState</span>.<span class="property">PENDING</span>,</span><br><span class="line">    );</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;[CodePush] running&#x27;</span>, running);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;[CodePush] pending&#x27;</span>, pending);</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">codePushSync</span>();</span><br><span class="line">  &#125;, []);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">View</span> <span class="attr">style</span>=<span class="string">&#123;styles.container&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">AwesomeButton</span> <span class="attr">onPress</span>=<span class="string">&#123;checkForUpdate&#125;</span> <span class="attr">style</span>=<span class="string">&#123;&#123;marginBottom:</span> <span class="attr">10</span>&#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">        Check For Update!</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">AwesomeButton</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">AwesomeButton</span> <span class="attr">onPress</span>=<span class="string">&#123;()</span> =&gt;</span> codePush.clearUpdates()&#125; style=&#123;&#123;marginBottom: 10&#125;&#125;&gt;</span></span><br><span class="line"><span class="language-xml">        Clear Updates!</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">AwesomeButton</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">AwesomeButton</span> <span class="attr">onPress</span>=<span class="string">&#123;getUpdateMetadata&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        getUpdateMetadata!</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">AwesomeButton</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">View</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> styles = <span class="title class_">StyleSheet</span>.<span class="title function_">create</span>(&#123;</span><br><span class="line">  <span class="attr">container</span>: &#123;</span><br><span class="line">    <span class="attr">flex</span>: <span class="number">1</span>,</span><br><span class="line">    <span class="attr">justifyContent</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">    <span class="attr">alignItems</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure><h3 id="CodePushUtils-js"><a href="#CodePushUtils-js" class="headerlink" title="CodePushUtils.js"></a>CodePushUtils.js</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">AppState</span>, <span class="title class_">Platform</span>, <span class="title class_">Alert</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;react-native&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> codePush <span class="keyword">from</span> <span class="string">&#x27;react-native-code-push&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> configReader <span class="keyword">from</span> <span class="string">&#x27;react-native-config-reader&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">CodePushDeploymentKey</span> = &#123;</span><br><span class="line">  <span class="attr">ios</span>: &#123;</span><br><span class="line">    <span class="attr">debug</span>: <span class="string">&#x27;&#x27;</span>,</span><br><span class="line">    <span class="attr">staging</span>: <span class="string">&#x27;944zuIiRSds-ZZY6AQF82aRl0b1vUL_mMxiie&#x27;</span>,</span><br><span class="line">    <span class="attr">release</span>: <span class="string">&#x27;yyJfk2vtpLUUlOCg3FnvCcky9o4U1lEWR1UJV&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">android</span>: &#123;</span><br><span class="line">    <span class="attr">debug</span>: <span class="string">&#x27;&#x27;</span>,</span><br><span class="line">    <span class="attr">releasestaging</span>: <span class="string">&#x27;tOncLvKACzzSkUaML9tCOUfPZxHVnobfaNIUe&#x27;</span>,</span><br><span class="line">    <span class="attr">release</span>: <span class="string">&#x27;Gtc4iXTPn24yu6CBrbl_V2GTy21xtdQyfm6x1&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">getDeploymentKey</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> buildType = configReader.<span class="property">BUILD_TYPE</span>.<span class="title function_">toLowerCase</span>();</span><br><span class="line">  <span class="keyword">const</span> deploymentKey = <span class="title class_">CodePushDeploymentKey</span>[<span class="title class_">Platform</span>.<span class="property">OS</span>][buildType];</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;[CodePushUtils]&#x27;</span>, deploymentKey);</span><br><span class="line">  <span class="keyword">return</span> deploymentKey;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">codePushStatusDidChange</span> = <span class="keyword">async</span> syncStatus =&gt; &#123;</span><br><span class="line">  <span class="keyword">switch</span> (syncStatus) &#123;</span><br><span class="line">    <span class="keyword">case</span> codePush.<span class="property">SyncStatus</span>.<span class="property">CHECKING_FOR_UPDATE</span>:</span><br><span class="line">      <span class="comment">// 0 - 正在查询CodePush服务器以进行更新。</span></span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">info</span>(<span class="string">&#x27;[CodePush] Checking for update.&#x27;</span>);</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> codePush.<span class="property">SyncStatus</span>.<span class="property">AWAITING_USER_ACTION</span>:</span><br><span class="line">      <span class="comment">// 1 - 有可用的更新，并且向最终用户显示了一个确认对话框。（仅在updateDialog使用时适用）</span></span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">info</span>(<span class="string">&#x27;[CodePush] Awaiting user action.&#x27;</span>);</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> codePush.<span class="property">SyncStatus</span>.<span class="property">DOWNLOADING_PACKAGE</span>:</span><br><span class="line">      <span class="comment">// 2 - 正在从CodePush服务器下载可用更新。</span></span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">info</span>(<span class="string">&#x27;[CodePush] Downloading package.&#x27;</span>);</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> codePush.<span class="property">SyncStatus</span>.<span class="property">INSTALLING_UPDATE</span>:</span><br><span class="line">      <span class="comment">// 3 - 已下载一个可用的更新，并将其安装。</span></span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">info</span>(<span class="string">&#x27;[CodePush] Installing update.&#x27;</span>);</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> codePush.<span class="property">SyncStatus</span>.<span class="property">UP_TO_DATE</span>:</span><br><span class="line">      <span class="comment">// 4 - 应用程序已配置的部署完全最新。</span></span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">info</span>(<span class="string">&#x27;[CodePush] App is up to date.&#x27;</span>);</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> codePush.<span class="property">SyncStatus</span>.<span class="property">UPDATE_IGNORED</span>:</span><br><span class="line">      <span class="comment">// 5 该应用程序具有可选更新，最终用户选择忽略该更新。（仅在updateDialog使用时适用）</span></span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">info</span>(<span class="string">&#x27;[CodePush] User cancelled the update.&#x27;</span>);</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> codePush.<span class="property">SyncStatus</span>.<span class="property">UPDATE_INSTALLED</span>:</span><br><span class="line">      <span class="comment">// 6 - 安装了一个可用的更新，它将根据 SyncOptions 中的 InstallMode指定在 syncStatusChangedCallback 函数返回后立即或在下次应用恢复/重新启动时立即运行。</span></span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">info</span>(<span class="string">&#x27;[CodePush] Installed update.&#x27;</span>);</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> codePush.<span class="property">SyncStatus</span>.<span class="property">SYNC_IN_PROGRESS</span>:</span><br><span class="line">      <span class="comment">// 7 - 正在执行的 sync 操作</span></span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">info</span>(<span class="string">&#x27;[CodePush] Sync already in progress.&#x27;</span>);</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> codePush.<span class="property">SyncStatus</span>.<span class="property">UNKNOWN_ERROR</span>:</span><br><span class="line">      <span class="comment">// -1 - 同步操作遇到未知错误。</span></span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">info</span>(<span class="string">&#x27;[CodePush] An unknown error occurred.&#x27;</span>);</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">codePushDownloadDidProgress</span> = progress =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> curPercent = (</span><br><span class="line">    (progress.<span class="property">receivedBytes</span> / progress.<span class="property">totalBytes</span>) *</span><br><span class="line">    <span class="number">100</span></span><br><span class="line">  ).<span class="title function_">toFixed</span>(<span class="number">0</span>);</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;[CodePushUtils] Downloading Progress&#x27;</span>, <span class="string">`<span class="subst">$&#123;curPercent&#125;</span>%`</span>);</span><br><span class="line">  <span class="comment">// console.log(`$&#123;progress.receivedBytes&#125; of $&#123;progress.totalBytes&#125; received.`);</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">syncImmediate</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> deploymentKey = <span class="title function_">getDeploymentKey</span>();</span><br><span class="line">  codePush.<span class="title function_">sync</span>(</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">updateDialog</span>: &#123;</span><br><span class="line">        <span class="comment">// 是否显示更新描述</span></span><br><span class="line">        <span class="attr">appendReleaseDescription</span>: <span class="literal">true</span>,</span><br><span class="line">        <span class="comment">// 更新描述的前缀。 默认为&quot;Description&quot;</span></span><br><span class="line">        <span class="attr">descriptionPrefix</span>: <span class="string">&#x27;\n\n更新内容：\n&#x27;</span>,</span><br><span class="line">        <span class="comment">// 强制更新按钮文字，默认为continue</span></span><br><span class="line">        <span class="attr">mandatoryContinueButtonLabel</span>: <span class="string">&#x27;立即更新&#x27;</span>,</span><br><span class="line">        <span class="comment">// 强制更新时的信息. 默认为&quot;An update is available that must be installed.&quot;</span></span><br><span class="line">        <span class="attr">mandatoryUpdateMessage</span>: <span class="string">&#x27;必须更新后才能使用&#x27;</span>,</span><br><span class="line">        <span class="comment">// 非强制更新时，按钮文字,默认为&quot;ignore&quot;</span></span><br><span class="line">        <span class="attr">optionalIgnoreButtonLabel</span>: <span class="string">&#x27;稍后&#x27;</span>,</span><br><span class="line">        <span class="comment">// 非强制更新时，确认按钮文字. 默认为&quot;Install&quot;</span></span><br><span class="line">        <span class="attr">optionalInstallButtonLabel</span>: <span class="string">&#x27;后台更新&#x27;</span>,</span><br><span class="line">        <span class="comment">// 非强制更新时，检查到更新的消息文本</span></span><br><span class="line">        <span class="attr">optionalUpdateMessage</span>: <span class="string">&#x27;有新版本了，是否更新？&#x27;</span>,</span><br><span class="line">        <span class="comment">// Alert窗口的标题</span></span><br><span class="line">        <span class="attr">title</span>: <span class="string">&#x27;更新&#x27;</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">      deploymentKey,</span><br><span class="line">      <span class="attr">installMode</span>: codePush.<span class="property">InstallMode</span>.<span class="property">IMMEDIATE</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    codePushStatusDidChange,</span><br><span class="line">    codePushDownloadDidProgress,</span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">checkForUpdate</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> deploymentKey = <span class="title function_">getDeploymentKey</span>();</span><br><span class="line">  <span class="keyword">const</span> update = <span class="keyword">await</span> codePush.<span class="title function_">checkForUpdate</span>(deploymentKey);</span><br><span class="line">  <span class="keyword">if</span> (!update) &#123;</span><br><span class="line">    <span class="title class_">Alert</span>.<span class="title function_">alert</span>(<span class="string">&#x27;提示&#x27;</span>, <span class="string">&#x27;已是最新版本&#x27;</span>);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="title function_">syncImmediate</span>();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">codePushSync</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="title class_">AppState</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;change&#x27;</span>, <span class="function"><span class="params">newState</span> =&gt;</span> &#123;</span><br><span class="line">    newState === <span class="string">&#x27;active&#x27;</span> &amp;&amp; <span class="title function_">syncImmediate</span>();</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="Npm-Scripts"><a href="#Npm-Scripts" class="headerlink" title="Npm Scripts"></a>Npm Scripts</h3><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    ...</span><br><span class="line">    <span class="attr">&quot;gradle:clean&quot;</span><span class="punctuation">:</span> <span class="string">&quot;cd android &amp;&amp; ./gradlew clean&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;an:release&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn gradle:clean &amp;&amp; cd android &amp;&amp; ./gradlew app:assembleRelease&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;an:installRelease&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn gradle:clean &amp;&amp; cd android &amp;&amp; ./gradlew app:installRelease&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;an:staging&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn gradle:clean &amp;&amp; cd android &amp;&amp; ./gradlew app:assembleReleaseStaging&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;an:installStaging&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn gradle:clean &amp;&amp; cd android &amp;&amp; ./gradlew app:installReleaseStaging&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;displayKeys&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn disPlayIosKeys &amp;&amp; yarn disPlayAndroidKeys&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;disPlayIosKeys&quot;</span><span class="punctuation">:</span> <span class="string">&quot;appcenter codepush deployment list --app youngjuning/AppCenterCodePushDemo-iOS --displayKeys&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;disPlayAndroidKeys&quot;</span><span class="punctuation">:</span> <span class="string">&quot;appcenter codepush deployment list --app youngjuning/AppCenterCodePushDemo-Android --displayKeys&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;release-react&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn release-react-ios &amp;&amp; yarn release-react-android&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;release-react-ios&quot;</span><span class="punctuation">:</span> <span class="string">&quot;appcenter codepush release-react --app youngjuning/AppCenterCodePushDemo-iOS&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;release-react-android&quot;</span><span class="punctuation">:</span> <span class="string">&quot;appcenter codepush release-react --app youngjuning/AppCenterCodePushDemo-Android&quot;</span></span><br><span class="line">    ...</span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="扩展"><a href="#扩展" class="headerlink" title="扩展"></a>扩展</h2><h3 id="CodePush-什么情况下不会立即重启应用"><a href="#CodePush-什么情况下不会立即重启应用" class="headerlink" title="CodePush 什么情况下不会立即重启应用"></a>CodePush 什么情况下不会立即重启应用</h3><ol><li>自上一次<code>disallowRestart</code>被调用，没有新的更新。</li><li>有更新，但<code>installMode</code>为<code>InstallMode.ON_NEXT_RESTART</code>的情况下。</li><li>有更新，但<code>installMode</code>为<code>InstallMode.ON_NEXT_RESUME</code>，并且程序一直处于前台，并没有从后台切换到前台的情况下。</li><li>自从上次<code>disallowRestart</code>被调用，没有再调用<code>restartApp</code>。</li></ol><h3 id="TypeSctipt"><a href="#TypeSctipt" class="headerlink" title="TypeSctipt"></a>TypeSctipt</h3><p>如果使用 TypeScript，再浏览一次文档的API部分之后，就可以依靠类型系统的提示来工作啦： <a href="https://github.com/microsoft/react-native-code-push/blob/master/typings/react-native-code-push.d.ts">react-native-code-push.d.ts</a></p><h3 id="iOS-添加-BUILD-Type"><a href="#iOS-添加-BUILD-Type" class="headerlink" title="iOS 添加 BUILD_Type"></a>iOS 添加 BUILD_Type</h3><p>在 <code>Info.plist</code> 中添加 <code>BUILD_TYPE</code>，取值为 <code>$(CONFIGURATION)</code></p><h3 id="react-native-bundle"><a href="#react-native-bundle" class="headerlink" title="react-native bundle"></a>react-native bundle</h3><p>生成  <code>bundle</code> 命名：<code>react-native bundle --platform</code> 平台 <code> --entry-file</code>启动文件 <code>--bundle-output</code> 打包js输出文件 <code> --assets-dest</code>  资源输出目录 <code> --dev</code>  是否调试：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ react-native bundle --platform android --entry-file index.js --bundle-output ./bundle/android/main.jsbundle --assets-dest ./bundle/android --dev <span class="literal">false</span></span><br></pre></td></tr></table></figure><h2 id="联系作者"><a href="#联系作者" class="headerlink" title="联系作者"></a>联系作者</h2><table><thead><tr><th align="center">作者微信</th><th align="center">知识星球</th><th align="center">赞赏作者</th></tr></thead><tbody><tr><td align="center"><img src="https://user-gold-cdn.xitu.io/2020/2/24/17074acbb24c7412?w=200&h=200&f=jpeg&s=17183" style="width:200px"/></td><td align="center"><img src="https://user-gold-cdn.xitu.io/2020/2/24/17074acbb26af8e1?w=200&h=200&f=png&s=39093" style="width:200px"/></td><td align="center"><img src="https://user-gold-cdn.xitu.io/2020/2/24/17074acbb338c643?w=698&h=700&f=png&s=315492" style="width:200px"/></td></tr></tbody></table><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://docs.microsoft.com/en-us/appcenter/distribution/codepush/">Appcenter CodePush</a></li><li><a href="https://bre.is/DxWyD6wP">React-Native应用部署-热更新-CodePush最新集成总结</a></li><li><a href="https://www.jianshu.com/p/6a5e00d22723">CodePush热更新详细接入教程</a></li><li><a href="http://techblog.sishuxuefu.com/atricle.html?5beaa7e59f5454007039e01c">react-native热更新</a></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/2167a27028fb/</id>
    <link href="https://blog.zisheng.pro/posts/2167a27028fb/"/>
    <published>2020-02-25T09:08:43.000Z</published>
    <summary>本文分享的是基于微软 AppCenter 的 CodePush 服务实现热更新，这个比较有代表性，也方便各位读者大大实践。当然鉴于国内的网络环境，后期会发布一篇如何基于 code-push-server 实现热更新功能。</summary>
    <title>React Native 集成 CodePush 指南</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/tags/React-Native/"/>
    <category term="metro" scheme="https://blog.zisheng.pro/tags/metro/"/>
    <content>
      <![CDATA[<p>当我们执行 <code>react-native start</code> 命令时，React Native 会启动一个默认端口号为 <code>8081</code>的本地服务，该 <code>8081</code> 的服务就是React Native项目的一个本地 Node 服务器，用于提供 JSBundle 包和一些静态资源。这一切都是 React Native 帮开发者配置好的，一切都是那么简便。</p><p>本文旨在告诉大家现在我们只需要简单地对 Metro 进行配置即可修改默认的版本号。</p><p>我翻阅了一下网上的方案，都太麻烦了，想着这功能官方不该提供可配置的吗？于是翻阅了官方的脚手架工具<a href="https://github.com/react-native-community/cli">@react-native-community&#x2F;cli</a>。打开<a href="https://bre.is/hDtBSjob">loadMetroConfig.ts</a> 文件我找到了以下代码：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">server</span>: &#123;</span><br><span class="line">  <span class="attr">port</span>: <span class="title class_">Number</span>(process.<span class="property">env</span>.<span class="property">RCT_METRO_PORT</span>) || <span class="number">8081</span>,</span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure><p>当我看到 <code>RCT_METRO_PORT</code> 这个属性，顿感这玩意和项目根目录的 <code>metro.config.js</code> 有关系。于是照猫画虎，在这个文件中添加了一下配置：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  ...</span><br><span class="line">  <span class="attr">server</span>: &#123;</span><br><span class="line">    <span class="attr">port</span>: <span class="number">8082</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>抱着试一试的心态，运行了 <code>react-native run-android</code>,运行成功：</p><img src="https://i.loli.net/2020/02/25/Zcs2rIDk5F9hoUS.png" style="zoom:60%;" /><p>知其然，知其所以然，这个为什么可行呢？打开 metro 的官方文档，我找到了下面的内容：</p><img src="https://i.loli.net/2020/02/25/KDgRdMbToZLB1Vs.png" style="zoom:67%;" /><p>现在 metro 已经接手了所有 React Native 的打包任务，后期遇到任何打包相关的问题，都可以先阅读一下 Metro 的文档进行解决。顺便吐槽一下，这么基础的设施，竟然没有中文翻译的文档。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/8c4c7f4e5f09/</id>
    <link href="https://blog.zisheng.pro/posts/8c4c7f4e5f09/"/>
    <published>2020-02-25T08:02:10.000Z</published>
    <summary>本文旨在告诉大家现在我们只需要简单地对 Metro 进行配置即可修改默认的版本号。</summary>
    <title>利用 Metro 配置 React Native 端口</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React-Native/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/tags/React-Native/"/>
    <category term="架构" scheme="https://blog.zisheng.pro/tags/%E6%9E%B6%E6%9E%84/"/>
    <category term="最佳实践" scheme="https://blog.zisheng.pro/tags/%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/"/>
    <content>
      <![CDATA[<h2 id="sigmayun-react-native-template-typescript"><a href="#sigmayun-react-native-template-typescript" class="headerlink" title="@sigmayun&#x2F;react-native-template-typescript"></a>@sigmayun&#x2F;react-native-template-typescript</h2><p><a href="https://github.com/sigmayun/react-native-template-typescript">@sigmayun&#x2F;react-native-template-typescript</a>是基于 <a href="https://github.com/react-native-community/react-native-template-typescript">react-native-community&#x2F;react-native-template-typescript</a> 二次开发的模板脚手架。本文的架构方案融进了该模板。你甚至不需要阅读本文的内容即可使用本文中的最佳实践！！！</p><h2 id="全局变量"><a href="#全局变量" class="headerlink" title="全局变量"></a>全局变量</h2><p>既然都引入 TypeScript 了，就不用再担心给全局对象添加属性不小心污染它了，但是需要我们进行一些配置。</p><p>1、在项目根目录新建 <code>types</code>,并新建 <code>global.d.ts</code> 文件名，添加一下内容：</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">declare</span> <span class="keyword">const</span> <span class="attr">global</span>: &#123;</span><br><span class="line">  <span class="title class_">HermesInternal</span>: <span class="literal">null</span> | &#123;&#125;</span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>2、为了防止和 <code>@types/node</code> 的 <code>Global</code> 声明冲突，需要把 <code>tsconfig.json</code> 中的 <code>skipLibCheck</code> 的值设置为 <code>true</code></p><blockquote><p>该部分示例代码涉及 <code>global.ts</code>、<code>types/global.d.ts</code>、<code>tsconfig.json</code>，请到 <a href="https://github.com/sigmayun/react-native-template-typescript">https://github.com/sigmayun/react-native-template-typescript</a> 查看。</p></blockquote><h2 id="编码规范"><a href="#编码规范" class="headerlink" title="编码规范"></a>编码规范</h2><ul><li><a href="https://github.com/sishuguojixuefu/eslint-config">@sishuguojixuefu&#x2F;eslint-config</a>：大而全的 ESlint 配置，支持 vue、jsx、js、ts、tsx、html 的 ESLint 配置插件，基于 airbnb，支持Prettier</li><li>husky + lint-staged: 禁止 <code>commit</code> 不符合规范的代码</li></ul><blockquote><p>该部分示例代码：<a href="https://bre.is/3GD7cHKn">https://bre.is/3GD7cHKn</a></p></blockquote><h2 id="Npm-Scripts"><a href="#Npm-Scripts" class="headerlink" title="Npm Scripts"></a>Npm Scripts</h2><p>善用 NPM Scripts 能帮助改善工作流程！！！</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;postinstall&quot;</span><span class="punctuation">:</span> <span class="string">&quot;npx jetifier&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;android&quot;</span><span class="punctuation">:</span> <span class="string">&quot;react-native run-android&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;ios&quot;</span><span class="punctuation">:</span> <span class="string">&quot;react-native run-ios&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;start&quot;</span><span class="punctuation">:</span> <span class="string">&quot;react-native start&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;test&quot;</span><span class="punctuation">:</span> <span class="string">&quot;jest&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;lint&quot;</span><span class="punctuation">:</span> <span class="string">&quot;eslint . --ext .js,.jsx,.ts,.tsx&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;pod&quot;</span><span class="punctuation">:</span> <span class="string">&quot;cd ios &amp;&amp; pod install&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;gradle:clean&quot;</span><span class="punctuation">:</span> <span class="string">&quot;cd android &amp;&amp; ./gradlew clean&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;an:release&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn gradle:clean &amp;&amp; cd android &amp;&amp; ./gradlew app:assembleRelease&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;an:installRelease&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn gradle:clean &amp;&amp; cd android &amp;&amp; ./gradlew app:installRelease&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;an:releaseStaging&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn gradle:clean &amp;&amp; cd android &amp;&amp; ./gradlew app:assembleReleaseStaging&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;an:installReleaseStaging&quot;</span><span class="punctuation">:</span> <span class="string">&quot;yarn gradle:clean &amp;&amp; cd android &amp;&amp; ./gradlew app:installReleaseStaging&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;an:genkeypair&quot;</span><span class="punctuation">:</span> <span class="string">&quot;keytool -genkeypair -v -keystore release.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;an:key-debug&quot;</span><span class="punctuation">:</span> <span class="string">&quot;keytool -list -v -keystore ./android/app/debug.keystore&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;an:key-release&quot;</span><span class="punctuation">:</span> <span class="string">&quot;keytool -v -list -keystore ./android/app/release.keystore&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;icon&quot;</span><span class="punctuation">:</span> <span class="string">&quot;npx iconfont-rn&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><blockquote><p>该部分示例代码：<a href="https://bre.is/LC6uu8kc">https://bre.is/LC6uu8kc</a></p></blockquote><h2 id="网络编程"><a href="#网络编程" class="headerlink" title="网络编程"></a>网络编程</h2><ul><li><a href="https://www.kancloud.cn/yunye/axios/234845">axios</a>: Axios 是一个基于 promise 的 HTTP 库，可以用在浏览器和 node.js 中。</li><li><a href="https://www.npmjs.com/package/qs">qs</a>: A querystring parsing and stringifying library with some added security.</li><li><a href="https://www.npmjs.com/package/clean-deep">clean-deep</a>: 从对象中递归删除空对象、空数组、空字符串、<code>null</code>和 <code>value</code>值。不改变原始数据。</li></ul><blockquote><p>该部分示例代码：<a href="https://bre.is/39khhJtn">https://bre.is/39khhJtn</a></p></blockquote><h2 id="路由管理"><a href="#路由管理" class="headerlink" title="路由管理"></a>路由管理</h2><ul><li>React Navigation: 这个模块是官方推荐的导航组件，升级到5之后不仅支持了 <code>hooks</code>，更是支持了动态路由。</li></ul><blockquote><p>该部分示例代码：<a href="https://bre.is/pzA7JArY">https://bre.is/pzA7JArY</a></p><p>相关博客：<a href="https://bre.is/pzA7JArY">https://bre.is/pzA7JArY</a></p></blockquote><h2 id="状态管理"><a href="#状态管理" class="headerlink" title="状态管理"></a>状态管理</h2><ul><li><code>mobx</code> + <code>mobx-react</code>: 相对于 redux 来说更简单易用，还在犹豫的同学，可以读一下有赞的一篇文章 <a href="https://tech.youzan.com/mobx_vs_redux/">我为什么从Redux迁移到了Mobx</a></li></ul><h2 id="本地存储"><a href="#本地存储" class="headerlink" title="本地存储"></a>本地存储</h2><ul><li><a href="https://www.npmjs.com/package/react-native-simple-store">react-native-simple-storage</a>:  AsyncStorage 的一个简单包装器</li><li><a href="https://github.com/pubkey/rxdb">rxdb</a>:  JavaScript 即时数据库，如果有复杂的缓存业务可以考虑使用</li><li><a href="https://github.com/Nozbe/WatermelonDB">watermelonDB</a>: 一个专为 React、React Native 服务的响应式数据库框架</li></ul><h2 id="启动屏"><a href="#启动屏" class="headerlink" title="启动屏"></a>启动屏</h2><ul><li><a href="https://github.com/crazycodeboy/react-native-splash-screen">react-native-splash-screen</a></li></ul><blockquote><p>启动屏的配置涉及到不少原生知识，请阅读 <a href="https://bre.is/CRC2SkqP">https://bre.is/CRC2SkqP</a> 进行配置。</p></blockquote><h2 id="热更新"><a href="#热更新" class="headerlink" title="热更新"></a>热更新</h2><ul><li><a href="https://update.reactnative.cn/home">react-native-pushy</a>: ReactNative中文网推出的代码热更新服务，免费，但是限制多，不推荐使用</li><li><a href="https://github.com/microsoft/react-native-code-push">react-native-code-push</a> + <a href="https://appcenter.ms/">AppCenter</a>：完全免费，国内速度可能慢，适合个人开发者【推荐】</li><li><a href="https://github.com/microsoft/react-native-code-push">react-native-code-push</a> + <a href="https://github.com/lisong/code-push-server">code-push-server</a>：适合公司自建热更新服务器</li></ul><blockquote><p>新版热更新部分我还没写好博客，可以先参考我之前写的： <a href="http://techblog.sishuxuefu.com/atricle.html?5beaa7e59f5454007039e01c%EF%BC%8C%E6%88%91%E4%BC%9A%E5%9C%A8%E7%9C%8B%E5%AE%8C%E6%96%B0%E6%96%87%E6%A1%A3%E4%B9%8B%E5%90%8E%E6%8A%8A%E8%BF%99%E9%83%A8%E5%88%86%E9%85%8D%E7%BD%AE%E4%B9%9F%E5%86%85%E7%BD%AE%E5%88%B0%E8%84%9A%E6%89%8B%E6%9E%B6%E4%B8%AD%EF%BC%8C%E6%AF%95%E7%AB%9F%E8%BF%99%E4%B8%AA%E6%9C%8D%E5%8A%A1%E4%B9%9F%E6%98%AF">http://techblog.sishuxuefu.com/atricle.html?5beaa7e59f5454007039e01c，我会在看完新文档之后把这部分配置也内置到脚手架中，毕竟这个服务也是</a> React Native 的标配。</p></blockquote><h2 id="UI框架"><a href="#UI框架" class="headerlink" title="UI框架"></a>UI框架</h2><ul><li><a href="https://rn.mobile.ant.design/index-cn">@ant-design&#x2F;react-native</a>:  Ant Design 出品的一个基于 React Native 的 UI 组件库</li><li><a href="https://github.com/Meituan-Dianping/beeshell">beeshell</a>: 美团 React Native 组件库</li><li><a href="https://react-native-elements.github.io/react-native-elements/">react-native-elements</a>: 跨平台React Native UI工具包</li><li><a href="https://github.com/wix/react-native-ui-lib">react-native-ui-lib</a>: 没有体验过，看起来很强大</li></ul><blockquote><p>其他组件收藏请查看：<a href="https://bre.is/jWUGvPrK">https://bre.is/jWUGvPrK</a></p></blockquote><h2 id="列表"><a href="#列表" class="headerlink" title="列表"></a>列表</h2><ul><li><a href="https://bre.is/yqJ2EEdL">mobx+react-native-largelist 实现分页功能</a>: 推荐使用 <a href="https://github.com/bolan9999/react-native-largelist">react-native-largelist-v3</a>，支持下拉刷新、上拉加载更多、分组列表、表格和瀑布流</li><li>自行封装 <code>FlatList</code>、<code>SectionList</code>: 并不复杂，建议还是要回手写的，react-native-largelist-v3 不一定能满足所有场景</li></ul><h2 id="图标"><a href="#图标" class="headerlink" title="图标"></a>图标</h2><ul><li><a href="https://www.npmjs.com/package/react-native-iconfont-cli">react-native-iconfont-cli</a>: 【推荐】用纯JS把图标转换成RN组件，不依赖字体，支持多色彩，支持热更新</li><li><a href="https://bre.is/dfCDM2Kw">@ant-design&#x2F;icons-react-native</a>: Ant Design Icons for React Native</li><li><a href="https://github.com/oblador/react-native-vector-icons">react-native-vector-icons</a>: Perfect for buttons, logos and nav&#x2F;tab bars. Easy to extend, style and integrate into your project.</li></ul><h2 id="字体"><a href="#字体" class="headerlink" title="字体"></a>字体</h2><ul><li><a href="https://github.com/react-native-training/react-native-fonts">react-native-fonts</a>: 在React Native项目中开箱即用的可用字体，这个库只是统计了一下 React Native 中可用的字体而已</li><li><a href="https://juejin.im/entry/59c74c91f265da065c5e9282">React Native 配置自定义字体</a></li><li><a href="https://bre.is/N2NqkX4N">React Native Custom Fonts</a></li><li><a href="https://github.com/heyman333/react-native-responsive-fontSize">react-native-responsive-fontSize</a>: 响应式fontSize基于React Native中设备的屏幕尺寸</li><li><a href="https://bre.is/WTxnrfPV">React Native 配置字体大小不随字体设置变化</a></li><li><a href="https://juejin.im/post/5ce66c26e51d4555fd20a2a0">React Native字体问题解决方案指北</a></li><li><a href="https://www.npmjs.com/package/react-native-responsive-fontsize">react-native-responsive-fontsize</a></li></ul><h2 id="工具"><a href="#工具" class="headerlink" title="工具"></a>工具</h2><ul><li><a href="https://ethercreative.github.io/react-native-shadow-generator/">react-native-shadow-generator</a>: 在线生成 React Native 阴影代码</li><li><a href="https://github.com/JonnyBurger/npx-visualize-bundle">visualize-bundle</a>: <code>npx visualize-bundle</code> 允许你一键检查你的 React Native bundle 包 并且找到大的依赖</li><li><a href="https://icon.wuruihong.com/">图标工厂</a>: 一键生成所有尺寸的应用图标&#x2F;启动图</li></ul><h2 id="React-Hooks"><a href="#React-Hooks" class="headerlink" title="React Hooks"></a>React Hooks</h2><p>这个新语法是2019年的明星，React Navigation、Mobx这些常用库都内置支持了，还没有学习的同学快去补课吧！！！</p><h3 id="推荐阅读"><a href="#推荐阅读" class="headerlink" title="推荐阅读"></a>推荐阅读</h3><blockquote><p>建议先读官方文档！！！</p></blockquote><ul><li><a href="https://zh-hans.reactjs.org/docs/hooks-intro.html">官方文档</a></li><li><a href="https://juejin.im/post/5be3ea136fb9a049f9121014">30分钟精通React Hooks</a></li><li><a href="https://juejin.im/post/5dbbdbd5f265da4d4b5fe57d">React Hooks 详解 【近 1W 字】+ 项目实战</a></li><li><a href="https://juejin.im/post/5d985deae51d4577f9285c2f">呕心沥血，一文看懂 react hooks</a></li><li><a href="https://zhuanlan.zhihu.com/p/86211675">React Hooks 带来的困扰与思考</a></li></ul><h3 id="如何兼容类组件"><a href="#如何兼容类组件" class="headerlink" title="如何兼容类组件"></a>如何兼容类组件</h3><p>考虑到对于不适应 Hooks 的但是业务又很紧急的场景，我们可以在类组件之上封装一层来支持 React Navigation 的 Hooks 组件，之所以这么做，起因是因为 React Navigation 5 中我们只能通过 <code>useHeaderHeight()</code> 方法获取标题栏高度。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Albums</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">ScrollView</span> <span class="attr">ref</span>=<span class="string">&#123;this.props.scrollRef&#125;</span>&gt;</span><span class="tag">&lt;<span class="name">Text</span>&gt;</span>&#123;this.props.headerHeight&#125;<span class="tag">&lt;/<span class="name">Text</span>&gt;</span><span class="tag">&lt;/<span class="name">ScrollView</span>&gt;</span></span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 封装并导出</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span>(<span class="params">props</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> ref = <span class="title class_">React</span>.<span class="title function_">useRef</span>(<span class="literal">null</span>);</span><br><span class="line">  <span class="keyword">const</span> headerHeight = <span class="title function_">useHeaderHeight</span>();</span><br><span class="line">  <span class="title function_">useScrollToTop</span>(ref);</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">Albums</span> &#123;<span class="attr">...props</span>&#125; <span class="attr">scrollRef</span>=<span class="string">&#123;ref&#125;</span> <span class="attr">headerHeight</span>=<span class="string">&#123;headerHeight&#125;/</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Metro"><a href="#Metro" class="headerlink" title="Metro"></a>Metro</h2><ul><li><a href="https://juejin.im/post/5e54d910f265da571671090d">利用Metro配置React Native端口</a></li></ul><h2 id="第三放服务"><a href="#第三放服务" class="headerlink" title="第三放服务"></a>第三放服务</h2><blockquote><p>这里尽量为大家推荐了免费的服务，当然选择自行搭建服务也是一个很棒的选择！！</p></blockquote><h3 id="Sentry"><a href="#Sentry" class="headerlink" title="Sentry"></a><a href="https://sentry.io/welcome/">Sentry</a></h3><blockquote><p>Sentry 胜在可以把服务搭建到自己的服务器上，并且官方提供了<a href="https://github.com/getsentry/sentry-react-native">sentry-react-native</a>插件来帮助 React Native 工程师快速集成。如果有条件，选择 Sentry 恐怕是目前行业的最佳选择。</p></blockquote><p>Sentry提供自托管和基于云的错误监控，可帮助所有软件团队实时发现分类和确定错误的优先级。已经有超过五万家公司的100万名开发人员使用，Sentry可以更快地提供更好的软件。你不加入他们吗？</p><h3 id="腾讯-Bugly"><a href="#腾讯-Bugly" class="headerlink" title="腾讯 Bugly"></a><a href="https://bugly.qq.com/v2/">腾讯 Bugly</a></h3><p>腾讯Bugly，为移动开发者提供专业的异常上报和运营统计，帮助开发者快速发现并解决异常，同时掌握产品运营动态，及时跟进用户反馈。免费提供异常上报、运营统计、应用升级</p><h3 id="推送"><a href="#推送" class="headerlink" title="推送"></a>推送</h3><h4 id="信鸽-腾讯移动推送"><a href="#信鸽-腾讯移动推送" class="headerlink" title="信鸽 | 腾讯移动推送"></a><a href="https://xg.qq.com/">信鸽 | 腾讯移动推送</a></h4><p>为开发者提供免费、快速、简单的推送服务。QQ登录即可快速注册，为APP接入SDK后马上获得无限量应用推送能力，<br>有效提升用户留存率、活跃度，开发者的不二选择！</p><h4 id="小米消息推送服务"><a href="#小米消息推送服务" class="headerlink" title="小米消息推送服务"></a><a href="https://dev.mi.com/console/appservice/push.html">小米消息推送服务</a></h4><ul><li>MIUI上系统级通道</li><li>iOS&#x2F;Android全平台支持</li><li>免费 稳定 安全 高效</li></ul><h4 id="极光推送"><a href="#极光推送" class="headerlink" title="极光推送"></a><a href="https://www.jiguang.cn/push">极光推送</a></h4><p>极光推送服务，可以免费使用，但限制较多。官方SDK, React Native 集成容易</p><h4 id="U-Push"><a href="#U-Push" class="headerlink" title="U-Push"></a><a href="https://www.umeng.com/push">U-Push</a></h4><p>和极光属于一类，限制级免费，友盟的产品胜在生态，公司产品考虑，后期用户上来之后方便扩展。</p><h3 id="社会化分享"><a href="#社会化分享" class="headerlink" title="社会化分享"></a>社会化分享</h3><h4 id="ShareSDK"><a href="#ShareSDK" class="headerlink" title="ShareSDK"></a><a href="http://www.mob.com/mobService/sharesdk">ShareSDK</a></h4><p>为开发者提供40+主流平台的分享与授权等社会化功能，效果稳定，完整清晰统计分享数据</p><ul><li>一键分享</li><li>第三方登录</li><li>闭环分享</li><li>短链转换</li><li>数据统计</li><li>新浪微博独家LinkCard</li></ul><h4 id="jshare"><a href="#jshare" class="headerlink" title="jshare"></a><a href="https://github.com/jpush/jshare-react-native">jshare</a></h4><ul><li>一键分享</li><li>第三放登录</li><li>社会化统计</li><li>官方 React Native SDK 集成方便</li></ul><h3 id="U-Share"><a href="#U-Share" class="headerlink" title="U-Share"></a><a href="https://bre.is/LM3eaCsL">U-Share</a></h3><ul><li>全面覆盖国内外社交平台</li><li>集成成本低、速度快</li><li>自由定制分享界面</li><li>权威、实时的大数据分析</li></ul><h3 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h3><ul><li><a href="https://github.com/yorkie/react-native-wechat">react-native-wechat</a>: 🚀 <strong>WeChat login</strong>, <strong>share</strong>, <strong>favorite</strong> and <strong>payment</strong> for React-Native on iOS and Android platforms</li><li><a href="https://github.com/0x5e/react-native-alipay">@0x5e&#x2F;react-native-alipay</a>: Alipay SDK for React Native. Support mobile webpage url payment. Support RN &gt;&#x3D; 0.47.</li></ul><h2 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h2><blockquote><p>这部分作者的理解仅限于 Jest，有大佬可以联系我补充！！！</p></blockquote><ul><li><a href="https://jestjs.io/zh-Hans/">Jest</a>: Jest是一个令人愉快的 JavaScript 测试框架，专注于简洁明快。React Native 已经集成好了，直接使用即可。</li></ul><h2 id="自动化运维"><a href="#自动化运维" class="headerlink" title="自动化运维"></a>自动化运维</h2><blockquote><p>这部分作者接触不深，只知道 Jekens + GitLab 的方案</p></blockquote><h3 id="shell-文件的坑"><a href="#shell-文件的坑" class="headerlink" title="shell 文件的坑"></a>shell 文件的坑</h3><p>为了安全性 shell 文件默认都是不可执行的，当然也包括 <code>android/gradlew</code> 这个用来打包的脚本文件，这会给持续集成带来麻烦：运维同学默认是执行不了我们的打包命令的。解决办法很简单：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git update-index --add --<span class="built_in">chmod</span>=+x android/gradlew</span></span><br></pre></td></tr></table></figure><h3 id="动态设置-package-json-的版本"><a href="#动态设置-package-json-的版本" class="headerlink" title="动态设置 package.json 的版本"></a>动态设置 package.json 的版本</h3><p>set-version.sh:</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># current_git_branch_latest_id=`git rev-parse HEAD`</span></span><br><span class="line">current_git_branch_latest_short_id=`git rev-parse --short HEAD`</span><br><span class="line">current_os=`<span class="built_in">uname</span> -s`</span><br><span class="line"></span><br><span class="line"><span class="comment"># echo current git branch latest commit id=$current_git_branch_latest_id</span></span><br><span class="line"><span class="built_in">echo</span> current git branch latest commit short <span class="built_in">id</span>=<span class="variable">$current_git_branch_latest_short_id</span></span><br><span class="line"><span class="built_in">echo</span> current os=<span class="variable">$current_os</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> [ <span class="string">&quot;<span class="variable">$current_os</span>&quot;</span> == <span class="string">&quot;Darwin&quot;</span> ]</span><br><span class="line">  <span class="keyword">then</span></span><br><span class="line">  sed -i <span class="string">&#x27;&#x27;</span> <span class="string">&#x27;s/&quot;version&quot;.*/&quot;version&quot;: &quot;1.0.0-&#x27;</span><span class="variable">$current_git_branch_latest_short_id</span><span class="string">&#x27;&quot;,/g&#x27;</span> package.json</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">  <span class="built_in">echo</span> windows</span><br><span class="line">  sed -i <span class="string">&#x27;s/&quot;version&quot;.*/&quot;version&quot;: &quot;&#x27;</span><span class="variable">$current_git_branch_latest_short_id</span><span class="string">&#x27;&quot;,/g&#x27;</span> package.json</span><br><span class="line"><span class="keyword">fi</span></span><br></pre></td></tr></table></figure><p>package.json:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="comment">// 依赖 husky</span></span><br><span class="line">  <span class="attr">&quot;husky&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;hooks&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;pre-commit&quot;</span><span class="punctuation">:</span> <span class="string">&quot;lint-staged&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;post-commit&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="string">&quot;./scripts/set-version.sh&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;git add .&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;git commit -m bump version&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;git push&quot;</span></span><br><span class="line">      <span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="官方组件缺陷处理"><a href="#官方组件缺陷处理" class="headerlink" title="官方组件缺陷处理"></a>官方组件缺陷处理</h2><ul><li><a href="https://github.com/youngjuning/blog/issues/7">React Native 解决Image 圆角在安卓上面没效果</a></li><li><a href="https://github.com/youngjuning/blog/issues/6">React Native 处理Android系统上文字偏下的问题</a></li><li><a href="https://github.com/youngjuning/blog/issues/5">React Native 自定义 TextInput 高度的问题</a></li><li><a href="https://github.com/youngjuning/blog/issues/32">[Android] Using TextInput inside ViewPagerAndroid causes context menu (copy&#x2F;paste) in some cases to not display</a></li></ul><h2 id="VsCode-插件推荐"><a href="#VsCode-插件推荐" class="headerlink" title="VsCode 插件推荐"></a>VsCode 插件推荐</h2><ul><li>React Native TypeScript Snippets: 本人开发，主要服务于本文的架构，帮助开发者快速搭建页面和组件，凡是重复的工作都可以靠工具来解决，一个不够，就来俩！！！</li><li>ES7 React&#x2F;Redux&#x2F;GraphQL&#x2F;React-Native Snippets</li></ul><h2 id="性能优化"><a href="#性能优化" class="headerlink" title="性能优化"></a>性能优化</h2><h3 id="自动-remove-console-语句"><a href="#自动-remove-console-语句" class="headerlink" title="自动 remove console 语句"></a>自动 remove console 语句</h3><p>准确地说，是在正式环境下删除 <code>console</code> 语句，配置方式请参考 <a href="https://bre.is/2CTUZyUj">自动 remove console 语句</a></p><h3 id="推荐阅读-1"><a href="#推荐阅读-1" class="headerlink" title="推荐阅读"></a>推荐阅读</h3><ul><li><a href="https://juejin.im/post/5e1676e16fb9a04847095b12">React Native 性能优化指南【全网最全，值得收藏】</a></li></ul><h2 id="应用内测"><a href="#应用内测" class="headerlink" title="应用内测"></a>应用内测</h2><ul><li><a href="https://fir.im/">fir</a>: 免费应用内测托管平台</li><li><a href="https://www.pgyer.com/">蒲公英</a>: 免费的苹果ios应用app内测分发托管、android安卓app内测分发托管、ios企业签名、ios专属签名、ios超级签名、ios企业账号</li></ul><h2 id="安卓必知必会"><a href="#安卓必知必会" class="headerlink" title="安卓必知必会"></a>安卓必知必会</h2><h3 id="配置应用名"><a href="#配置应用名" class="headerlink" title="配置应用名"></a>配置应用名</h3><p>很简单,我们直接打开 <code>android/app/src/main/res/values/strings.xml</code>，即可看到配置中的 <code>app_name</code>，修改为你想要的即可。</p><blockquote><p>你可以在初始化项目的时候指定应用的名字，像这样：<code>npx react-native init MyApp --title 掘金</code></p></blockquote><h3 id="配置图标"><a href="#配置图标" class="headerlink" title="配置图标"></a>配置图标</h3><p>1、使用<a href="https://icon.wuruihong.com/">图标工厂</a>、<a href="https://www.npmjs.com/package/react-native-svg-app-icon">react-native-svg-app-icon</a> 或者让设计师给图片</p><p>2、在 <code>android\app\src\main\res\mipmap-xxxxxx</code> 中直接覆盖图标就可以，注意图标的大小。</p><h3 id="打包-APK"><a href="#打包-APK" class="headerlink" title="打包 APK"></a>打包 APK</h3><p>1、在项目根目录执行 <code>keytool -genkeypair -v -keystore release.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000</code> 生成密钥文件 <code>release.keystore</code></p><p>2、把 <code>release.keystore</code> 文件放到你工程中的 <code>android/app</code> 文件夹下。</p><p>3、配置 <code>android/app/build.gradle</code></p><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">android&#123;</span><br><span class="line">    ...</span><br><span class="line">    signingConfigs &#123;</span><br><span class="line">        release &#123;</span><br><span class="line">            storeFile file(<span class="string">&quot;release.keystore&quot;</span>)</span><br><span class="line">            storePassword <span class="string">&quot;****&quot;</span></span><br><span class="line">            keyAlias <span class="string">&quot;my-key-alias&quot;</span></span><br><span class="line">            keyPassword <span class="string">&quot;****&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    ...</span><br><span class="line">    buildTypes &#123;</span><br><span class="line">        release &#123;</span><br><span class="line">            ...</span><br><span class="line">            signingConfig signingConfigs.release</span><br><span class="line">            ...</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="打包优化"><a href="#打包优化" class="headerlink" title="打包优化"></a>打包优化</h3><h4 id="去除无用的语言资源"><a href="#去除无用的语言资源" class="headerlink" title="去除无用的语言资源"></a>去除无用的语言资源</h4><p>通过配置 <code>android/defaultConfig/resConfigs</code> 可以选择只打包哪几种语言，进而去掉各种 <code>aar</code> 包中全世界的语言，尤其是 <code>support</code> 包中的。</p><p>选择保留什么语言要根据产品的用户和市场来定，如果只选择默认英语和中文语言，配置如下：</p><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">defaultConfig &#123;</span><br><span class="line">resConfigs <span class="string">&quot;en&quot;</span>,<span class="string">&quot;zh&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="配置-PackagingOptions"><a href="#配置-PackagingOptions" class="headerlink" title="配置 PackagingOptions"></a>配置 PackagingOptions</h4><p>打开 <code>android/app/build.gradle</code> 文件，添加如下配置：</p><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">packagingOptions &#123;</span><br><span class="line">    exclude <span class="string">&#x27;META-INF/android_release.kotlin_module&#x27;</span></span><br><span class="line">    exclude <span class="string">&#x27;META-INF/DEPENDENCIES&#x27;</span></span><br><span class="line">    exclude <span class="string">&#x27;META-INF/LICENSE&#x27;</span></span><br><span class="line">    exclude <span class="string">&#x27;META-INF/LICENSE.txt&#x27;</span></span><br><span class="line">    exclude <span class="string">&#x27;META-INF/license.txt&#x27;</span></span><br><span class="line">    exclude <span class="string">&#x27;META-INF/NOTICE&#x27;</span></span><br><span class="line">    exclude <span class="string">&#x27;META-INF/NOTICE.txt&#x27;</span></span><br><span class="line">    exclude <span class="string">&#x27;META-INF/notice.txt&#x27;</span></span><br><span class="line">    exclude <span class="string">&#x27;META-INF/ASL2.0&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><code>pickFirsts</code>: 当出现重复文件，会使用第一个匹配的文件打包进入 apk</li><li><code>merges</code>: 当出现重复文件，合并重复的文件打入 apk</li><li><code>excludes</code>: 打包的时候排除匹配的文件</li></ul><h4 id="配置-splits"><a href="#配置-splits" class="headerlink" title="配置 splits"></a>配置 splits</h4><blockquote><p>查看手机 CPU 信息：<code>adb shell</code> -&gt; <code>cd /proc</code> -&gt; <code>cat cpuinfo</code></p></blockquote><p>默认情况下，生成的 <code>APK</code> 会同时包含针对于 <code>x86</code> 和 <code>ARMv7a</code> 两种 <code>CPU</code> 架构的原生代码。这样可以让我们更方便的向其他人分享这个 <code>APK</code>，因为它几乎可以运行在所有的 Android 设备上。但是，这会导致所有设备上都有一些根本不会运行的代码，白白占据了空间。目前安卓设备绝大多数是 <code>ARM</code> 架构，因此对于大部分应用来说可以考虑去掉 <code>x86</code> 架构的支持。</p><p>你可以在 <code>android/app/build.gradle</code> 中修改如下代码：</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="deletion">- include &quot;armeabi-v7a&quot;, &quot;x86&quot;, &quot;arm64-v8a&quot;, &quot;x86_64&quot;</span></span><br><span class="line"><span class="addition">+ include &quot;armeabi-v7a&quot;, &quot;arm64-v8a&quot;</span></span><br><span class="line"><span class="deletion">- def versionCodes = [&quot;armeabi-v7a&quot;:1, &quot;x86&quot;:2, &quot;arm64-v8a&quot;: 3, &quot;x86_64&quot;: 4]</span></span><br><span class="line"><span class="addition">+ def versionCodes = [&quot;armeabi-v7a&quot;:1, &quot;arm64-v8a&quot;: 2]</span></span><br></pre></td></tr></table></figure><h3 id="gradle-编译速度优化配置"><a href="#gradle-编译速度优化配置" class="headerlink" title="gradle 编译速度优化配置"></a>gradle 编译速度优化配置</h3><p>在 <code>android\gradle.properties</code> 中加入以下配置：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"># 让gradle使用单独的守护进程</span><br><span class="line">org.gradle.daemon=true</span><br><span class="line"># 让gradle并行编译</span><br><span class="line">org.gradle.parallel=true</span><br><span class="line"># 让gradle在需要的时候才配置</span><br><span class="line">org.gradle.configureondemand=true</span><br><span class="line"># 增加gradle运行的java虚拟机的大小</span><br><span class="line">org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=4096m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8</span><br></pre></td></tr></table></figure><h3 id="BuildConfig"><a href="#BuildConfig" class="headerlink" title="BuildConfig"></a>BuildConfig</h3><blockquote><p>在 react-native 中，我们可以借助 react-native-config-reader 来方便地读取这些属性</p></blockquote><p><code>BuildConfig</code> 是程序编译后，根据 <code>buildType</code> 生成在 <code>app\build\generated\source\buildConfig\debug(release)\</code> 包名下的一个 java 文件。默认有一下属性：</p><ul><li><code>DEBUG</code>：是否是调试版本</li><li><code>APPLICATION_ID</code>：当前应用的包名</li><li><code>FLAVOR</code>：产品（渠道包的名称）</li><li><code>BUILD_TYPE</code>：当前的编译类型(<code>release</code>&#x2F;<code>debug</code>)</li><li><code>VERSION_CODE</code>：版本号(数字)</li><li><code>VERSION_NAME</code>：版本号</li></ul><h4 id="自定义-BuildConfig"><a href="#自定义-BuildConfig" class="headerlink" title="自定义 BuildConfig"></a>自定义 BuildConfig</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">defaultConfig &#123;</span><br><span class="line">  <span class="comment">// 三个参数: 1.要定义的常量的类型 2.该常量的命名 3.该常量的值</span></span><br><span class="line">  <span class="comment">// APP_NAME，对应 ios 的 CFBundleDisplayName</span></span><br><span class="line">  buildConfigField <span class="string">&quot;String&quot;</span>, <span class="string">&quot;APP_NAME&quot;</span>, <span class="string">&#x27;&quot;我是谁&quot;&#x27;</span></span><br><span class="line">  <span class="comment">// BUILD_TIME</span></span><br><span class="line">  buildConfigField <span class="string">&quot;String&quot;</span>, <span class="string">&quot;BUILD_TIME&quot;</span>, <span class="string">&#x27;&quot;&#x27;</span> + <span class="keyword">new</span> <span class="title class_">Date</span>().format(<span class="string">&quot;yyyy-MM-dd HH:mm:ss&quot;</span>, TimeZone.getTimeZone(<span class="string">&quot;Asia/Shanghai&quot;</span>)) + <span class="string">&#x27;&quot;&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="在子模块中取主项目的-BuildConfig"><a href="#在子模块中取主项目的-BuildConfig" class="headerlink" title="在子模块中取主项目的 BuildConfig"></a>在子模块中取主项目的 BuildConfig</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> Object <span class="title function_">getBuildConfigValue</span><span class="params">(Context context, String fieldName)</span> &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    Class&lt;?&gt; clazz = Class.forName(context.getPackageName() + <span class="string">&quot;.BuildConfig&quot;</span>);</span><br><span class="line">    <span class="type">Field</span> <span class="variable">field</span> <span class="operator">=</span> clazz.getField(fieldName);</span><br><span class="line">    <span class="keyword">return</span> field.get(<span class="literal">null</span>);</span><br><span class="line">  &#125; <span class="keyword">catch</span> (ClassNotFoundException e) &#123;</span><br><span class="line">    e.printStackTrace();</span><br><span class="line">  &#125; <span class="keyword">catch</span> (NoSuchFieldException e) &#123;</span><br><span class="line">    e.printStackTrace();</span><br><span class="line">  &#125; <span class="keyword">catch</span> (IllegalAccessException e) &#123;</span><br><span class="line">    e.printStackTrace();</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br><span class="line">...</span><br><span class="line"><span class="type">String</span> <span class="variable">versionName</span> <span class="operator">=</span> (String)getBuildConfigValue(activity, <span class="string">&quot;VERSION_NAME&quot;</span>))</span><br></pre></td></tr></table></figure><h3 id="解决在-Android-P-上的提醒弹窗-（Detected-problems-with-API-compatibility-visit-g-co-dev-appcompat-for-more-info"><a href="#解决在-Android-P-上的提醒弹窗-（Detected-problems-with-API-compatibility-visit-g-co-dev-appcompat-for-more-info" class="headerlink" title="解决在 Android P 上的提醒弹窗 （Detected problems with API compatibility(visit g.co&#x2F;dev&#x2F;appcompat for more info)"></a>解决在 Android P 上的提醒弹窗 （Detected problems with API compatibility(visit g.co&#x2F;dev&#x2F;appcompat for more info)</h3><p>在 <code>MainActivity.java</code> 中添加 closeAndroidPDialog 方法并在 <code>onCreate</code> 方法中调用</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> android.os.Bundle;</span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.Constructor;</span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.Field;</span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.Method;</span><br><span class="line">...</span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">onCreate</span><span class="params">(Bundle savedInstanceState)</span> &#123;</span><br><span class="line">  closeAndroidPDialog(); <span class="comment">// here</span></span><br><span class="line">  <span class="built_in">super</span>.onCreate(savedInstanceState);</span><br><span class="line">&#125;</span><br><span class="line">...</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">closeAndroidPDialog</span><span class="params">()</span>&#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="type">Class</span> <span class="variable">aClass</span> <span class="operator">=</span> Class.forName(<span class="string">&quot;android.content.pm.PackageParser$Package&quot;</span>);</span><br><span class="line">    <span class="type">Constructor</span> <span class="variable">declaredConstructor</span> <span class="operator">=</span> aClass.getDeclaredConstructor(String.class);</span><br><span class="line">    declaredConstructor.setAccessible(<span class="literal">true</span>);</span><br><span class="line">  &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">    e.printStackTrace();</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="type">Class</span> <span class="variable">cls</span> <span class="operator">=</span> Class.forName(<span class="string">&quot;android.app.ActivityThread&quot;</span>);</span><br><span class="line">    <span class="type">Method</span> <span class="variable">declaredMethod</span> <span class="operator">=</span> cls.getDeclaredMethod(<span class="string">&quot;currentActivityThread&quot;</span>);</span><br><span class="line">    declaredMethod.setAccessible(<span class="literal">true</span>);</span><br><span class="line">    <span class="type">Object</span> <span class="variable">activityThread</span> <span class="operator">=</span> declaredMethod.invoke(<span class="literal">null</span>);</span><br><span class="line">    <span class="type">Field</span> <span class="variable">mHiddenApiWarningShown</span> <span class="operator">=</span> cls.getDeclaredField(<span class="string">&quot;mHiddenApiWarningShown&quot;</span>);</span><br><span class="line">    mHiddenApiWarningShown.setAccessible(<span class="literal">true</span>);</span><br><span class="line">    mHiddenApiWarningShown.setBoolean(activityThread, <span class="literal">true</span>);</span><br><span class="line">  &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">    e.printStackTrace();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="自定义安卓打包的后缀"><a href="#自定义安卓打包的后缀" class="headerlink" title="自定义安卓打包的后缀"></a>自定义安卓打包的后缀</h3><p>配置 <code>android/app/build.gradle</code>:</p><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line"><span class="keyword">def</span> releaseTime() &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> Date().format(<span class="string">&quot;yyyyMMdd-HHmmss&quot;</span>, TimeZone.getTimeZone(<span class="string">&quot;GMT+08:00&quot;</span>))</span><br><span class="line">&#125;</span><br><span class="line">...</span><br><span class="line"><span class="attr">android:</span> &#123;</span><br><span class="line">    applicationVariants.all &#123; variant -&gt;</span><br><span class="line">        ...</span><br><span class="line">    variant.outputs.all &#123;</span><br><span class="line">            <span class="comment">// the apk name is e.g. galaxy_v1.0.1_2018-11-1_debug.apk</span></span><br><span class="line">           outputFileName = <span class="string">&quot;galaxy_v$&#123;defaultConfig.versionName&#125;_$&#123;releaseTime()&#125;_$&#123;variant.buildType.name&#125;.apk&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="常见报错及解决办法"><a href="#常见报错及解决办法" class="headerlink" title="常见报错及解决办法"></a>常见报错及解决办法</h3><h4 id="Failed-to-read-PNG-signature-file-does-not-start-with-PNG-signature"><a href="#Failed-to-read-PNG-signature-file-does-not-start-with-PNG-signature" class="headerlink" title="Failed to read PNG signature: file does not start with PNG signature"></a>Failed to read PNG signature: file does not start with PNG signature</h4><p>有时从网上下载的 Demo 资源文件不规范，会出现直接将 jpg 文件改为 png 后缀名的情况，gradle 打包检查时报错编译通不过的。我们通过 <code>aaptOptions.cruncherEnabled=false</code> 来禁止 Gradle 检查 png 的合法性：</p><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">android &#123;</span><br><span class="line">  aaptOptions &#123;</span><br><span class="line">    cruncherEnabled=<span class="literal">false</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="com-android-build-api-transform-TransformException"><a href="#com-android-build-api-transform-TransformException" class="headerlink" title="com.android.build.api.transform.TransformException"></a><a href="http://t.cn/EZcTDtV">com.android.build.api.transform.TransformException</a></h4><p>在 <code>android\gradle.properties</code> 中加入以下配置：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dexOptions.javaMaxHeapSize = 2g</span><br></pre></td></tr></table></figure><h4 id="The-number-of-method-references-in-a-dex-file-cannot-exceed-64K"><a href="#The-number-of-method-references-in-a-dex-file-cannot-exceed-64K" class="headerlink" title="The number of method references in a .dex file cannot exceed 64K."></a>The number of method references in a .dex file cannot exceed 64K.</h4><p>随着 Android 平台的持续成长，Android 应用的大小也在增加。当您的应用及其引用的库达到特定大小时，您会遇到构建错误，指明您的应用已达到 Android 应用构建架构的极限。</p><p>解决办法是配置您的应用进行 <code>Dalvik</code> 可执行文件分包，在 <code>android/app/build.gradle</code> 中做下面的配置：</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">defaultConfig &#123;</span><br><span class="line"><span class="addition">+    multiDexEnabled true</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="ios必知必会"><a href="#ios必知必会" class="headerlink" title="ios必知必会"></a>ios必知必会</h2><blockquote><p>这部分目前作者接触不深，有大佬可以联系我补充！！！</p></blockquote><h3 id="镜像"><a href="#镜像" class="headerlink" title="镜像"></a>镜像</h3><blockquote><p><code>pod install</code> 会从 GitHub 拉代码，所以在国内会很慢。年轻人要爱国，自觉不翻墙！！！</p></blockquote><ul><li><a href="https://github.com/youngjuning/blog/issues/10">Homebrew 镜像使用帮助</a></li><li><a href="https://mirror.tuna.tsinghua.edu.cn/help/CocoaPods/">CocoaPods 镜像使用帮助</a></li></ul><h3 id="配置应用名-1"><a href="#配置应用名-1" class="headerlink" title="配置应用名"></a>配置应用名</h3><ol><li>选中工程名称</li><li>找到右侧菜单 <code>Info</code> 选项</li><li>添加 <code>Bundle display name</code> 并把 <code>value</code> 设置成 “应用名称”</li><li>最后重新再 <code>Run</code>一次，即可看到最新的效果</li></ol><blockquote><p>你可以在初始化项目的时候指定应用的名字，像这样：<code>npx react-native init MyApp --title 掘金</code></p></blockquote><h3 id="配置图标-1"><a href="#配置图标-1" class="headerlink" title="配置图标"></a>配置图标</h3><p>1、使用<a href="https://icon.wuruihong.com/">图标工厂</a>、<a href="https://www.npmjs.com/package/react-native-svg-app-icon">react-native-svg-app-icon</a> 或者让设计师给图片<br>2、把准备好的图标拖到图中箭头指向的位置：</p><img src="https://i.loli.net/2020/02/21/Sy3OjWBKbMdYgoH.png" style="zoom:36%;" /><h3 id="获取BUILD-TYPE"><a href="#获取BUILD-TYPE" class="headerlink" title="获取BUILD_TYPE"></a>获取BUILD_TYPE</h3><p>在 <code>Info.plist</code> 中添加 <code>BUILD_TYPE</code>，取值为 <code>$(CONFIGURATION)</code></p><h3 id="获取构建时间"><a href="#获取构建时间" class="headerlink" title="获取构建时间"></a>获取构建时间</h3><p>在 <code>Info.plist</code> 中添加 <code>BUILD_TIME</code>,取值为空，并通过脚本在每次编译的时候对其更新，脚本添加步骤 <code>Target</code>-&gt; <code>Build Phases</code> -&gt; <code>+</code> -&gt; <code>New Run Script Phase</code>, Shell 代码如下：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">#</span><span class="language-bash">!/bin/bash</span></span><br><span class="line">infoplist=&quot;$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH&quot;</span><br><span class="line">builddate=`date +%Y-%m-%d_%H:%M`</span><br><span class="line">if [[ -n &quot;$builddate&quot; ]]; then</span><br><span class="line">/usr/libexec/PlistBuddy -c &quot;Set :BUILD_TIME $builddate&quot; $&#123;infoplist&#125;</span><br><span class="line">fi</span><br></pre></td></tr></table></figure><h3 id="权限申请"><a href="#权限申请" class="headerlink" title="权限申请"></a>权限申请</h3><ul><li><code>Privacy - Camera Usage Description</code></li><li><code>Privacy - Photo Library Usage Description</code></li><li><code>Privacy - Microphone Usage Description</code></li></ul><h2 id="Lean-Core"><a href="#Lean-Core" class="headerlink" title="Lean Core"></a>Lean Core</h2><table><thead><tr><th align="left">COMPONENT</th><th align="left">DEPRECATED?</th><th align="left">NEW HOME</th></tr></thead><tbody><tr><td align="left"><strong>AsyncStorage</strong></td><td align="left">0.59</td><td align="left"><a href="https://github.com/react-native-community/react-native-async-storage">@react-native-community&#x2F;react-native-async-storage</a></td></tr><tr><td align="left"><strong>ImageStore</strong></td><td align="left">0.59</td><td align="left"><a href="https://github.com/expo/expo/tree/master/packages/expo-file-system">expo-file-system</a> or <a href="https://github.com/itinance/react-native-fs">react-native-fs</a></td></tr><tr><td align="left"><strong>MaskedViewIOS</strong></td><td align="left">0.59</td><td align="left"><a href="https://github.com/react-native-community/react-native-masked-view">@react-native-community&#x2F;react-native-masked-view</a></td></tr><tr><td align="left"><strong>NetInfo</strong></td><td align="left">0.59</td><td align="left"><a href="https://github.com/react-native-community/react-native-netinfo">@react-native-community&#x2F;react-native-netinfo</a></td></tr><tr><td align="left"><strong>Slider</strong></td><td align="left">0.59</td><td align="left"><a href="https://github.com/react-native-community/react-native-slider">@react-native-community&#x2F;react-native-slider</a></td></tr><tr><td align="left"><strong>ViewPagerAndroid</strong></td><td align="left">0.59</td><td align="left"><a href="https://github.com/react-native-community/react-native-viewpager">@react-native-community&#x2F;react-native-viewpager</a></td></tr><tr><td align="left"><strong>WebView</strong></td><td align="left">0.60</td><td align="left"><a href="https://github.com/react-native-community/react-native-webview">react-native-webview</a></td></tr><tr><td align="left"><strong>NetInfo</strong></td><td align="left">0.60</td><td align="left"><a href="https://github.com/react-native-community/react-native-netinfo">@react-native-community&#x2F;netinfo</a></td></tr><tr><td align="left"><strong>Geolocation</strong></td><td align="left">0.60</td><td align="left"><a href="https://github.com/react-native-community/react-native-geolocation">@react-native-community&#x2F;geolocation</a></td></tr><tr><td align="left"><strong>Apple TV Support</strong></td><td align="left">0.62</td><td align="left"><a href="https://github.com/react-native-community/react-native-tvos">react-native-community&#x2F;react-native-tvos</a></td></tr></tbody></table><h2 id="联系作者"><a href="#联系作者" class="headerlink" title="联系作者"></a>联系作者</h2><table><thead><tr><th align="center">作者微信</th><th align="center">知识星球</th><th align="center">赞赏作者</th></tr></thead><tbody><tr><td align="center"><img src="https://user-gold-cdn.xitu.io/2020/2/24/17074acbb24c7412?w=200&h=200&f=jpeg&s=17183" style="width:200px"/></td><td align="center"><img src="https://user-gold-cdn.xitu.io/2020/2/24/17074acbb26af8e1?w=200&h=200&f=png&s=39093" style="width:200px"/></td><td align="center"><img src="https://user-gold-cdn.xitu.io/2020/2/24/17074acbb338c643?w=698&h=700&f=png&s=315492" style="width:200px"/></td></tr></tbody></table>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/f5a60d0347b3/</id>
    <link href="https://blog.zisheng.pro/posts/f5a60d0347b3/"/>
    <published>2020-02-23T09:26:09.000Z</published>
    <summary>本文记录了一些 React Native 架构方案的最佳实践</summary>
    <title>React Native 架构面面观</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React-Native/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/tags/React-Native/"/>
    <category term="启动屏" scheme="https://blog.zisheng.pro/tags/%E5%90%AF%E5%8A%A8%E5%B1%8F/"/>
    <category term="React Native Splash screen" scheme="https://blog.zisheng.pro/tags/React-Native-Splash-screen/"/>
    <category term="React Native 启动白屏" scheme="https://blog.zisheng.pro/tags/React-Native-%E5%90%AF%E5%8A%A8%E7%99%BD%E5%B1%8F/"/>
    <content>
      <![CDATA[<p>如果你有设计师，请让设计师给你要的所有尺寸图，如果没有请自行使用 <a href="https://icon.wuruihong.com/">图标工厂</a> 一键生成所有尺寸的图标&#x2F;启动图。</p><h2 id="安装依赖"><a href="#安装依赖" class="headerlink" title="安装依赖"></a>安装依赖</h2><p>$ yarn add react-native-splash-screen</p><h2 id="Android"><a href="#Android" class="headerlink" title="Android"></a>Android</h2><p>1、通过创建 <code>launch_screen.png</code> 文件并把它们放到 <code>mipmap-</code> 文件夹下来自定义你的启动图。安卓会自动选择合适的分辨率，因此你不是必须为所有手机分辨率提供图片。不过，你可以为以下所有分辨率提供启动图：</p><ul><li><code>mipmap-mdpi</code></li><li><code>mipmap-hdpi</code></li><li><code>mipmap-xhdpi</code></li><li><code>mipmap-xxhdpi</code></li><li><code>mipmap-xxxhdpi</code></li></ul><p>2、更新你的 <code>MainActivity.java</code> 文件如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> android.os.Bundle;</span><br><span class="line"><span class="keyword">import</span> org.devio.rn.splashscreen.SplashScreen;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MainActivity</span> <span class="keyword">extends</span> <span class="title class_">ReactActivity</span> &#123;</span><br><span class="line">   <span class="meta">@Override</span></span><br><span class="line">   <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">onCreate</span><span class="params">(Bundle savedInstanceState)</span> &#123;</span><br><span class="line">     SplashScreen.show(<span class="built_in">this</span>, <span class="literal">true</span>);  <span class="comment">// 添加这一句</span></span><br><span class="line">     <span class="built_in">super</span>.onCreate(savedInstanceState);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// ...other code</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>2、创建一个名为 <code>launch_screen.xml</code> 的布局文件来自定义你的启动屏幕。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">&quot;1.0&quot;</span> encoding=<span class="string">&quot;utf-8&quot;</span>?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">RelativeLayout</span> <span class="attr">xmlns:android</span>=<span class="string">&quot;http://schemas.android.com/apk/res/android&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:orientation</span>=<span class="string">&quot;vertical&quot;</span> <span class="attr">android:layout_width</span>=<span class="string">&quot;match_parent&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:layout_height</span>=<span class="string">&quot;match_parent&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">ImageView</span> <span class="attr">android:layout_width</span>=<span class="string">&quot;match_parent&quot;</span> <span class="attr">android:layout_height</span>=<span class="string">&quot;match_parent&quot;</span> <span class="attr">android:src</span>=<span class="string">&quot;@mipmap/launch_screen&quot;</span> <span class="attr">android:scaleType</span>=<span class="string">&quot;centerCrop&quot;</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">RelativeLayout</span>&gt;</span></span><br></pre></td></tr></table></figure><p>3、你也可以启用app主题透明选项来解决在APP启动时因主题原因导致的短暂白屏的问题,具体步骤如下:</p><p>打开 <code>android/app/src/main/res/values/styles.xml</code> 文件,添加 <code>&lt;item name=&quot;android:windowIsTranslucent&quot;&gt;true&lt;/item&gt;</code>,如下 :</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">resources</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- Base application theme. --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">style</span> <span class="attr">name</span>=<span class="string">&quot;AppTheme&quot;</span> <span class="attr">parent</span>=<span class="string">&quot;Theme.AppCompat.Light.NoActionBar&quot;</span>&gt;</span><span class="language-xml"></span></span><br><span class="line"><span class="language-xml">        <span class="comment">&lt;!-- Customize your theme here. --&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="comment">&lt;!--设置透明背景--&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">item</span> <span class="attr">name</span>=<span class="string">&quot;android:windowIsTranslucent&quot;</span>&gt;</span>true<span class="tag">&lt;/<span class="name">item</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    </span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">resources</span>&gt;</span></span><br></pre></td></tr></table></figure><p>4、Add a color called <code>primary_dark</code> in <code>app/src/main/res/values/colors.xml</code></p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">&quot;1.0&quot;</span> encoding=<span class="string">&quot;utf-8&quot;</span>?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">resources</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">color</span> <span class="attr">name</span>=<span class="string">&quot;primary_dark&quot;</span>&gt;</span>#000000<span class="tag">&lt;/<span class="name">color</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">resources</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="iOS"><a href="#iOS" class="headerlink" title="iOS"></a>iOS</h2><h3 id="Xcode-11-配置-LaunchImage"><a href="#Xcode-11-配置-LaunchImage" class="headerlink" title="Xcode 11 配置 LaunchImage"></a>Xcode 11 配置 LaunchImage</h3><p>如果有设计师资源，请 ui 同学提供以下尺寸的图片</p><ul><li>640x960</li><li>640x1136</li><li>750x1334</li><li>828x1792</li><li>1125x2436</li><li>1242x2436</li></ul><p>如果你没有设计师资源，可以使用 <a href="https://icon.wuruihong.com/">图标工厂</a> 自行生成</p><p>1、添加 LaunchImage</p><p><img src="https://i.loli.net/2019/09/16/SpIYPmE7MHTzw6o.png" alt="紫升"></p><p>2、将准备好的图片拖到下图红框的区域</p><p><img src="https://i.loli.net/2019/09/16/bBO1u4PAaWLdMqV.png" alt="紫升"></p><p>3、你在 <code>buildSetting</code> 中搜索 <code>launch</code>,可看到 <code>Asset Catalog Launch Image Set Name</code> 你只要把对应的<code>LaunchImage</code> 名称设置上去就好了:</p><p><img src="https://i.loli.net/2020/02/21/dXqnrGc2sWK4aVP.png" alt="紫升"></p><p>4、清空 Launch Screen File</p><p><img src="https://i.loli.net/2020/02/21/oyFKjUY1RZwtf45.png" alt="紫升"></p><p>5、最后别忘了把 <code>Info.list</code> 的 <code>UILaunchStoryboardName</code> 删除:</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">key</span>&gt;</span>UILaunchStoryboardName<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">string</span>&gt;</span>LaunchScreen<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="更新-AppDelegate-m"><a href="#更新-AppDelegate-m" class="headerlink" title="更新 AppDelegate.m"></a>更新 <code>AppDelegate.m</code></h3><figure class="highlight obj-c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#import <span class="string">&quot;AppDelegate.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#import <span class="string">&lt;React/RCTBundleURLProvider.h&gt;</span></span></span><br><span class="line"><span class="meta">#import <span class="string">&lt;React/RCTRootView.h&gt;</span></span></span><br><span class="line"><span class="meta">#import <span class="string">&quot;RNSplashScreen.h&quot;</span>  <span class="comment">// 添加这一句</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">AppDelegate</span></span></span><br><span class="line"></span><br><span class="line">- (<span class="type">BOOL</span>)application:(<span class="built_in">UIApplication</span> *)application didFinishLaunchingWithOptions:(<span class="built_in">NSDictionary</span> *)launchOptions</span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">// ...other code</span></span><br><span class="line"></span><br><span class="line">    [RNSplashScreen show];  <span class="comment">// 添加这一句，这一句一定要在最后</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">YES</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure><h2 id="隐藏启动图"><a href="#隐藏启动图" class="headerlink" title="隐藏启动图"></a>隐藏启动图</h2><h3 id="类组件"><a href="#类组件" class="headerlink" title="类组件"></a>类组件</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">SplashScreen</span> <span class="keyword">from</span> <span class="string">&#x27;react-native-splash-screen&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">class</span> <span class="title class_">WelcomePage</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Component</span> &#123;</span><br><span class="line">    <span class="title function_">componentDidMount</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="title class_">SplashScreen</span>.<span class="title function_">hide</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="函数组件"><a href="#函数组件" class="headerlink" title="函数组件"></a>函数组件</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">App</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="title class_">React</span>.<span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title class_">SplashScreen</span>.<span class="title function_">hide</span>()</span><br><span class="line">  &#125;, [])</span><br><span class="line">  <span class="keyword">return</span> (...)</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/c948214d88f6/</id>
    <link href="https://blog.zisheng.pro/posts/c948214d88f6/"/>
    <published>2020-02-21T04:41:18.000Z</published>
    <summary>本文介绍了配置 React Native Splash Screen 防止启动白屏</summary>
    <title>配置 React Native Splash Screen 防止启动白屏</title>
    <updated>2026-07-12T09:21:36.911Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React-Native/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/tags/React-Native/"/>
    <category term="字体" scheme="https://blog.zisheng.pro/tags/%E5%AD%97%E4%BD%93/"/>
    <content>
      <![CDATA[<p>添加以下代码到 <code>index.js</code>:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">AppRegistry</span>, <span class="title class_">Text</span>, <span class="title class_">TextInput</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;react-native&#x27;</span></span><br><span class="line"><span class="title class_">Text</span>.<span class="property">defaultProps</span> = &#123; ...<span class="title class_">Text</span>.<span class="property">defaultProps</span>, <span class="attr">allowFontScaling</span>: <span class="literal">false</span> &#125;</span><br><span class="line"><span class="title class_">TextInput</span>.<span class="property">defaultProps</span> = &#123; ...<span class="title class_">TextInput</span>.<span class="property">defaultProps</span>, <span class="attr">allowFontScaling</span>: <span class="literal">false</span> &#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/528f99f5addc/</id>
    <link href="https://blog.zisheng.pro/posts/528f99f5addc/"/>
    <published>2020-02-20T07:38:31.000Z</published>
    <summary>本文记录了 React Native 如何配置字体大小不随字体设置变化</summary>
    <title>React Native 配置字体大小不随字体设置变化</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React-Native/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/tags/React-Native/"/>
    <category term="安卓" scheme="https://blog.zisheng.pro/tags/%E5%AE%89%E5%8D%93/"/>
    <content>
      <![CDATA[<h2 id="配置应用名"><a href="#配置应用名" class="headerlink" title="配置应用名"></a>配置应用名</h2><p>很简单,我们直接打开 <code>android/app/src/main/res/values/strings.xml</code>，即可看到配置中的 <code>app_name</code>，修改为你想要的即可。</p><h2 id="配置图标"><a href="#配置图标" class="headerlink" title="配置图标"></a>配置图标</h2><p>也很简单，在 <code>android\app\src\main\res\mipmap-xxxxxx</code> 中直接覆盖图标就可以，注意图标的大小。</p><h2 id="打包-APK"><a href="#打包-APK" class="headerlink" title="打包 APK"></a>打包 APK</h2><p>1、在项目根目录执行 <code>keytool -genkeypair -v -keystore release.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000</code> 生成密钥文件 <code>release.keystore</code></p><p>2、把 <code>release.keystore</code> 文件放到你工程中的 <code>android/app</code> 文件夹下。</p><p>3、配置 <code>android/app/build.gradle</code></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">android&#123;</span><br><span class="line">    ...</span><br><span class="line">    signingConfigs &#123;</span><br><span class="line">        release &#123;</span><br><span class="line">            storeFile <span class="title function_">file</span><span class="params">(<span class="string">&quot;release.keystore&quot;</span>)</span></span><br><span class="line">            storePassword <span class="string">&quot;****&quot;</span></span><br><span class="line">            keyAlias <span class="string">&quot;my-key-alias&quot;</span></span><br><span class="line">            keyPassword <span class="string">&quot;****&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    ...</span><br><span class="line">    buildTypes &#123;</span><br><span class="line">        release &#123;</span><br><span class="line">            ...</span><br><span class="line">            signingConfig signingConfigs.release</span><br><span class="line">            ...</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="去除无用的语言资源"><a href="#去除无用的语言资源" class="headerlink" title="去除无用的语言资源"></a>去除无用的语言资源</h3><p>通过配置 <code>android/defaultConfig/resConfigs</code> 可以选择只打包哪几种语言，进而去掉各种 aar 包中全世界的语言，尤其是 support 包中的。</p><p>选择保留什么语言要根据产品的用户和市场来定，如果只选择默认英语和中文语言，配置如下：</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">defaultConfig &#123;</span><br><span class="line"><span class="addition">+    resConfigs &quot;en&quot;,&quot;zh&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="配置-PackagingOptions"><a href="#配置-PackagingOptions" class="headerlink" title="配置 PackagingOptions"></a>配置 PackagingOptions</h3><p>打开 <code>android/app/build.gradle</code> 文件，添加如下配置：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">packagingOptions &#123;</span><br><span class="line">    exclude &#x27;META-INF/android_release.kotlin_module&#x27;</span><br><span class="line">    exclude &#x27;META-INF/DEPENDENCIES&#x27;</span><br><span class="line">    exclude &#x27;META-INF/LICENSE&#x27;</span><br><span class="line">    exclude &#x27;META-INF/LICENSE.txt&#x27;</span><br><span class="line">    exclude &#x27;META-INF/license.txt&#x27;</span><br><span class="line">    exclude &#x27;META-INF/NOTICE&#x27;</span><br><span class="line">    exclude &#x27;META-INF/NOTICE.txt&#x27;</span><br><span class="line">    exclude &#x27;META-INF/notice.txt&#x27;</span><br><span class="line">    exclude &#x27;META-INF/ASL2.0&#x27;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>pickFirsts: 当出现重复文件，会使用第一个匹配的文件打包进入 apk</li><li>merges: 当出现重复文件，合并重复的文件打入 apk</li><li>excludes: 打包的时候排除匹配的文件</li></ul><p><strong>参考链接</strong></p><ul><li><a href="http://t.cn/Ewt1xD2">PackagingOptions</a></li><li><a href="http://t.cn/AipuM9Ll">More than one file was found with OS independent path</a></li><li><a href="http://t.cn/AipuMfcH">More than one file was found with OS independent path ‘META-INF&#x2F;LICENSE’</a></li></ul><h3 id="配置-splits"><a href="#配置-splits" class="headerlink" title="配置 splits"></a>配置 splits</h3><blockquote><p>查看手机 CPU 信息：<code>adb shell</code> -&gt; <code>cd /proc</code> -&gt; <code>cat cpuinfo</code></p></blockquote><p>默认情况下，生成的 <code>APK</code> 会同时包含针对于 <code>x86</code> 和 <code>ARMv7a</code> 两种 <code>CPU</code> 架构的原生代码。这样可以让我们更方便的向其他人分享这个 <code>APK</code>，因为它几乎可以运行在所有的 Android 设备上。但是，这会导致所有设备上都有一些根本不会运行的代码，白白占据了空间。目前安卓设备绝大多数是 <code>ARM</code> 架构，因此对于大部分应用来说可以考虑去掉 <code>x86</code> 架构的支持。</p><p>你可以在 <code>android/app/build.gradle</code> 中修改如下代码：</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="deletion">- include &quot;armeabi-v7a&quot;, &quot;x86&quot;, &quot;arm64-v8a&quot;, &quot;x86_64&quot;</span></span><br><span class="line"><span class="addition">+ include &quot;armeabi-v7a&quot;, &quot;arm64-v8a&quot;</span></span><br><span class="line"><span class="deletion">- def versionCodes = [&quot;armeabi-v7a&quot;:1, &quot;x86&quot;:2, &quot;arm64-v8a&quot;: 3, &quot;x86_64&quot;: 4]</span></span><br><span class="line"><span class="addition">+ def versionCodes = [&quot;armeabi-v7a&quot;:1, &quot;arm64-v8a&quot;: 2]</span></span><br></pre></td></tr></table></figure><ul><li><p>abi: Application Binary Interface，针对不同的 <code>CPU</code> 架构生成 <code>APK</code> 以减小 <code>APK</code> 文件的大小</p><ul><li><code>mips/mips64</code>：极少用于手机，出发点是高性能,主要用于路由器、猫。</li><li><code>armeabi</code>：老版本 <code>ARMv5</code>，不支持硬件辅助浮点运算，支持所有的 <code>ARM</code> 设备。</li><li><code>x86</code>&#x2F;<code>x86_64</code>：<code>x86</code> 架构的手机的市场占有率很低，约为 1%左右。而且 <code>x86</code> 架构都包含 <code>ARM</code> 模拟层，兼容 <code>ARM</code> 类型的 <code>ABI</code>。注意，模拟器为 <code>x86</code> 架构。</li><li><code>arm64-v8a</code>：64 位 <code>ARM</code> 架构。可用 32 位模式运行 <code>armeabi-v7a</code> 和 <code>armeabi</code>。（所谓的 <code>ARMv8</code> 架构，就是在 <code>MIPS64</code> 架构上增加了 <code>ARMv7</code> 架构中已经拥有的的 <code>TrustZone</code> 技术、虚拟化技术及 <code>NEON advanced SIMD</code> 技术等特性，研发成的）</li><li><code>armeabi-v7a</code>：主流版本 <code>AMRv7</code>，2011 年 15 月以后的生产的大部分 Android 设备都使用它。</li></ul></li><li><p>density: 针对不同的分辨率生成 <code>APK</code> 以减小 <code>APK</code> 文件的大小</p></li></ul><h3 id="Failed-to-read-PNG-signature-file-does-not-start-with-PNG-signature"><a href="#Failed-to-read-PNG-signature-file-does-not-start-with-PNG-signature" class="headerlink" title="Failed to read PNG signature: file does not start with PNG signature"></a>Failed to read PNG signature: file does not start with PNG signature</h3><p>有时从网上下载的 Demo 资源文件不规范，会出现直接将 jpg 文件改为 png 后缀名的情况，gradle 打包检查时报错编译通不过的。我们通过 <code>aaptOptions.cruncherEnabled=false</code> 来禁止 Gradle 检查 png 的合法性：</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">android &#123;</span><br><span class="line">  aaptOptions &#123;</span><br><span class="line">    cruncherEnabled=false</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="配置-dexOptions-javaMaxHeapSize"><a href="#配置-dexOptions-javaMaxHeapSize" class="headerlink" title="配置 dexOptions.javaMaxHeapSize"></a>配置 dexOptions.javaMaxHeapSize</h3><blockquote><p>android studio 需要较大的内存才能正常编译项目，主要解决这个警告：<a href="http://t.cn/EZcTDtV">com.android.build.api.transform.TransformException</a></p></blockquote><p>在 <code>android\gradle.properties</code> 中加入以下配置：</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="addition">+ dexOptions.javaMaxHeapSize = 2g</span></span><br></pre></td></tr></table></figure><h3 id="gradle-优化配置"><a href="#gradle-优化配置" class="headerlink" title="gradle 优化配置"></a>gradle 优化配置</h3><p>在 <code>android\gradle.properties</code> 中加入以下配置：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"># 让gradle使用单独的守护进程</span><br><span class="line">org.gradle.daemon=true</span><br><span class="line"># 让gradle并行编译</span><br><span class="line">org.gradle.parallel=true</span><br><span class="line"># 让gradle在需要的时候才配置</span><br><span class="line">org.gradle.configureondemand=true</span><br><span class="line"># 增加gradle运行的java虚拟机的大小</span><br><span class="line">org.gradle.jvmargs=-Xmx3072m -XX:MaxPermSize=1024m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8</span><br></pre></td></tr></table></figure><h3 id="配置方法数超过-64K-的应用"><a href="#配置方法数超过-64K-的应用" class="headerlink" title="配置方法数超过 64K 的应用"></a>配置方法数超过 64K 的应用</h3><p>随着 Android 平台的持续成长，Android 应用的大小也在增加。当您的应用及其引用的库达到特定大小时，您会遇到构建错误，指明您的应用已达到 Android 应用构建架构的极限。会报告这一错误：</p><blockquote><p>The number of method references in a .dex file cannot exceed 64K.</p></blockquote><p>解决办法是配置您的应用进行 Dalvik 可执行文件分包，在 <code>android/app/build.gradle</code> 中做下面的配置：</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">defaultConfig &#123;</span><br><span class="line"><span class="addition">+    multiDexEnabled true</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="BuildConfig"><a href="#BuildConfig" class="headerlink" title="BuildConfig"></a>BuildConfig</h2><blockquote><p>在 react-native 中，我们可以借助 react-native-config-reader 来方便地读取这些属性</p></blockquote><p>BuildConfig 是程序编译后，根据 buildType 生成在 <code>app\build\generated\source\buildConfig\debug(release)\</code> 包名下的一个 java 文件。默认有一下属性：</p><ul><li>DEBUG：是否是调试版本</li><li>APPLICATION_ID：当前应用的包名</li><li>FLAVOR：产品（渠道包的名称）</li><li>BUILD_TYPE：当前的编译类型(release&#x2F;debug)</li><li>VERSION_CODE：版本号(数字)</li><li>VERSION_NAME：版本号</li></ul><h3 id="自定义-BuildConfig"><a href="#自定义-BuildConfig" class="headerlink" title="自定义 BuildConfig"></a>自定义 BuildConfig</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">defaultConfig &#123;</span><br><span class="line">  <span class="comment">// 三个参数: 1.要定义的常量的类型 2.该常量的命名 3.该常量的值</span></span><br><span class="line">  <span class="comment">// APP_NAME，对应 ios 的 CFBundleDisplayName</span></span><br><span class="line">  buildConfigField <span class="string">&quot;String&quot;</span>, <span class="string">&quot;APP_NAME&quot;</span>, <span class="string">&#x27;&quot;我是谁&quot;&#x27;</span></span><br><span class="line">  <span class="comment">// BUILD_TIME</span></span><br><span class="line">  buildConfigField <span class="string">&quot;String&quot;</span>, <span class="string">&quot;BUILD_TIME&quot;</span>, <span class="string">&#x27;&quot;&#x27;</span> + <span class="keyword">new</span> <span class="title class_">Date</span>().format(<span class="string">&quot;yyyy-MM-dd HH:mm:ss&quot;</span>, TimeZone.getTimeZone(<span class="string">&quot;Asia/Shanghai&quot;</span>)) + <span class="string">&#x27;&quot;&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="在子模块中取主项目的-BuildConfig"><a href="#在子模块中取主项目的-BuildConfig" class="headerlink" title="在子模块中取主项目的 BuildConfig"></a>在子模块中取主项目的 BuildConfig</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> Object <span class="title function_">getBuildConfigValue</span><span class="params">(Context context, String fieldName)</span> &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    Class&lt;?&gt; clazz = Class.forName(context.getPackageName() + <span class="string">&quot;.BuildConfig&quot;</span>);</span><br><span class="line">    <span class="type">Field</span> <span class="variable">field</span> <span class="operator">=</span> clazz.getField(fieldName);</span><br><span class="line">    <span class="keyword">return</span> field.get(<span class="literal">null</span>);</span><br><span class="line">  &#125; <span class="keyword">catch</span> (ClassNotFoundException e) &#123;</span><br><span class="line">    e.printStackTrace();</span><br><span class="line">  &#125; <span class="keyword">catch</span> (NoSuchFieldException e) &#123;</span><br><span class="line">    e.printStackTrace();</span><br><span class="line">  &#125; <span class="keyword">catch</span> (IllegalAccessException e) &#123;</span><br><span class="line">    e.printStackTrace();</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br><span class="line">...</span><br><span class="line"><span class="type">String</span> <span class="variable">versionName</span> <span class="operator">=</span> (String)getBuildConfigValue(activity, <span class="string">&quot;VERSION_NAME&quot;</span>))</span><br></pre></td></tr></table></figure><ul><li><a href="https://www.jianshu.com/p/3d9b23afe514">BuildConfig 与 build.gradle 的关系</a></li><li><a href="https://www.jianshu.com/p/274c9d95cf76">Gradle 之 BuildConfig 自定义常量</a></li></ul><h2 id="解决在-Android-P-上的提醒弹窗-（Detected-problems-with-API-compatibility-visit-g-co-dev-appcompat-for-more-info"><a href="#解决在-Android-P-上的提醒弹窗-（Detected-problems-with-API-compatibility-visit-g-co-dev-appcompat-for-more-info" class="headerlink" title="解决在 Android P 上的提醒弹窗 （Detected problems with API compatibility(visit g.co&#x2F;dev&#x2F;appcompat for more info)"></a>解决在 Android P 上的提醒弹窗 （Detected problems with API compatibility(visit g.co&#x2F;dev&#x2F;appcompat for more info)</h2><p>在 <code>MainActivity.java</code> 中添加 closeAndroidPDialog 方法并在 <code>onCreate</code> 方法中调用</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> android.os.Bundle;</span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.Constructor;</span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.Field;</span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.Method;</span><br><span class="line">...</span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">onCreate</span><span class="params">(Bundle savedInstanceState)</span> &#123;</span><br><span class="line">  closeAndroidPDialog(); <span class="comment">// here</span></span><br><span class="line">  <span class="built_in">super</span>.onCreate(savedInstanceState);</span><br><span class="line">&#125;</span><br><span class="line">...</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">closeAndroidPDialog</span><span class="params">()</span>&#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="type">Class</span> <span class="variable">aClass</span> <span class="operator">=</span> Class.forName(<span class="string">&quot;android.content.pm.PackageParser$Package&quot;</span>);</span><br><span class="line">    <span class="type">Constructor</span> <span class="variable">declaredConstructor</span> <span class="operator">=</span> aClass.getDeclaredConstructor(String.class);</span><br><span class="line">    declaredConstructor.setAccessible(<span class="literal">true</span>);</span><br><span class="line">  &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">    e.printStackTrace();</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="type">Class</span> <span class="variable">cls</span> <span class="operator">=</span> Class.forName(<span class="string">&quot;android.app.ActivityThread&quot;</span>);</span><br><span class="line">    <span class="type">Method</span> <span class="variable">declaredMethod</span> <span class="operator">=</span> cls.getDeclaredMethod(<span class="string">&quot;currentActivityThread&quot;</span>);</span><br><span class="line">    declaredMethod.setAccessible(<span class="literal">true</span>);</span><br><span class="line">    <span class="type">Object</span> <span class="variable">activityThread</span> <span class="operator">=</span> declaredMethod.invoke(<span class="literal">null</span>);</span><br><span class="line">    <span class="type">Field</span> <span class="variable">mHiddenApiWarningShown</span> <span class="operator">=</span> cls.getDeclaredField(<span class="string">&quot;mHiddenApiWarningShown&quot;</span>);</span><br><span class="line">    mHiddenApiWarningShown.setAccessible(<span class="literal">true</span>);</span><br><span class="line">    mHiddenApiWarningShown.setBoolean(activityThread, <span class="literal">true</span>);</span><br><span class="line">  &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">    e.printStackTrace();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="闪屏图尺寸"><a href="#闪屏图尺寸" class="headerlink" title="闪屏图尺寸"></a>闪屏图尺寸</h2><ul><li>mdpi：320x480</li><li>hdpi：480x800</li><li>xhdpi：720x1280</li><li>xxhdpi：1080x1920</li><li>xxxhdpi：2160x3840</li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/e49e15e51f69/</id>
    <link href="https://blog.zisheng.pro/posts/e49e15e51f69/"/>
    <published>2020-02-20T06:57:55.000Z</published>
    <summary>本文记录了一些 React Native 安卓端必须掌握的知识</summary>
    <title>React Native 安卓必知必会</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="JavaScript" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/JavaScript/"/>
    <category term="JavaScript" scheme="https://blog.zisheng.pro/tags/JavaScript/"/>
    <category term="apply" scheme="https://blog.zisheng.pro/tags/apply/"/>
    <category term="call" scheme="https://blog.zisheng.pro/tags/call/"/>
    <category term="bind" scheme="https://blog.zisheng.pro/tags/bind/"/>
    <category term="手写代码" scheme="https://blog.zisheng.pro/tags/%E6%89%8B%E5%86%99%E4%BB%A3%E7%A0%81/"/>
    <category term="Function.prototype.bind" scheme="https://blog.zisheng.pro/tags/Function-prototype-bind/"/>
    <category term="this" scheme="https://blog.zisheng.pro/tags/this/"/>
    <content>
      <![CDATA[<p><code>bind</code> 是在 ES5 加入的语法，具体如何使用 MDN 都有。但有一点需要额外注意一下：<code>bind</code> 会返回一个新的函数。也就是说：它会修改 <code>this</code> 的指向，并返回新的函数体。</p><h2 id="实现思路"><a href="#实现思路" class="headerlink" title="实现思路"></a>实现思路</h2><ol><li>改变 <code>this</code> 指向: 需要使用 <code>apply</code> 或 <code>call</code></li><li>返回新的函数体: 函数作为值返回</li></ol><h2 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h2><p>使用闭包保存 this 指针、上下文和参数，并将上下文和参数使用 apply 改变 this 指向。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Function</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">bind1</span> = <span class="keyword">function</span>(<span class="params">context</span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> that = <span class="variable language_">this</span></span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    that.<span class="title function_">apply</span>(context, <span class="variable language_">arguments</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用方式和原生 <code>bind()</code> 一样：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> person = &#123; <span class="attr">name</span>:<span class="string">&quot;紫升&quot;</span> &#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">sayHi</span>(<span class="params">name</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>, name)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> pSay = sayHi.<span class="title function_">bind1</span>(person)</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/ef0fa347e647/</id>
    <link href="https://blog.zisheng.pro/posts/ef0fa347e647/"/>
    <published>2020-02-19T07:28:58.000Z</published>
    <summary>bind 是在 ES5 加入的语法，具体如何使用 MDN 都有。但有一点需要额外注意一下：bind 会返回一个新的函数。也就是说：它会修改 this 的指向，并返回新的函数体。</summary>
    <title>手写 JavaScript bind 函数</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="TypeScript" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/TypeScript/"/>
    <category term="TypeScript" scheme="https://blog.zisheng.pro/tags/TypeScript/"/>
    <category term="掘金" scheme="https://blog.zisheng.pro/tags/%E6%8E%98%E9%87%91/"/>
    <category term="interface" scheme="https://blog.zisheng.pro/tags/interface/"/>
    <category term="type" scheme="https://blog.zisheng.pro/tags/type/"/>
    <content>
      <![CDATA[<h2 id="相同点"><a href="#相同点" class="headerlink" title="相同点"></a>相同点</h2><h3 id="都可以描述一个对象或者函数"><a href="#都可以描述一个对象或者函数" class="headerlink" title="都可以描述一个对象或者函数"></a>都可以描述一个对象或者函数</h3><h4 id="interface"><a href="#interface" class="headerlink" title="interface"></a>interface</h4><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">User</span> &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="built_in">string</span></span><br><span class="line">  <span class="attr">age</span>: <span class="built_in">number</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">SetUser</span> &#123;</span><br><span class="line">  (<span class="attr">name</span>: <span class="built_in">string</span>, <span class="attr">age</span>: <span class="built_in">number</span>) : <span class="built_in">void</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="type"><a href="#type" class="headerlink" title="type"></a>type</h4><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> <span class="title class_">User</span> &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="built_in">string</span></span><br><span class="line">  <span class="attr">age</span>: <span class="built_in">number</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">type</span> <span class="title class_">SetUser</span> = <span class="function">(<span class="params">name:<span class="built_in">string</span>, age: <span class="built_in">number</span></span>) =&gt;</span> <span class="built_in">void</span></span><br></pre></td></tr></table></figure><h3 id="都允许拓展（extends）"><a href="#都允许拓展（extends）" class="headerlink" title="都允许拓展（extends）"></a>都允许拓展（<code>extends</code>）</h3><p><code>interface</code> 和 <code>type</code> 都可以扩展，并且两者并不是相互独立的，也就是说 <code>interface</code> 可以 <code>extends</code> <code>type</code>，<code>type</code> 也可以 <code>extends</code> <code>interface</code>。<strong>虽然效果差不多，但是两者语法不同</strong>。</p><h4 id="interface-extends-type"><a href="#interface-extends-type" class="headerlink" title="interface extends type"></a>interface extends type</h4><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> <span class="title class_">Name</span> = &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">User</span> <span class="keyword">extends</span> <span class="title class_">Name</span> &#123;</span><br><span class="line">  <span class="attr">age</span>: <span class="built_in">number</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="type-extends-interface"><a href="#type-extends-interface" class="headerlink" title="type extends interface"></a>type extends interface</h4><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">Name</span> &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">type</span> <span class="title class_">User</span> = <span class="title class_">Name</span> &amp; &#123;</span><br><span class="line">  <span class="attr">age</span>: <span class="built_in">number</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="不同点"><a href="#不同点" class="headerlink" title="不同点"></a>不同点</h2><h3 id="type-可以而-interface-不行"><a href="#type-可以而-interface-不行" class="headerlink" title="type 可以而 interface 不行"></a>type 可以而 interface 不行</h3><h4 id="type-可以声明基本类型别名，联合类型，元组等类型"><a href="#type-可以声明基本类型别名，联合类型，元组等类型" class="headerlink" title="type 可以声明基本类型别名，联合类型，元组等类型"></a>type 可以声明基本类型别名，联合类型，元组等类型</h4><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 基本类型别名</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Name</span> = <span class="built_in">string</span></span><br><span class="line"><span class="comment">// 联合类型</span></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">Dog</span> &#123;</span><br><span class="line">  <span class="title function_">wong</span>()</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">Cat</span> &#123;</span><br><span class="line">  <span class="title function_">miao</span>()</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Pet</span> = <span class="title class_">Dog</span> | <span class="title class_">Cat</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 具体定义数组每个的类型</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">PetList</span> = [<span class="title class_">Dog</span>, <span class="title class_">Pet</span>]</span><br></pre></td></tr></table></figure><h4 id="type-语句中还可以使用-typeof-获取实例的类型进行赋值"><a href="#type-语句中还可以使用-typeof-获取实例的类型进行赋值" class="headerlink" title="type 语句中还可以使用 typeof 获取实例的类型进行赋值"></a>type 语句中还可以使用 typeof 获取实例的类型进行赋值</h4><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 当你想获取一个变量的类型时，使用 typeof</span></span><br><span class="line"><span class="keyword">let</span> div = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;div&#x27;</span>)</span><br><span class="line"><span class="keyword">type</span> B = <span class="keyword">typeof</span> div</span><br></pre></td></tr></table></figure><h3 id="interface-可以而-type-不行"><a href="#interface-可以而-type-不行" class="headerlink" title="interface 可以而 type 不行"></a>interface 可以而 type 不行</h3><p>interface 能够声明合并</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">User</span> &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="built_in">string</span></span><br><span class="line">  <span class="attr">age</span>: <span class="built_in">number</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">User</span> &#123;</span><br><span class="line">  <span class="attr">sex</span>: <span class="built_in">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">User 接口为 &#123;</span></span><br><span class="line"><span class="comment">  name: string</span></span><br><span class="line"><span class="comment">  age: number</span></span><br><span class="line"><span class="comment">  sex: string</span></span><br><span class="line"><span class="comment">&#125;</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>一般来说，如果不清楚什么时候用interface&#x2F;type，能用 interface 实现，就用 interface , 如果不能就用 type 。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/a7d393e0caa2/</id>
    <link href="https://blog.zisheng.pro/posts/a7d393e0caa2/"/>
    <published>2020-02-18T17:26:43.000Z</published>
    <summary>本文介绍了 TypeScript 中 type 和 interface 的区别</summary>
    <title>TypeScript 中 type 和 interface 的区别</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="微信小程序" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F/"/>
    <category term="微信小程序" scheme="https://blog.zisheng.pro/tags/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F/"/>
    <category term="微信小程序异步库" scheme="https://blog.zisheng.pro/tags/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%BC%82%E6%AD%A5%E5%BA%93/"/>
    <category term="微信小程序promise" scheme="https://blog.zisheng.pro/tags/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8Fpromise/"/>
    <category term="微信小程序async/await" scheme="https://blog.zisheng.pro/tags/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8Fasync-await/"/>
    <content>
      <![CDATA[<p>✨强大、优雅的小程序异步库🚀 小程序promise</p><p><a href="https://nodei.co/npm/wx-promise-pro/"><img src="https://user-gold-cdn.xitu.io/2020/2/18/1705883821e962d8?w=297&h=45&f=png&s=3701" alt="NPM"></a></p><h2 id="优势"><a href="#优势" class="headerlink" title="优势"></a>优势</h2><ul><li>方便集成：一处引用，处处使用</li><li>把微信小程序所有异步 API promise 化并挂在到<code>wx.pro</code> 对象下</li><li>支持 ES2018 <code>finally</code> 特性</li><li>支持 TypeScript 开发</li></ul><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ npm i wx-promise-pro</span><br><span class="line"><span class="comment"># or</span></span><br><span class="line">$ yarn add wx-promise-pro</span><br></pre></td></tr></table></figure><h2 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; promisifyAll, promisify &#125; <span class="keyword">from</span> <span class="string">&#x27;wx-promise-pro&#x27;</span></span><br><span class="line"><span class="comment">// promisify all wx‘s api</span></span><br><span class="line"><span class="title function_">promisifyAll</span>()</span><br><span class="line"><span class="comment">// promisify single api</span></span><br><span class="line"><span class="title function_">promisify</span>(wx.<span class="property">getSystemInfo</span>)().<span class="title function_">then</span>(<span class="variable language_">console</span>.<span class="property">log</span>)</span><br></pre></td></tr></table></figure><h2 id="支持所有的微信小程序异步API"><a href="#支持所有的微信小程序异步API" class="headerlink" title="支持所有的微信小程序异步API"></a>支持所有的微信小程序异步API</h2><p>使用 <code>promisifyAll</code> 开发者无需关心兼容与否，只要是 <code>wx</code> 支持的 api，<code>wx.pro</code> 全部支持。</p><p><strong>示例代码：</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 演示 wxPromise 的能力</span></span><br><span class="line">wx.<span class="property">pro</span>.<span class="title function_">showLoading</span>(&#123;</span><br><span class="line">  <span class="attr">title</span>: <span class="string">&#x27;加载中&#x27;</span>,</span><br><span class="line">  <span class="attr">mask</span>: <span class="literal">true</span></span><br><span class="line">&#125;)</span><br><span class="line">wx.<span class="property">pro</span>.<span class="title function_">request</span>(&#123;</span><br><span class="line">  <span class="attr">url</span>: <span class="string">&#x27;https://cnodejs.org/api/v1/topics&#x27;</span>,</span><br><span class="line">  <span class="attr">data</span>: &#123;&#125;,</span><br><span class="line">  <span class="attr">method</span>: <span class="string">&#x27;GET&#x27;</span>,</span><br><span class="line">  <span class="attr">header</span>: &#123;<span class="string">&#x27;content-type&#x27;</span>: <span class="string">&#x27;application/json&#x27;</span>&#125;</span><br><span class="line">&#125;).<span class="title function_">then</span>(<span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(res)</span><br><span class="line">&#125;).<span class="title function_">catch</span>(<span class="function"><span class="params">err</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(err)</span><br><span class="line">&#125;).<span class="title function_">finally</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  wx.<span class="title function_">hideLoading</span>()</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h2 id="Contributors"><a href="#Contributors" class="headerlink" title="Contributors"></a>Contributors</h2><p>This project exists thanks to all the people who contribute. <!--[[Contribute](CONTRIBUTING.md)].--><br><a href="https://github.com/youngjuning/wx-promise-pro/graphs/contributors"><img src="https://user-gold-cdn.xitu.io/2020/2/18/17058837fd8af7c9?w=890&h=74&f=svg&s=150945" /></a></p><h2 id="GitHub源码：https-github-com-youngjuning-wx-promise-pro"><a href="#GitHub源码：https-github-com-youngjuning-wx-promise-pro" class="headerlink" title="GitHub源码：https://github.com/youngjuning/wx-promise-pro"></a>GitHub源码：<a href="https://github.com/youngjuning/wx-promise-pro">https://github.com/youngjuning/wx-promise-pro</a></h2>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/2f2bd5ced1a6/</id>
    <link href="https://blog.zisheng.pro/posts/2f2bd5ced1a6/"/>
    <published>2020-02-18T13:57:59.000Z</published>
    <summary>wx-promise-pro 是一个 强大、优雅的小程序异步库🚀 小程序 promise 插件</summary>
    <title>wx-promise-pro</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="Node.js" scheme="https://blog.zisheng.pro/tags/Node-js/"/>
    <category term="掘金" scheme="https://blog.zisheng.pro/tags/%E6%8E%98%E9%87%91/"/>
    <category term="cli" scheme="https://blog.zisheng.pro/tags/cli/"/>
    <content>
      <![CDATA[<p>Node.js 用途很广，常用来开发服务、桌面应用等被开发者熟知，Node.js 还有另外一个非常实用的场景 - 命令行应用（后文统称 CLI）。</p><blockquote><p>本文可参考的源码：<a href="https://github.com/youngjuning/cli">https://github.com/youngjuning/cli</a></p></blockquote><h2 id="HelloWorld"><a href="#HelloWorld" class="headerlink" title="HelloWorld"></a>HelloWorld</h2><p>创建一个<code>youngjuning</code> 目录，并使用进入该目录:</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">mkdir</span> youngjuning &amp;&amp; <span class="built_in">cd</span> youngjuning</span><br></pre></td></tr></table></figure><p>在该目录下新建 <code>bin/cli.js</code>:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/usr/bin/env node</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Hello World&#x27;</span>)</span><br></pre></td></tr></table></figure><p>使用 <code>package.json</code> 的 <code>bin</code> 项是用来指定各个内部命令对应的可执行文件的位置：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;bin&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;youngjuning&quot;</span><span class="punctuation">:</span> <span class="string">&quot;./bin/cli.js&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>在命令行输入 <code>npm link</code> 或 <code>npm install -g</code> 将当前项目安装到全局环境，这样就可以直接使用 <code>youngjuning</code> 来运行文件了。</p><h2 id="commander-js"><a href="#commander-js" class="headerlink" title="commander.js"></a>commander.js</h2><p>Node.js 为我们提供了 <code>process.argv</code> 来读取命令行参数，这里我们对此不作介绍，有兴趣可以自行研究，我选择了 tj 大神的 <a href="https://www.npmjs.com/package/commander">commander</a>：</p><ul><li><code>command</code>: 定义命令行指令，后面可跟上一个 name，用空格隔开，如 <code>.command( &#39;app [name]&#39;)</code></li><li><code>alias</code>: 定义一个更短的命令行指令</li><li><code>description</code>: 描述，它会在 help 里面展示</li><li><code>option</code>: 定义参数。它接受四个参数<ul><li>在第一个参数中，它可输入短名字 <code>-a</code> 和长名字 <code>–app</code>，使用 <code>|</code> 或者 <code>,</code> 分隔，在命令行里使用时，这两个是等价的，区别是后者可以在程序里通过回调获取到</li><li>第二个为描述, 会在 <code>help</code> 信息里展示出来</li><li>第三个参数为回调函数，他接收的参数为一个 <code>string</code>，有时候我们需要一个命令行创建多个模块，就需要一个回调来处理</li><li>第四个参数为默认值</li></ul></li><li><code>action</code>: 注册一个 <code>callback</code> 函数,这里需注意目前回调不支持 let 声明变量</li><li><code>parse</code>: 解析命令行</li></ul><h2 id="inquire-js"><a href="#inquire-js" class="headerlink" title="inquire.js"></a>inquire.js</h2><blockquote><p>交互命令输入插件！</p></blockquote><h3 id="基本语法"><a href="#基本语法" class="headerlink" title="基本语法"></a>基本语法</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> answers = <span class="keyword">await</span> inquirer.<span class="title function_">prompt</span>([</span><br><span class="line">  <span class="comment">/* Pass your questions in here */</span></span><br><span class="line">])</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(answers)</span><br></pre></td></tr></table></figure><h3 id="参数详解"><a href="#参数详解" class="headerlink" title="参数详解"></a>参数详解</h3><ul><li><code>type</code>: 表示提问的类型，包括：<code>input</code>, <code>confirm</code>, <code>list</code>, <code>rawlist</code>, <code>expand</code>, <code>checkbox</code>, <code>password</code>, <code>editor</code>；</li><li><code>name</code>: 存储当前问题回答的变量；</li><li><code>message</code>: 问题的描述；</li><li><code>default</code>: 默认值；</li><li><code>choices</code>: 列表选项，在某些 type 下可用，并且包含一个分隔符(separator)；</li><li><code>validate</code>: 对用户的回答进行校验；</li><li><code>filter</code>: 对用户的回答进行过滤处理，返回处理后的值；</li><li><code>transformer</code>: 对用户回答的显示效果进行处理(如：修改回答的字体或背景颜色)，但不会影响最终的答案的内容；</li><li><code>when</code>: 根据前面问题的回答，判断当前问题是否需要被回答；</li><li><code>pageSize</code>: 修改某些 <code>type</code> 类型下的渲染行数；</li><li><code>prefix</code>: 修改 <code>message</code> 默认前缀；</li><li><code>suffix</code>: 修改 <code>message</code> 默认后缀。</li></ul><h2 id="实现动态模板"><a href="#实现动态模板" class="headerlink" title="实现动态模板"></a>实现动态模板</h2><ul><li>利用 <code>recursive-readdir</code> 库递归读取项目所有文件</li><li>利用 <code>handlebars</code> 进行模板替换</li><li>利用 <code>fs</code> 模块进行读文件和写文件操作</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> handlebars = <span class="built_in">require</span>(<span class="string">&#x27;handlebars&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> recursive = <span class="built_in">require</span>(<span class="string">&#x27;recursive-readdir&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">compile</span> = (<span class="params">meta, file</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> content = fs.<span class="title function_">readFileSync</span>(file).<span class="title function_">toString</span>()</span><br><span class="line">  <span class="keyword">const</span> result = handlebars.<span class="title function_">compile</span>(content)(meta)</span><br><span class="line">  fs.<span class="title function_">writeFileSync</span>(file, result)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = <span class="function">(<span class="params">meta, src</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">recursive</span>(src, <span class="function">(<span class="params">err, files</span>) =&gt;</span> &#123;</span><br><span class="line">    files.<span class="title function_">forEach</span>(<span class="function"><span class="params">file</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="title function_">compile</span>(meta, file)</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="命令行工具"><a href="#命令行工具" class="headerlink" title="命令行工具"></a>命令行工具</h2><ul><li><a href="https://www.npmjs.com/package/chalk">chalk.js</a>: 美化命令行的模块</li><li><a href="https://www.npmjs.com/package/ora">ora</a>: Elegant terminal spinner</li><li><a href="https://www.npmjs.com/package/ncp">ncp</a>: 异步的拷贝文件，包含空文件夹</li><li><a href="https://www.npmjs.com/package/recursive-readdir">recursive-readdir</a>: 递归地列出目录和子目录下的所有文件，不包含目录本身。</li><li><a href="https://github.com/shelljs/shelljs">shelljs</a>: Unix shell commands for Node.js</li><li><a href="https://www.npmjs.com/package/log-symbols">log-symbols</a>: 打印日志的特殊标志</li><li><a href="https://www.npmjs.com/package/download-git-repo">download-git-repo</a>: Download and extract a git repository (GitHub, GitLab, Bitbucket) from node</li><li><a href="https://www.npmjs.com/package/global-prefix">global-prefix</a>: 获取 npm 全局安装的前缀</li><li><a href="https://www.npmjs.com/package/exec-sh">exec-sh</a>: 执行 shell 命令转发所有 stdio 流，比 shelljs exec 命令好用</li><li><a href="https://www.npmjs.com/package/wml">wml</a>: wml 侦听某个文件夹中的更改（使用 watchman），然后将更改的文件复制到另一个文件夹中。</li><li><a href="https://blog.ihaiu.com/cmd-open/">cmd-open</a>: 扩展 Windows 命令 open</li><li><a href="https://github.com/shelljs/shx">shx</a>: shx is a wrapper around ShellJS Unix commands, providing an easy solution for simple Unix-like, cross-platform commands in npm package scripts.</li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/614cd34a921f/</id>
    <link href="https://blog.zisheng.pro/posts/614cd34a921f/"/>
    <published>2020-02-17T07:23:35.000Z</published>
    <summary>Node.js 用途很广，常用来开发服务、桌面应用等被开发者熟知，Node.js 还有另外一个非常实用的场景 - 命令行应用（后文统称 CLI）。</summary>
    <title>从零开发一个 Node Cli</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React-Native/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/tags/React-Native/"/>
    <category term="最佳实践" scheme="https://blog.zisheng.pro/tags/%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/"/>
    <content>
      <![CDATA[<blockquote><p>文章示例源码: <a href="https://github.com/youngjuning/react-navigation-best-practice">https://github.com/youngjuning/react-navigation-best-practice</a></p></blockquote><h2 id="安装依赖"><a href="#安装依赖" class="headerlink" title="安装依赖"></a>安装依赖</h2><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add @react-navigation/native @react-navigation/stack @react-navigation/bottom-tabs react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view</span><br></pre></td></tr></table></figure><h2 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h2><p>为了完成  <code>react-native-screens</code> 的安装，添加下面两行代码到 <code>android/app/build.gradle</code> 文件的 <code>dependencies</code> 部分中：</p><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">implementation <span class="string">&#x27;androidx.appcompat:appcompat:1.1.0-rc01&#x27;</span></span><br><span class="line">implementation <span class="string">&#x27;androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha02&#x27;</span></span><br></pre></td></tr></table></figure><p>为了完成  <code>react-native-gesture-handler</code> 的安装, 在入口文件的顶部添加下面的代码, 比如 <code>index.js</code> 或 <code>App.js</code>:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">&#x27;react-native-gesture-handler&#x27;</span>;</span><br></pre></td></tr></table></figure><p>现在，我们需要把整个 App用 <code>NavigationContainer</code>包裹：</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">NavigationContainer</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@react-navigation/native&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">App</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">NavigationContainer</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;/* Rest of your app code */&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">NavigationContainer</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure><h2 id="App-js"><a href="#App-js" class="headerlink" title="App.js"></a>App.js</h2><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;</span><br><span class="line">  <span class="title class_">View</span>,</span><br><span class="line">  <span class="title class_">Text</span>,</span><br><span class="line">  <span class="title class_">StyleSheet</span>,</span><br><span class="line">  <span class="title class_">SafeAreaView</span>,</span><br><span class="line">  <span class="title class_">StatusBar</span>,</span><br><span class="line">  <span class="title class_">BackHandler</span>,</span><br><span class="line">&#125; <span class="keyword">from</span> <span class="string">&#x27;react-native&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">NavigationContainer</span>, useFocusEffect&#125; <span class="keyword">from</span> <span class="string">&#x27;@react-navigation/native&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;createBottomTabNavigator&#125; <span class="keyword">from</span> <span class="string">&#x27;@react-navigation/bottom-tabs&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;createStackNavigator, <span class="title class_">HeaderBackButton</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;@react-navigation/stack&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">IconOutline</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;@ant-design/icons-react-native&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">Button</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;@ant-design/react-native&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">IconWithBadge</span> <span class="keyword">from</span> <span class="string">&#x27;./IconWithBadge&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">HeaderButtons</span> <span class="keyword">from</span> <span class="string">&#x27;./HeaderButtons&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> getActiveRouteName <span class="keyword">from</span> <span class="string">&#x27;./getActiveRouteName&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> getScreenOptions <span class="keyword">from</span> <span class="string">&#x27;./getScreenOptions&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;navigationRef&#125; <span class="keyword">from</span> <span class="string">&#x27;./NavigationService&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">HomeScreen</span> = (<span class="params">&#123;navigation, route&#125;</span>) =&gt; &#123;</span><br><span class="line">  navigation.<span class="title function_">setOptions</span>(&#123;</span><br><span class="line">    <span class="attr">headerLeft</span>: <span class="function"><span class="params">props</span> =&gt;</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">HeaderBackButton</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        &#123;<span class="attr">...props</span>&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">onPress</span>=<span class="string">&#123;()</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-xml">          console.log(&#x27;不能再返回了！&#x27;);</span></span><br><span class="line"><span class="language-xml">        &#125;&#125;</span></span><br><span class="line"><span class="language-xml">      /&gt;</span></span><br><span class="line">    ),</span><br><span class="line">    <span class="attr">headerRight</span>: <span class="function">() =&gt;</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">HeaderButtons</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        &#123;/* title、iconName、onPress、IconComponent、iconSize、color */&#125;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">HeaderButtons.Item</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">title</span>=<span class="string">&quot;添加&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">iconName</span>=<span class="string">&quot;plus&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">onPress</span>=<span class="string">&#123;()</span> =&gt;</span> console.log(&#x27;点击了添加按钮&#x27;)&#125;</span></span><br><span class="line"><span class="language-xml">          iconSize=&#123;24&#125;</span></span><br><span class="line"><span class="language-xml">          color=&quot;#ffffff&quot;</span></span><br><span class="line"><span class="language-xml">        /&gt;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">HeaderButtons</span>&gt;</span></span></span><br><span class="line">    ),</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="title function_">useFocusEffect</span>(</span><br><span class="line">    <span class="title class_">React</span>.<span class="title function_">useCallback</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="comment">// Do something when the screen is focused</span></span><br><span class="line">      <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="comment">// Do something when the screen is unfocused</span></span><br><span class="line">        <span class="comment">// Useful for cleanup functions</span></span><br><span class="line">      &#125;;</span><br><span class="line">    &#125;, []),</span><br><span class="line">  );</span><br><span class="line">  <span class="keyword">const</span> &#123;author&#125; = route.<span class="property">params</span> || &#123;&#125;;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">StatusBar</span> <span class="attr">barStyle</span>=<span class="string">&quot;dark-content&quot;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">View</span> <span class="attr">style</span>=<span class="string">&#123;styles.container&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Text</span>&gt;</span>Home Screen<span class="tag">&lt;/<span class="name">Text</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Text</span>&gt;</span>&#123;author&#125;<span class="tag">&lt;/<span class="name">Text</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Button</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">type</span>=<span class="string">&quot;warning&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          // <span class="attr">使用</span> <span class="attr">setOptions</span> <span class="attr">更新标题</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">onPress</span>=<span class="string">&#123;()</span> =&gt;</span> navigation.setOptions(&#123;headerTitle: &#x27;Updated!&#x27;&#125;)&#125;&gt;</span></span><br><span class="line"><span class="language-xml">          Update the title</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">Button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Button</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">type</span>=<span class="string">&quot;primary&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">onPress</span>=<span class="string">&#123;()</span> =&gt;</span></span></span><br><span class="line"><span class="language-xml">            // 跳转到指定页面，并传递两个参数</span></span><br><span class="line"><span class="language-xml">            navigation.navigate(&#x27;DetailsScreen&#x27;, &#123;</span></span><br><span class="line"><span class="language-xml">              otherParam: &#x27;anything you want here&#x27;,</span></span><br><span class="line"><span class="language-xml">            &#125;)</span></span><br><span class="line"><span class="language-xml">          &#125;&gt;</span></span><br><span class="line"><span class="language-xml">          Go to DetailsScreen</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">Button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Button</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">type</span>=<span class="string">&quot;warning&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">onPress</span>=<span class="string">&#123;()</span> =&gt;</span> navigation.navigate(&#x27;SafeAreaViewScreen&#x27;)&#125;&gt;</span></span><br><span class="line"><span class="language-xml">          Go SafeAreaViewScreen</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">Button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Button</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">type</span>=<span class="string">&quot;primary&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">onPress</span>=<span class="string">&#123;()</span> =&gt;</span></span></span><br><span class="line"><span class="language-xml">            navigation.navigate(&#x27;CustomAndroidBackButtonBehaviorScreen&#x27;)</span></span><br><span class="line"><span class="language-xml">          &#125;&gt;</span></span><br><span class="line"><span class="language-xml">          Go CustomAndroidBackButtonBehavior</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">Button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">View</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">DetailsScreen</span> = (<span class="params">&#123;navigation, route&#125;</span>) =&gt; &#123;</span><br><span class="line">  <span class="comment">// 通过 props.route.params 接收参数</span></span><br><span class="line">  <span class="keyword">const</span> &#123;itemId, otherParam&#125; = route.<span class="property">params</span>;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">View</span> <span class="attr">style</span>=<span class="string">&#123;styles.container&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Text</span>&gt;</span>Details Screen<span class="tag">&lt;/<span class="name">Text</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Text</span>&gt;</span>itemId: &#123;itemId&#125;<span class="tag">&lt;/<span class="name">Text</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Text</span>&gt;</span>otherParam: &#123;otherParam&#125;<span class="tag">&lt;/<span class="name">Text</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Button</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">type</span>=<span class="string">&quot;primary&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        // <span class="attr">返回上一页</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">onPress</span>=<span class="string">&#123;()</span> =&gt;</span> navigation.goBack()&#125;&gt;</span></span><br><span class="line"><span class="language-xml">        Go back</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">Button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Button</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">type</span>=<span class="string">&quot;primary&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        // <span class="attr">如果返回上一个页面需要传递参数</span>，<span class="attr">请使用</span> <span class="attr">navigate</span> <span class="attr">方法</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">onPress</span>=<span class="string">&#123;()</span> =&gt;</span> navigation.navigate(&#x27;HomeScreen&#x27;, &#123;author: &#x27;紫升&#x27;&#125;)&#125;&gt;</span></span><br><span class="line"><span class="language-xml">        Go back with Params</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">Button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">View</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">SettingsScreen</span> = (<span class="params">&#123;navigation, route&#125;</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">SafeAreaView</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">style</span>=<span class="string">&#123;&#123;flex:</span> <span class="attr">1</span>, <span class="attr">justifyContent:</span> &#x27;<span class="attr">space-between</span>&#x27;, <span class="attr">alignItems:</span> &#x27;<span class="attr">center</span>&#x27;&#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Text</span>&gt;</span>This is top text.<span class="tag">&lt;/<span class="name">Text</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Text</span>&gt;</span>This is bottom text.<span class="tag">&lt;/<span class="name">Text</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">SafeAreaView</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">SafeAreaViewScreen</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">SafeAreaView</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">style</span>=<span class="string">&#123;&#123;flex:</span> <span class="attr">1</span>, <span class="attr">justifyContent:</span> &#x27;<span class="attr">space-between</span>&#x27;, <span class="attr">alignItems:</span> &#x27;<span class="attr">center</span>&#x27;&#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Text</span>&gt;</span>This is top text.<span class="tag">&lt;/<span class="name">Text</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Text</span>&gt;</span>This is bottom text.<span class="tag">&lt;/<span class="name">Text</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">SafeAreaView</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">CustomAndroidBackButtonBehaviorScreen</span> = (<span class="params">&#123;navigation, route&#125;</span>) =&gt; &#123;</span><br><span class="line">  <span class="title function_">useFocusEffect</span>(</span><br><span class="line">    <span class="title class_">React</span>.<span class="title function_">useCallback</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> <span class="title function_">onBackPress</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">        <span class="title function_">alert</span>(<span class="string">&#x27;物理返回键被拦截了！&#x27;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">      &#125;;</span><br><span class="line"></span><br><span class="line">      <span class="title class_">BackHandler</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;hardwareBackPress&#x27;</span>, onBackPress);</span><br><span class="line"></span><br><span class="line">      <span class="keyword">return</span> <span class="function">() =&gt;</span></span><br><span class="line">        <span class="title class_">BackHandler</span>.<span class="title function_">removeEventListener</span>(<span class="string">&#x27;hardwareBackPress&#x27;</span>, onBackPress);</span><br><span class="line">    &#125;, []),</span><br><span class="line">  );</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">View</span> <span class="attr">style</span>=<span class="string">&#123;styles.container&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Text</span>&gt;</span>AndroidBackHandlerScreen<span class="tag">&lt;/<span class="name">Text</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">View</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Stack</span> = <span class="title function_">createStackNavigator</span>();</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">BottomTab</span> = <span class="title function_">createBottomTabNavigator</span>();</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">BottomTabScreen</span> = (<span class="params"></span>) =&gt; (</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">BottomTab.Navigator</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    <span class="attr">screenOptions</span>=<span class="string">&#123;(&#123;route&#125;)</span> =&gt;</span> (&#123;</span></span><br><span class="line"><span class="language-xml">      tabBarIcon: (&#123;focused, color, size&#125;) =&gt; &#123;</span></span><br><span class="line"><span class="language-xml">        let iconName;</span></span><br><span class="line"><span class="language-xml">        if (route.name === &#x27;HomeScreen&#x27;) &#123;</span></span><br><span class="line"><span class="language-xml">          iconName = focused ? &#x27;apple&#x27; : &#x27;apple&#x27;;</span></span><br><span class="line"><span class="language-xml">          return (</span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">IconWithBadge</span> <span class="attr">badgeCount</span>=<span class="string">&#123;90&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">              <span class="tag">&lt;<span class="name">IconOutline</span> <span class="attr">name</span>=<span class="string">&#123;iconName&#125;</span> <span class="attr">size</span>=<span class="string">&#123;size&#125;</span> <span class="attr">color</span>=<span class="string">&#123;color&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;/<span class="name">IconWithBadge</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          );</span></span><br><span class="line"><span class="language-xml">        &#125; else if (route.name === &#x27;SettingsScreen&#x27;) &#123;</span></span><br><span class="line"><span class="language-xml">          iconName = focused ? &#x27;twitter&#x27; : &#x27;twitter&#x27;;</span></span><br><span class="line"><span class="language-xml">        &#125;</span></span><br><span class="line"><span class="language-xml">        return <span class="tag">&lt;<span class="name">IconOutline</span> <span class="attr">name</span>=<span class="string">&#123;iconName&#125;</span> <span class="attr">size</span>=<span class="string">&#123;size&#125;</span> <span class="attr">color</span>=<span class="string">&#123;color&#125;</span> /&gt;</span>;</span></span><br><span class="line"><span class="language-xml">      &#125;,</span></span><br><span class="line"><span class="language-xml">    &#125;)&#125;</span></span><br><span class="line"><span class="language-xml">    tabBarOptions=&#123;&#123;</span></span><br><span class="line"><span class="language-xml">      activeTintColor: &#x27;tomato&#x27;,</span></span><br><span class="line"><span class="language-xml">      inactiveTintColor: &#x27;gray&#x27;,</span></span><br><span class="line"><span class="language-xml">    &#125;&#125;&gt;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">Stack.Screen</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">name</span>=<span class="string">&quot;HomeScreen&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">component</span>=<span class="string">&#123;HomeScreen&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">options</span>=<span class="string">&#123;&#123;tabBarLabel:</span> &#x27;<span class="attr">首页</span>&#x27;&#125;&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">Stack.Screen</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">name</span>=<span class="string">&quot;SettingsScreen&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">component</span>=<span class="string">&#123;SettingsScreen&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">options</span>=<span class="string">&#123;&#123;tabBarLabel:</span> &#x27;<span class="attr">设置</span>&#x27;&#125;&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    /&gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/<span class="name">BottomTab.Navigator</span>&gt;</span></span></span><br><span class="line">);</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">App</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> routeNameRef = <span class="title class_">React</span>.<span class="title function_">useRef</span>();</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">NavigationContainer</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">ref</span>=<span class="string">&#123;navigationRef&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">onStateChange</span>=<span class="string">&#123;state</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-xml">          const previousRouteName = routeNameRef.current;</span></span><br><span class="line"><span class="language-xml">          const currentRouteName = getActiveRouteName(state);</span></span><br><span class="line"><span class="language-xml">          if (previousRouteName !== currentRouteName) &#123;</span></span><br><span class="line"><span class="language-xml">            console.log(&#x27;[onStateChange]&#x27;, currentRouteName);</span></span><br><span class="line"><span class="language-xml">            if (currentRouteName === &#x27;HomeScreen&#x27;) &#123;</span></span><br><span class="line"><span class="language-xml">              StatusBar.setBarStyle(&#x27;dark-content&#x27;); // 修改 StatusBar</span></span><br><span class="line"><span class="language-xml">            &#125; else &#123;</span></span><br><span class="line"><span class="language-xml">              StatusBar.setBarStyle(&#x27;dark-content&#x27;); // 修改 StatusBar</span></span><br><span class="line"><span class="language-xml">            &#125;</span></span><br><span class="line"><span class="language-xml">          &#125;</span></span><br><span class="line"><span class="language-xml">          // Save the current route name for later comparision</span></span><br><span class="line"><span class="language-xml">          routeNameRef.current = currentRouteName;</span></span><br><span class="line"><span class="language-xml">        &#125;&#125;&gt;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Stack.Navigator</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">initialRouteName</span>=<span class="string">&quot;HomeScreen&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          // <span class="attr">页面共享的配置</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">screenOptions</span>=<span class="string">&#123;getScreenOptions()&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Stack.Screen</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">name</span>=<span class="string">&quot;BottomTabScreen&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">component</span>=<span class="string">&#123;BottomTabScreen&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">options</span>=<span class="string">&#123;&#123;headerShown:</span> <span class="attr">false</span>&#125;&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          /&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Stack.Screen</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">name</span>=<span class="string">&quot;DetailsScreen&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">component</span>=<span class="string">&#123;DetailsScreen&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">options</span>=<span class="string">&#123;&#123;headerTitle:</span> &#x27;<span class="attr">详情</span>&#x27;&#125;&#125; // <span class="attr">headerTitle</span> <span class="attr">用来设置标题栏</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">initialParams</span>=<span class="string">&#123;&#123;itemId:</span> <span class="attr">42</span>&#125;&#125; // <span class="attr">默认参数</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          /&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Stack.Screen</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">name</span>=<span class="string">&quot;SafeAreaViewScreen&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">component</span>=<span class="string">&#123;SafeAreaViewScreen&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">options</span>=<span class="string">&#123;&#123;headerTitle:</span> &#x27;<span class="attr">SafeAreaView</span>&#x27;&#125;&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          /&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Stack.Screen</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">name</span>=<span class="string">&quot;CustomAndroidBackButtonBehaviorScreen&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">component</span>=<span class="string">&#123;CustomAndroidBackButtonBehaviorScreen&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">options</span>=<span class="string">&#123;&#123;headerTitle:</span> &#x27;<span class="attr">拦截安卓物理返回键</span>&#x27;&#125;&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          /&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">Stack.Navigator</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">NavigationContainer</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> styles = <span class="title class_">StyleSheet</span>.<span class="title function_">create</span>(&#123;</span><br><span class="line">  <span class="attr">container</span>: &#123;</span><br><span class="line">    <span class="attr">flex</span>: <span class="number">1</span>,</span><br><span class="line">    <span class="attr">alignItems</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">    <span class="attr">justifyContent</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure><blockquote><p>路由名称的大小写无关紧要 – 你可以使用小写字母<code>home</code>或大写字母<code>Home</code>，这取决于你的喜好。 我们更喜欢将路由名称大写。 我们更喜欢利用我们的路由名称。</p></blockquote><blockquote><p>跳转方法有 <code>navigate</code>、 <code>push</code> 、<code>goBack</code>、<code>popToTop</code></p></blockquote><blockquote><p>可以用 <code>navigation.setParams</code> 方法更新页面的参数</p></blockquote><blockquote><p>我们可以通过 <code>options={({ route, navigation }) =&gt; ({ headerTitle: route.params.name })}</code> 的方式在标题中使用参数</p></blockquote><blockquote><p>我们可以用 <code>navigation.setOptions</code> 更新页面配置</p></blockquote><ul><li><code>Stack.Navigator</code><ul><li><code>initialRouteName</code> : 用来配置 <code>Stack.Navigator</code> 的初始路由</li><li><code>screenOptions</code>: 页面共享配置对象</li></ul></li><li><code>Stack.Screen</code><ul><li><code>name</code>: 页面名</li><li><code>component</code>: 页面对应组件</li><li><code>options</code>: 页面配置对象</li><li><code>initialParams</code>: 默认参数</li></ul></li></ul><h2 id="HeaderButtons-js"><a href="#HeaderButtons-js" class="headerlink" title="HeaderButtons.js"></a>HeaderButtons.js</h2><p>使用 <code>react-navigation-header-buttons</code> 组件搭配任意 Icon 组件可以自定义自己的 Header Button 组件，我这里为了演示方便，使用了 <code>@ant-design/icons-react-native</code>：</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;</span><br><span class="line">  <span class="title class_">HeaderButtons</span> <span class="keyword">as</span> <span class="title class_">RNHeaderButtons</span>,</span><br><span class="line">  <span class="title class_">HeaderButton</span> <span class="keyword">as</span> <span class="title class_">RNHeaderButton</span>,</span><br><span class="line">  <span class="title class_">Item</span>,</span><br><span class="line">&#125; <span class="keyword">from</span> <span class="string">&#x27;react-navigation-header-buttons&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">IconOutline</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;@ant-design/icons-react-native&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">HeaderButton</span> = props =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">RNHeaderButton</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      &#123;<span class="attr">...props</span>&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">IconComponent</span>=<span class="string">&#123;IconOutline&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">iconSize</span>=<span class="string">&#123;props.iconSize</span> || <span class="attr">23</span>&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">color</span>=<span class="string">&#123;props.color</span> || &#x27;#<span class="attr">000000</span>&#x27;&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    /&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">HeaderButtons</span> = props =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">RNHeaderButtons</span> <span class="attr">HeaderButtonComponent</span>=<span class="string">&#123;HeaderButton&#125;</span> &#123;<span class="attr">...props</span>&#125; /&gt;</span></span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="title class_">HeaderButtons</span>.<span class="property">Item</span> = <span class="title class_">Item</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">HeaderButtons</span>;</span><br></pre></td></tr></table></figure><h2 id="IconWithBadge-js"><a href="#IconWithBadge-js" class="headerlink" title="IconWithBadge.js"></a>IconWithBadge.js</h2><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">View</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;react-native&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">Badge</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;@ant-design/react-native&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">IconWithBadge</span> = (<span class="params">&#123;children, badgeCount, ...props&#125;</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">View</span> <span class="attr">style</span>=<span class="string">&#123;&#123;width:</span> <span class="attr">24</span>, <span class="attr">height:</span> <span class="attr">24</span>, <span class="attr">margin:</span> <span class="attr">5</span>&#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;children&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Badge</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        &#123;<span class="attr">...props</span>&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">style</span>=<span class="string">&#123;&#123;position:</span> &#x27;<span class="attr">absolute</span>&#x27;, <span class="attr">right:</span> <span class="attr">-6</span>, <span class="attr">top:</span> <span class="attr">-3</span>&#125;&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">text</span>=<span class="string">&#123;badgeCount&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">View</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">IconWithBadge</span>;</span><br></pre></td></tr></table></figure><h2 id="getActiveRouteName-js"><a href="#getActiveRouteName-js" class="headerlink" title="getActiveRouteName.js"></a>getActiveRouteName.js</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Gets the current screen from navigation state</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> <span class="variable">state</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">getActiveRouteName</span> = state =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> route = state.<span class="property">routes</span>[state.<span class="property">index</span>];</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (route.<span class="property">state</span>) &#123;</span><br><span class="line">    <span class="comment">// Dive into nested navigators</span></span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">getActiveRouteName</span>(route.<span class="property">state</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> route.<span class="property">name</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> getActiveRouteName;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="getScreenOptions-js"><a href="#getScreenOptions-js" class="headerlink" title="getScreenOptions.js"></a>getScreenOptions.js</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">TransitionPresets</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;@react-navigation/stack&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">getScreenOptions</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="attr">headerStyle</span>: &#123;</span><br><span class="line">      <span class="attr">backgroundColor</span>: <span class="string">&#x27;#ffffff&#x27;</span>,</span><br><span class="line">    &#125;, <span class="comment">// 一个应用于 header 的最外层 View 的 样式对象</span></span><br><span class="line">    <span class="attr">headerTintColor</span>: <span class="string">&#x27;#000000&#x27;</span>, <span class="comment">// 返回按钮和标题都使用这个属性作为它们的颜色</span></span><br><span class="line">    <span class="attr">headerTitleStyle</span>: &#123;</span><br><span class="line">      <span class="attr">fontWeight</span>: <span class="string">&#x27;bold&#x27;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">headerBackTitleVisible</span>: <span class="literal">false</span>,</span><br><span class="line">    <span class="attr">headerTitleAlign</span>: <span class="string">&#x27;center&#x27;</span>,</span><br><span class="line">    <span class="attr">cardStyle</span>: &#123;</span><br><span class="line">      <span class="attr">flex</span>: <span class="number">1</span>,</span><br><span class="line">      <span class="attr">backgroundColor</span>: <span class="string">&#x27;#f5f5f9&#x27;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    ...<span class="title class_">TransitionPresets</span>.<span class="property">SlideFromRightIOS</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> getScreenOptions;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="NavigationService-js"><a href="#NavigationService-js" class="headerlink" title="NavigationService.js"></a>NavigationService.js</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> navigationRef = <span class="title class_">React</span>.<span class="title function_">createRef</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">navigate</span> = (<span class="params">name, params</span>) =&gt; &#123;</span><br><span class="line">  navigationRef.<span class="property">current</span> &amp;&amp; navigationRef.<span class="property">current</span>.<span class="title function_">navigate</span>(name, params);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">getNavigation</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> navigationRef.<span class="property">current</span> &amp;&amp; navigationRef.<span class="property">current</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  navigate,</span><br><span class="line">  getNavigation,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="页面生命周期与React-Navigation"><a href="#页面生命周期与React-Navigation" class="headerlink" title="页面生命周期与React Navigation"></a>页面生命周期与React Navigation</h2><p>一个包含 页面 A 和 B 的 StackNavigator ，当跳转到 A 时，<code>componentDidMount</code> 方法会被调用； 当跳转到 B 时，<code>componentDidMount</code> 方法也会被调用，但是 A 依然在堆栈中保持 被加载状态，他的 <code>componentWillUnMount</code> 也不会被调用。</p><p>当从 B 跳转到 A，B的 <code>componentWillUnmount</code> 方法会被调用，但是 A 的 <code>componentDidMount</code>方法不会被调用，应为此时 A 依然是被加载状态。</p><h2 id="React-Navigation-生命周期事件"><a href="#React-Navigation-生命周期事件" class="headerlink" title="React Navigation 生命周期事件"></a>React Navigation 生命周期事件</h2><h3 id="addListener"><a href="#addListener" class="headerlink" title="addListener"></a>addListener</h3><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Profile</span>(<span class="params">&#123; navigation &#125;</span>) &#123;</span><br><span class="line">  <span class="title class_">React</span>.<span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> unsubscribe = navigation.<span class="title function_">addListener</span>(<span class="string">&#x27;focus&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="comment">// Screen was focused</span></span><br><span class="line">      <span class="comment">// Do something</span></span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> unsubscribe;</span><br><span class="line">  &#125;, [navigation]);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">ProfileContent</span> /&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="useFocusEffect"><a href="#useFocusEffect" class="headerlink" title="useFocusEffect"></a>useFocusEffect</h3><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">useFocusEffect</span>(</span><br><span class="line">    <span class="title class_">React</span>.<span class="title function_">useCallback</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="comment">// Do something when the screen is focused</span></span><br><span class="line">      <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="comment">// Do something when the screen is unfocused</span></span><br><span class="line">        <span class="comment">// Useful for cleanup functions</span></span><br><span class="line">      &#125;;</span><br><span class="line">    &#125;, []),</span><br><span class="line">  );</span><br></pre></td></tr></table></figure><h2 id="隐藏-Header-TabBar"><a href="#隐藏-Header-TabBar" class="headerlink" title="隐藏 Header&#x2F;TabBar"></a>隐藏 Header&#x2F;TabBar</h2><ul><li><code>headerMode:&quot;none&quot;</code>: hide Header for <code>Stack.Navigator</code></li><li><code>headerShown:false</code>: hide Header for <code>Stack.Screen</code></li><li><code>tabBar={() =&gt; null}</code>: hide TabBar for <code>BottomTab.Navigator</code></li></ul><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">NavigationContainer</span>, useFocusEffect&#125; <span class="keyword">from</span> <span class="string">&#x27;@react-navigation/native&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;createStackNavigator, <span class="title class_">TransitionPresets</span>, <span class="title class_">HeaderBackButton</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;@react-navigation/stack&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;createBottomTabNavigator&#125; <span class="keyword">from</span> <span class="string">&#x27;@react-navigation/bottom-tabs&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Stack</span> = <span class="title function_">createStackNavigator</span>();</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">BottomTab</span> = <span class="title function_">createBottomTabNavigator</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span> = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">NavigationContainer</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;<span class="name">Stack.Navigator</span> <span class="attr">headerMode</span>=<span class="string">&quot;none&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Stack.Screen</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">...</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">options</span>=<span class="string">&#123;&#123;</span> <span class="attr">headerShown:</span> <span class="attr">false</span> &#125;&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Stack.Screen</span> <span class="attr">...</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        &#123;() =&gt; (</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">BottomTab.Navigator</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">            <span class="attr">...</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">           <span class="attr">tabBar</span>=<span class="string">&#123;()</span> =&gt;</span> null&#125;</span></span><br><span class="line"><span class="language-xml">          &gt;</span></span><br><span class="line"><span class="language-xml">            ...</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;/<span class="name">BottomTab.Navigator</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        )&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">Stack.Screen</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">Stack.Navigator</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/<span class="name">NavigationContainer</span>&gt;</span></span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="TabBar-的-StatusBar-不同"><a href="#TabBar-的-StatusBar-不同" class="headerlink" title="TabBar 的 StatusBar 不同"></a>TabBar 的 StatusBar 不同</h2><p>一般我们会对特殊的那个TabBar进行处理。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">getActiveRouteName</span> = state =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> route = state.<span class="property">routes</span>[state.<span class="property">index</span>];</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (route.<span class="property">state</span>) &#123;</span><br><span class="line">    <span class="comment">// Dive into nested navigators</span></span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">getActiveRouteName</span>(route.<span class="property">state</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> route.<span class="property">name</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">App</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> ref = <span class="title class_">React</span>.<span class="title function_">useRef</span>(<span class="literal">null</span>);</span><br><span class="line"><span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">    &#123;/* 访问 ref.current?.navigate */&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">NavigationContainer</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">ref</span>=<span class="string">&#123;ref&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">onStateChange</span>=<span class="string">&#123;state</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-xml">          const previousRouteName = ref.current;</span></span><br><span class="line"><span class="language-xml">          const currentRouteName = getActiveRouteName(state);</span></span><br><span class="line"><span class="language-xml">          if (previousRouteName !== currentRouteName) &#123;</span></span><br><span class="line"><span class="language-xml">            console.log(&#x27;[onStateChange]&#x27;, currentRouteName);</span></span><br><span class="line"><span class="language-xml">            if (currentRouteName === &#x27;HomeScreen&#x27;) &#123;</span></span><br><span class="line"><span class="language-xml">              StatusBar.setBarStyle(&#x27;dark-content&#x27;);  // 修改 StatusBar</span></span><br><span class="line"><span class="language-xml">            &#125; else &#123;</span></span><br><span class="line"><span class="language-xml">              StatusBar.setBarStyle(&#x27;dark-content&#x27;);  // 修改 StatusBar</span></span><br><span class="line"><span class="language-xml">            &#125;</span></span><br><span class="line"><span class="language-xml">          &#125;</span></span><br><span class="line"><span class="language-xml">        &#125;&#125;</span></span><br><span class="line"><span class="language-xml">      &gt;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">NavigationContainer</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="监听安卓物理返回键"><a href="#监听安卓物理返回键" class="headerlink" title="监听安卓物理返回键"></a>监听安卓物理返回键</h2><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">View</span>, <span class="title class_">Text</span>, <span class="title class_">BackHandler</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;react-native&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">CustomAndroidBackButtonBehaviorScreen</span> = (<span class="params">&#123;navigation, route&#125;</span>) =&gt; &#123;</span><br><span class="line">  <span class="title function_">useFocusEffect</span>(</span><br><span class="line">    <span class="title class_">React</span>.<span class="title function_">useCallback</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> <span class="title function_">onBackPress</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">        <span class="title function_">alert</span>(<span class="string">&#x27;物理返回键被拦截了！&#x27;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">      &#125;;</span><br><span class="line">      <span class="title class_">BackHandler</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;hardwareBackPress&#x27;</span>, onBackPress);</span><br><span class="line">      <span class="keyword">return</span> <span class="function">() =&gt;</span></span><br><span class="line">        <span class="title class_">BackHandler</span>.<span class="title function_">removeEventListener</span>(<span class="string">&#x27;hardwareBackPress&#x27;</span>, onBackPress);</span><br><span class="line">    &#125;, []),</span><br><span class="line">  );</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">View</span> <span class="attr">style</span>=<span class="string">&#123;styles.container&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Text</span>&gt;</span>AndroidBackHandlerScreen<span class="tag">&lt;/<span class="name">Text</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">View</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="在子组件中访问-navigation"><a href="#在子组件中访问-navigation" class="headerlink" title="在子组件中访问 navigation"></a>在子组件中访问 <code>navigation</code></h2><p>我们可以通过 <code>useNavigation()</code> hook 来访问 navigation，再也不用传递多层 <code>navigation</code></p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Button</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;react-native&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; useNavigation &#125; <span class="keyword">from</span> <span class="string">&#x27;@react-navigation/native&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">GoToButton</span>(<span class="params">&#123; screenName &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> navigation = <span class="title function_">useNavigation</span>();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">Button</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">title</span>=<span class="string">&#123;</span>`<span class="attr">Go</span> <span class="attr">to</span> $&#123;<span class="attr">screenName</span>&#125;`&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">onPress</span>=<span class="string">&#123;()</span> =&gt;</span> navigation.navigate(screenName)&#125;</span></span><br><span class="line"><span class="language-xml">    /&gt;</span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="给页面传递额外的属性"><a href="#给页面传递额外的属性" class="headerlink" title="给页面传递额外的属性"></a>给页面传递额外的属性</h2><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&lt;<span class="title class_">Stack</span>.<span class="property">Screen</span></span><br><span class="line">  name=<span class="string">&quot;HomeScreen&quot;</span></span><br><span class="line">  options=&#123;&#123;<span class="attr">headerTitle</span>: <span class="string">&#x27;首页&#x27;</span>&#125;&#125;&gt;</span><br><span class="line">  &#123;<span class="function"><span class="params">props</span> =&gt;</span> <span class="language-xml"><span class="tag">&lt;<span class="name">HomeScreen</span> &#123;<span class="attr">...props</span>&#125; <span class="attr">extraData</span>=<span class="string">&#123;&#123;author:</span> &#x27;<span class="attr">紫升</span>&#x27;&#125;&#125; /&gt;</span></span>&#125;</span><br><span class="line">&lt;/<span class="title class_">Stack</span>.<span class="property">Screen</span>&gt;</span><br></pre></td></tr></table></figure><h2 id="获取-Header-Height"><a href="#获取-Header-Height" class="headerlink" title="获取 Header Height"></a>获取 Header Height</h2><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useHeaderHeight &#125; <span class="keyword">from</span> <span class="string">&#x27;@react-navigation/stack&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">App</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> <span class="title class_">HeaderHeight</span> = <span class="title function_">useHeaderHeight</span>() <span class="comment">// 获取Header Height</span></span><br><span class="line">    <span class="keyword">return</span>(...)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span></span><br></pre></td></tr></table></figure><h2 id="继续使用类组件"><a href="#继续使用类组件" class="headerlink" title="继续使用类组件"></a>继续使用类组件</h2><p>考虑到对于不适应 Hooks 的但是业务又很紧急的场景，我们可以再类组件之上封装一层来支持 React Navigation 的 Hooks 组件，之所以这么做，起因是因为 React Navigation 5 中我们只能通过 <code>useHeaderHeight()</code> 方法获取标题栏高度。</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Albums</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">ScrollView</span> <span class="attr">ref</span>=<span class="string">&#123;this.props.scrollRef&#125;</span>&gt;</span>&#123;/* content */&#125;<span class="tag">&lt;/<span class="name">ScrollView</span>&gt;</span></span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 封装并导出</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span>(<span class="params">props</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> ref = <span class="title class_">React</span>.<span class="title function_">useRef</span>(<span class="literal">null</span>);</span><br><span class="line">  <span class="title function_">useScrollToTop</span>(ref);</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">Albums</span> &#123;<span class="attr">...props</span>&#125; <span class="attr">scrollRef</span>=<span class="string">&#123;ref&#125;</span> /&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/863ed71c199e/</id>
    <link href="https://blog.zisheng.pro/posts/863ed71c199e/"/>
    <published>2020-02-17T02:29:14.000Z</published>
    <summary>本文记录了 React Navigation 5 最佳实践</summary>
    <title>React Navigation 5 最佳实践</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="JavaScript" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/JavaScript/"/>
    <category term="JavaScript" scheme="https://blog.zisheng.pro/tags/JavaScript/"/>
    <category term="闭包" scheme="https://blog.zisheng.pro/tags/%E9%97%AD%E5%8C%85/"/>
    <category term="循环" scheme="https://blog.zisheng.pro/tags/%E5%BE%AA%E7%8E%AF/"/>
    <category term="词法作用域" scheme="https://blog.zisheng.pro/tags/%E8%AF%8D%E6%B3%95%E4%BD%9C%E7%94%A8%E5%9F%9F/"/>
    <content>
      <![CDATA[<p>当函数可以记住并访问所在词法作用域时，就产生了闭包，即使函数是在当前词法作用域之外执行的。</p><p>要说明循环闭包，for 循环是最常见的例子。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span>(<span class="keyword">var</span> i=<span class="number">1</span>; i&lt;=<span class="number">5</span>; i++)&#123;</span><br><span class="line">  <span class="built_in">setTimeout</span>(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(i)</span><br><span class="line">  &#125;, i * <span class="number">1000</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>由于很多开发者对闭包的概念认识地不是很清楚，因此当循环内部包含函数定义时，代码格式检查器经常发出警告，我们在这里介绍如何才能正确使用闭包并发挥它的威力，但是代码格式检查器并没有那么灵敏，它会假设你并不真正了解自己在做什么，所以无论如何都会发出警告。</p></blockquote><p>正常情况下，我们对这段代码行为的预期是分别输出数字 1-5，每秒一次，每次一个。</p><p>但实际上，这段代码在运行时会以每秒一次的频率输出五次 <code>6</code>.</p><p>这是为什么？</p><p>首先解释 6 是从哪里来的。这个循环的终止条件是 <code>i</code> 不在 <code>&lt;=5</code>。条件首次成立时 <code>i</code> 的值是 <code>6</code>。因此，输出显示的是循环结束时 <code>i</code> 的最终值。</p><p>仔细想一下，这好像又是显而易见的，延迟函数的回调会在循环结束时才执行。事实上，当定时器运行时即使每个迭代中执行的是 <code>setTimeout(..., 0)</code>，所有的回调函数依然是在循环结束后才被执行，因此会每次输出一个 6 出来。</p><p>这里引申出一个更深入的问题，代码中到底有什么缺陷导致它的行为同语义所暗示的不一致呢？</p><p>缺陷是我们试图假设循环中的每个迭代在运行时都会给自己 “捕获” 一个 <code>i</code> 的副本。但是个根据作用域的工作原理，实际情况是尽管循环中的五个函数是在各个迭代中分别定义的，但是<strong>它们都被封闭在一个共享的全局作用域中</strong>，因此实际上只有一个 <code>i</code>。</p><p>这样的话，当然所有函数共享一个 <code>i</code> 的引用。循环结构让我们误以为背后还有更复杂的机制在起作用，但实际上没有。如果将延迟函数的回调重复定义五次，完全不使用循环，那它同这段代码时完全等价的。</p><p>下面回到正题。缺陷是什么？我们需要更多的闭包作用域，特别是在循环的过程中每个迭代都需要一个闭包作用域。</p><h3 id="IIFE"><a href="#IIFE" class="headerlink" title="IIFE"></a>IIFE</h3><p>通过 IIFE 会立即执行一个函数来创建作用域。我们可以利用 IIFE 的特性来创建一个作用域并保存每次循环的 <code>i</code>：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span>(<span class="keyword">var</span> i; i &lt;= <span class="number">5</span>; i++) &#123;</span><br><span class="line">  (<span class="keyword">function</span>(<span class="params">j</span>) &#123;</span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(j)</span><br><span class="line">    &#125;, j *<span class="number">1000</span>)</span><br><span class="line">  &#125;)(i)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当然，这些 IIFE 也不过就是函数，因此我们可以将 i 传递进去，如果愿意的话可以将变量名定义为 j，当然也可以还叫做 i。无论如何这段代码现在可以工作了。</p><p>在迭代内使用 IIFE 会为每个迭代都生成一个新的作用域，使得延迟函数的回调可以将新的作用域封闭在每个迭代内部，每个迭代中都会含有一个具有正确值的变量供我们访问。</p><h3 id="let"><a href="#let" class="headerlink" title="let"></a>let</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span>(<span class="keyword">let</span> i=<span class="number">1</span>; i&lt;= <span class="number">5</span>; i++) &#123; <span class="comment">// 通过let创建闭包的作用域</span></span><br><span class="line">  <span class="built_in">setTimeout</span>(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(i)</span><br><span class="line">  &#125;, i++)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>let 隐式地创建了一个作用域，起到了闭包的作用。很酷是吧？块作用域和闭包联手便可天下无敌。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/082429a479e9/</id>
    <link href="https://blog.zisheng.pro/posts/082429a479e9/"/>
    <published>2020-02-16T15:57:59.000Z</published>
    <summary>本文介绍了 JavaScript 中的循环闭包</summary>
    <title>JavaScript 循环闭包</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React-Native/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/tags/React-Native/"/>
    <category term="metro" scheme="https://blog.zisheng.pro/tags/metro/"/>
    <content>
      <![CDATA[<p>一个 React Native 应用中可能包含许多不同的业务，这些不同的业务很可能是不同部门开发的，这样一个库中就有许许多多的重复的 React Native 代码和第三方库代码。</p><p>所以一般做法都是将重复的RN代码和第三方库打包成一个基础包，然后各个业务在基础包的基础上进行开发，这样做的好处是可以降低对内存的占用，减少加载时间，减少热更新时流量带宽等，在优化方面起到了非常大的作用。</p><h2 id="拆包方案选择"><a href="#拆包方案选择" class="headerlink" title="拆包方案选择"></a>拆包方案选择</h2><p>目前，最好的RN分包方案还是facebook官方提供的<a href="https://facebook.github.io/metro/docs/en/getting-started">metro bundle</a>，此方案是fb在0.50版本引入的，并随着RN版本的迭代不断完善。也即是说，只要你使用的是0.50以上的RN版本，就可以使用metro bundle进行差分包进行热更新。</p><p>我们选择 react-native-multibundler 作为我们的拆包方案，它基于react native的metro bundler的配置化开发来处理分包，支持iOS和Android，metro bundler为官方打包的工具，使用官方分包方法更灵活稳定，比网上的一些方法更实用可靠。</p><p>下图是我们的目标架构：</p><img src="https://i.loli.net/2019/10/12/AYqMTmdfp5jRlEi.png" style="zoom: 50%;" /><h2 id="metro-bundle"><a href="#metro-bundle" class="headerlink" title="metro bundle"></a>metro bundle</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">react-native bundle</span><br><span class="line">\ --platform android</span><br><span class="line">\ --dev false</span><br><span class="line">\ --entry-file platformDep.js</span><br><span class="line">\ --bundle-output ./android/app/src/main/assets/platform.android.bundle</span><br><span class="line">\ --assets-dest android/app/src/main/res/</span><br><span class="line">\ --config /&#123;你的绝对路径&#125;/platform57.config.js</span><br></pre></td></tr></table></figure><ul><li><code>--platform [string]</code>：ios 或 android（默认ios）</li><li><code>--dev [boolean]</code>：打正式包请设置为false（默认true）</li><li><code>--entry-file &lt;path&gt;</code> ：打包入口文件，绝对路径、相对路径都可以</li><li>s&#96;–bundle-output [string]：bundle包存放的位置</li><li><code>--assets-dest [string]</code> ：存放静态资源的文件夹</li><li><code>--config [string]</code>：CLI 配置文件的路径，根据基础包业务包的不同，添加 <code>--config &lt;path/to/config&gt;</code> 参数对相应入口文件打包。</li></ul><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><ul><li><a href="https://juejin.im/post/5cee0095f265da1b6d4006ec">React Native 拆包及实践「iOS&amp;Android」</a></li><li><a href="https://www.zhuanzhi.ai/document/70cdd36e33264f337529e3a18c27215c">React Native 分包哪家强？看这文就够了！</a></li><li><a href="https://github.com/ctripcorp/CRN">ctripcorp&#x2F;CRN</a>：偏向于性能优化，拆包这块不够详细</li><li><a href="https://github.com/smallnew/react-native-multibundler">react-native-multibundler</a>：react native可视化bundle拆包,支持debug,支持官方的0.57~0.60版本,使用官方的metro拆包,适用于Android、iOS</li><li><a href="https://juejin.im/post/5d906e19f265da5b9c3cf2ea">一种强大、可靠的React Native拆包以及热更新方案，基于CodePush，Metro</a></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/c2664d63468a/</id>
    <link href="https://blog.zisheng.pro/posts/c2664d63468a/"/>
    <published>2020-02-15T08:56:19.000Z</published>
    <summary>一个 React Native 应用中可能包含许多不同的业务，这些不同的业务很可能是不同部门开发的，这样一个库中就有许许多多的重复的 React Native 代码和第三方库代码。</summary>
    <title>React Native 拆包解决方案</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React-Native/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/tags/React-Native/"/>
    <category term="收藏夹" scheme="https://blog.zisheng.pro/tags/%E6%94%B6%E8%97%8F%E5%A4%B9/"/>
    <content>
      <![CDATA[<h2 id="官方"><a href="#官方" class="headerlink" title="官方"></a>官方</h2><ul><li><a href="https://github.com/react-native-community/cli/">@react-native-community&#x2F;cli</a>: Command line tools that ship with react-native</li><li><a href="https://react-native-community.github.io/upgrade-helper/">React Native upgrade helper</a></li><li><a href="https://react-navigation.canny.io/">React Navigation FeedBack</a></li><li><a href="https://github.com/facebook/metro">metro</a>:  🚇 The JavaScript bundler for React Native</li><li><a href="https://fbflipper.com/">Flipper</a>: Extensible mobile app debugger</li></ul><h2 id="必备"><a href="#必备" class="headerlink" title="必备"></a>必备</h2><ul><li><a href="https://github.com/ant-design/ant-design-mobile-rn">ant-design&#x2F;ant-design-mobile-rn</a>: Ant Design for React Native</li><li><a href="https://reactnavigation.org/zh-Hans/">react-navigation</a>: React Native 应用程序的路由和导航</li><li><a href="https://github.com/Microsoft/react-native-code-push">react-native-code-push</a> React Native module for CodePush<ul><li><a href="https://github.com/strawferry/CodePushDialog">react-native-code-push-dialog</a></li></ul></li><li><a href="http://t.cn/RM8Gg7q">react-native-splash-screen</a>): React Native 启动屏，解决 iOS，Android 启动白屏问题，支持 Android 和 iOS</li><li><a href="https://github.com/jasonmerino/react-native-simple-store">react-native-simple-store</a>: <a href="https://github.com/jasonmerino/react-native-simple-store">https://github.com/jasonmerino/react-native-simple-store</a></li><li><a href="https://github.com/react-native-community/react-native-device-info">react-native-device-info</a>: Device Information for React Native iOS and Android</li></ul><h2 id="表单"><a href="#表单" class="headerlink" title="表单"></a>表单</h2><ul><li><a href="https://www.npmjs.com/package/rc-form">rc-form</a></li><li><a href="https://www.npmjs.com/package/react-native-validator">react-native-validator</a></li><li><a href="https://www.npmjs.com/package/react-native-confirmation-code-field">react-native-confirmation-code-field</a>: A react-native confirmation code field compatible with iOS, Android</li><li><a href="https://github.com/halilb/react-native-textinput-effects">react-native-textinput-effects</a>: Text inputs with custom label and icon animations for iOS and android. Built with react native and inspired by Codrops.</li><li><a href="https://www.npmjs.com/package/react-native-hide-show-password-input">react-native-hide-show-password-input</a>: React Native Hide Show Password InputText Component</li><li><a href="https://github.com/react-native-community/react-native-datetimepicker">react-native-community&#x2F;react-native-datetimepicker</a>: React Native date &amp; time picker component for iOS and Android</li><li><a href="https://github.com/umhan35/react-native-search-bar">react-native-search-bar</a>: The high-quality iOS native search bar for react native.</li></ul><h2 id="动画-手势-进度"><a href="#动画-手势-进度" class="headerlink" title="动画&#x2F;手势&#x2F;进度"></a>动画&#x2F;手势&#x2F;进度</h2><ul><li><a href="https://github.com/oblador/react-native-animatable">react-native-animatable</a>: React Native 标准易用的动画集和声明性过渡</li><li><a href="https://github.com/react-native-community/lottie-react-native">lottie-react-native</a>: Lottie wrapper for React Native</li><li><a href="http://t.cn/RsYAM96">react-native-linear-gradient</a>: A <code>&lt;LinearGradient&gt;</code> component for react-native</li><li><a href="http://t.cn/AiKMLWNy">react-native-gesture-handler</a>: 声明性 API 将平台原生触摸和手势系统暴露给 React Native</li><li><a href="http://t.cn/AiC4H5l3">react-native-spinkit</a>: React Native 的动画加载指示符集合</li><li><a href="https://github.com/oblador/react-native-progress">react-native-progress</a>: Progress indicators and spinners for React Native using ReactART</li><li><a href="https://github.com/oblador/react-native-image-progress">react-native-image-progress</a>: Progress indicator for networked images in React Native</li></ul><h2 id="UI"><a href="#UI" class="headerlink" title="UI"></a>UI</h2><ul><li><a href="https://github.com/tomLadder/react-native-echarts-wrapper">react-native-echarts-wrapper</a>: ECharts Wrapper for React Native</li><li><a href="https://github.com/avishayil/react-native-user-avatar">react-native-user-avatar</a>: React Native 的头像组件</li><li><a href="https://www.npmjs.com/package/react-native-icon-badge">react-native-icon-badge</a>: Icon badge is used to create icon badge on some element, such as avatar, icon, image…</li><li><a href="https://www.npmjs.com/package/react-native-signature-capture">react-native-signature-capture</a>: React Native library for capturing signature</li><li><a href="http://t.cn/AiC4Tgcw">@react-native-community&#x2F;blur</a>: React Native Blur component</li><li><a href="https://github.com/react-native-community/react-native-viewpager">react-native-viewpager</a>: This component allows the user to swipe left and right through pages of data</li><li><a href="http://t.cn/Ai9YU5QM">react-native-looped-carousel</a>: 🎠ReactNative 的循环旋转木马</li><li><a href="http://t.cn/Ai9YybZ6">react-native-snap-carousel</a>: deck、layout、parallax images</li><li><a href="http://t.cn/Ai9Y4qZ0">react-native-app-intro-slider</a>: Easy-to-use yet very configurable app introduction slider&#x2F;swiper based on FlatList</li><li><a href="https://github.com/bolan9999/react-native-largelist">react-native-largelist </a>: The best large list component for React Native.</li><li><a href="https://github.com/Spikef/react-native-gesture-password">react-native-gesture-password</a>: 一个React Native的手势密码组件，纯JavaScript实现，因此同时支持iOS、安卓和Web平台。</li><li><a href="https://github.com/i6mi6/react-native-parallax-scroll-view">react-native-parallax-scroll-view</a>: A ScrollView-like component with parallax and sticky header support.</li><li><a href="http://t.cn/EIJQNBS">react-native-loading-spinner-overlay</a>: React Native Loading Spinner Overlay</li><li><a href="https://github.com/crazycodeboy/react-native-easy-toast">react-native-easy-toast</a>: A react native module to show toast like android, it works on iOS and Android.</li><li><a href="https://github.com/jeanregisser/react-native-popover">react-native-popover</a>: A <code>&lt;Popover /&gt;</code> component for react-native</li><li><a href="https://github.com/morenoh149/react-native-contacts">react-native-contacts</a>: 通讯录</li><li><a href="https://github.com/jeanregisser/react-native-slider">react-native-slider</a>: React Native 的纯 JavaScript Slider 组件</li><li><a href="https://github.com/deanmcpherson/react-native-sortable-listview">react-native-sortable-listview</a>: Drag drop capable wrapper of ListView for React Native</li><li><a href="https://github.com/alexbrillant/react-native-deck-swiper">react-native-deck-swiper</a>: react-native deck swiper</li><li><a href="https://github.com/WheelerLee/react-native-dropdown-menu">react-native-dropdown-menu</a>: 易于使用的 React Native 下拉菜单组件</li><li><a href="https://github.com/gitim/react-native-sortable-list">react-native-sortable-list</a>: React Native Sortable List component</li></ul><h2 id="System"><a href="#System" class="headerlink" title="System"></a>System</h2><ul><li><a href="http://t.cn/AiCl5HTw">react-native-app-shortcuts</a>: 用于在 React Native 中创建 Android App 快捷方式的库</li><li><a href="http://t.cn/AiC5uTiP">react-native-push-notification</a>: React Native Local and Remote Notifications</li><li><a href="https://github.com/jordanbyron/react-native-quick-actions">react-native-quick-actions</a>: A react-native interface for Touch 3D home screen quick actions</li><li><a href="http://t.cn/RsYAuMA">react-native-orientation</a>: 在 react-native 中监听设备方向更改</li><li><a href="https://github.com/react-native-community/react-native-permissions">react-native-permissions</a>:An unified permissions API for React Native on iOS and Android</li><li><a href="https://github.com/douglasjunior/react-native-keyboard-manager">react-native-keyboard-manager</a>: ⚛ Library to prevent issues of keyboard sliding up and cover inputs on React-Native iOS projects.</li></ul><h2 id="多媒体"><a href="#多媒体" class="headerlink" title="多媒体"></a>多媒体</h2><ul><li><a href="http://t.cn/RcqvN9z">react-native-image-crop-picker</a>: iOS&#x2F;Android image picker with support for camera, video, configurable compression, multiple images and cropping</li><li><a href="https://github.com/huangzuizui/react-native-file-opener">react-native-file-opener</a>: A React Native module that allows you to open a file (mp3, mp4, pdf, word, excel, dwg etc.) on your device with its default application</li><li><a href="https://github.com/react-native-community/react-native-audio-toolkit">react-native-audio-toolkit</a>: Cross-platform audio library for React Native</li><li><a href="https://github.com/react-native-community/react-native-video">react-native-video</a>: A Video component for react-native</li><li><a href="https://github.com/react-native-community/react-native-camera">react-native-camera</a>: A Camera component for React Native. Also supports barcode scanning!</li><li><a href="https://github.com/DylanVann/react-native-fast-image">react-native-fast-image</a>: 🚩 FastImage, performant React Native image component.</li><li><a href="https://github.com/shahen94/react-native-video-processing">react-native-video-processing</a>: React Native 的原生视频编辑&#x2F;修剪&#x2F;压缩 🎥 库</li></ul><h2 id="Utils"><a href="#Utils" class="headerlink" title="Utils"></a>Utils</h2><ul><li><a href="https://github.com/ptelad/react-native-iphone-x-helper">react-native-iphone-x-helper</a>: A library to help you design your react-native app for the iPhone X, XS, XS Max &amp; XR</li><li><a href="https://github.com/oblador/react-native-keychain">react-native-keychain</a>: 🔑 Keychain Access for React Native</li><li><a href="https://github.com/joshswan/react-native-globalize">react-native-globalize</a>: 国际化</li></ul><h2 id="工具"><a href="#工具" class="headerlink" title="工具"></a>工具</h2><ul><li><a href="https://github.com/JonnyBurger/npx-visualize-bundle">visualize-bundle</a>: <code>npx visualize-bundle</code> allows you to inspect your React Native bundle in just one command and to diagnose big modules.</li><li><a href="https://github.com/smallnew/react-native-multibundler">react-native-multibundler</a>: react native 可视化 bundle 拆包，支持官方的 0.57~0.59 版本，使用官方的 metro 拆包，适用于 Android、iOS</li><li><a href="https://github.com/shwenzhang/AndResGuard">AndResGuard</a>: Android 资源混淆工具</li><li><a href="https://github.com/skylot/jadx">jadx</a>: 反编译工具</li><li><a href="https://github.com/frostney/react-native-create-library">react-native-create-library</a>: 📓 Command line tool to create a React Native library with a single command</li><li><a href="http://t.cn/Rk0o2em">lisong&#x2F;code-push-server</a>: 自建 code-push 服务器</li><li><a href="https://github.com/wix/wml">wml</a>: 符号链接的替代方法，它实际上将已更改的文件从源文件夹复制到目标文件夹</li><li><a href="https://ethercreative.github.io/react-native-shadow-generator/">react-native-shadow-generator</a></li></ul><h2 id="第三方SDK"><a href="#第三方SDK" class="headerlink" title="第三方SDK"></a>第三方SDK</h2><ul><li><a href="http://t.cn/RIPNOd5">jpush-react-native</a>: 极光推送官方支持的 React Native 插件</li><li><a href="https://github.com/sishuguojixuefu/react-native-qiniu">react-native-qiniu</a>: react-native-qiniu</li><li><a href="http://t.cn/E9W1RhM">react-native-amap3d</a>: react-native 高德地图组件，使用最新 3D SDK，支持 Android + iOS</li><li><a href="http://t.cn/E9W1rwb">react-native-amap-geolocation</a>: React Native geolocation module for Android + iOS</li><li><a href="https://github.com/getsentry/sentry-react-native">sentry-react-native</a>: Official Sentry SDK for react-native</li></ul><h2 id="开源项目"><a href="#开源项目" class="headerlink" title="开源项目"></a>开源项目</h2><ul><li><a href="https://github.com/stoneWeb/elm-react-native">elm-react-native</a>: A react native app simulating eleme app，run ios and android.</li><li><a href="https://github.com/listenzz/MyApp">React Native 工程实践</a></li></ul><h2 id="优质文章"><a href="#优质文章" class="headerlink" title="优质文章"></a>优质文章</h2><ul><li><a href="https://bre.is/JdW9YRdE">用React-Native+Mobx做一个迷你水果商城APP</a></li><li><a href="https://bre.is/xNQwNZbZ">react-native 通讯录字母跳转分组功能</a></li><li><a href="https://bre.is/DxEXYBfx">React Native应用部署&#x2F;热更新-CodePush最新集成总结(新)</a></li></ul><h2 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h2><ul><li>LeancloudPushDemo: React native + leancloud push notification demo</li><li>react-native-code-push-dialog: CodePush Custom Dialog，注意：苹果 App、中国的 android 市场必须采用静默更新</li><li>react-native-dropdown这是下拉菜单的简单实现</li><li>react-native-baidu-map百度地图 React Native 模块，支持 react native 0.57+，已更新到最新的百度地图 SDK 版本。</li><li>react-native-search-boxA simple search box with animation, inspired from ios search bar. Lightweight, fast, flexible.</li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/2d966006042c/</id>
    <link href="https://blog.zisheng.pro/posts/2d966006042c/"/>
    <published>2020-02-14T07:36:00.000Z</published>
    <summary>本文记录了一些 React Native 相关的收藏夹</summary>
    <title>React Native 收藏夹</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="JavaScript" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/JavaScript/"/>
    <category term="JavaScript 函数防抖" scheme="https://blog.zisheng.pro/tags/JavaScript-%E5%87%BD%E6%95%B0%E9%98%B2%E6%8A%96/"/>
    <category term="JavaScript 函数节流" scheme="https://blog.zisheng.pro/tags/JavaScript-%E5%87%BD%E6%95%B0%E8%8A%82%E6%B5%81/"/>
    <category term="JavaScript throttle" scheme="https://blog.zisheng.pro/tags/JavaScript-throttle/"/>
    <category term="JavaScript debounce" scheme="https://blog.zisheng.pro/tags/JavaScript-debounce/"/>
    <content>
      <![CDATA[<p>在前端开发中有一部分的用户行为会频繁地触发事件执行，而对于 DOM 操作、资源加载等耗费性能的处理，很可能导致界面卡顿，甚至浏览器的崩溃，函数节流（<code>throttle</code>）和函数防抖（<code>debounce</code>）就是为了解决类似需求应运而生的。</p><h2 id="函数节流"><a href="#函数节流" class="headerlink" title="函数节流"></a>函数节流</h2><p>函数节流就是预定一个函数只有在大于等于执行周期时才执行，周期内调用不执行。好像水滴攒到一定重量才会落下一样。场景：窗口调整（resize）、页面滚动（scroll）、抢购疯狂点击（mousedown）。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">throttle</span> (fn, wait = <span class="number">0</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> lastTime = <span class="number">0</span></span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> now = <span class="title class_">Date</span>.<span class="title function_">now</span>()</span><br><span class="line">    <span class="keyword">if</span>(now - lastTime &gt;= wait) &#123;</span><br><span class="line">      <span class="comment">// 这里并不需要考虑执行环境，所以调用 apply() 时第一个参数是 null，之所以使用 apply 传递参数，是因为第二个参数可以是 arguments 对象</span></span><br><span class="line">      fn.<span class="title function_">apply</span>(<span class="literal">null</span>, <span class="variable language_">arguments</span>)</span><br><span class="line">      lastTime = now</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">boom</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;boom&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">setInterval</span>(<span class="title function_">throttle</span>(boom,<span class="number">2000</span>), <span class="number">1000</span>) <span class="comment">// 一个周期内触发了两次定时器，但是一个周期内只会执行一次 boom 方法。</span></span><br></pre></td></tr></table></figure><h2 id="函数防抖"><a href="#函数防抖" class="headerlink" title="函数防抖"></a>函数防抖</h2><p>函数防抖就是在函数需要频繁触发情况时，只有足够空闲的时间，才执行一次。好像公交司机会等人都上车后才出站一样。场景：实时搜索（keyup）、拖拽（mousemove）</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">debounce</span>(<span class="params">fn, wait = <span class="number">0</span></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="built_in">clearTimeout</span>(fn.<span class="property">tId</span>)</span><br><span class="line">    fn.<span class="property">tId</span> = <span class="built_in">setTimeout</span>(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">      fn.<span class="title function_">apply</span>(<span class="variable language_">this</span>, <span class="variable language_">arguments</span>)</span><br><span class="line">    &#125;.<span class="title function_">bind</span>(<span class="variable language_">this</span>), wait) <span class="comment">// 使用 bind 来确保函数执行环境的正确性，否则就是 global 了</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;debounce&quot;</span>).<span class="property">onclick</span> = <span class="title function_">debounce</span>(boom, <span class="number">300</span>)</span><br><span class="line"><span class="comment">// 单位时间内无论操作多少次，都会合并成一次操作，并在最后一次操作后延迟 300 毫秒执行</span></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/74820d054d6e/</id>
    <link href="https://blog.zisheng.pro/posts/74820d054d6e/"/>
    <published>2020-02-13T12:33:46.000Z</published>
    <summary>在前端开发中有一部分的用户行为会频繁地触发事件执行，而对于 DOM 操作、资源加载等耗费性能的处理，很可能导致界面卡顿，甚至浏览器的崩溃，JavaScript 函数节流与函数防抖就是为了解决类似需求应运而生的。</summary>
    <title>JavaScript 函数节流与函数防抖</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="JavaScript" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/JavaScript/"/>
    <category term="JavaScript" scheme="https://blog.zisheng.pro/tags/JavaScript/"/>
    <category term="slice" scheme="https://blog.zisheng.pro/tags/slice/"/>
    <category term="substring" scheme="https://blog.zisheng.pro/tags/substring/"/>
    <category term="substr" scheme="https://blog.zisheng.pro/tags/substr/"/>
    <content>
      <![CDATA[<table><thead><tr><th>方法</th><th>参数</th><th>返回值</th></tr></thead><tbody><tr><td><code>slice(start,end)</code></td><td><code>start</code>(必需)-起始位置<br /><code>end</code>(可选)-结束位置，若未指定，则默认到末尾所有元素</td><td>返回 <code>[start,end]</code>之间的元素</td></tr><tr><td><code>substring(start, end)</code></td><td><code>start</code>(必需)-起始位置<br /><code>end</code>(必需)-结束位置，若未指定，则默认到末尾所有元素</td><td>返回 <code>[start, end]</code> 之间的元素</td></tr><tr><td><code>substr(start)</code></td><td><code>start</code>(必需)-起始位置<br /><code>length</code>(可选)-所截取的元素的个数，若未指定，则默认到末尾</td><td>返回 <code>[start, start+length]</code>之间的元素</td></tr></tbody></table><blockquote><p>当传递的参数都为正数时，<code>substring</code> 没有区别，当参数为负数时，三个函数的行为不尽相同。</p></blockquote><ul><li><code>slice</code>: 将传入的福参数与字符串长度想加；</li><li><code>substring</code>: 把所有的负值置为0；</li><li><code>substr</code>: 将负的第一个参数与字符串长度相加，负的第二个参数置为0。</li></ul><p><strong>参数为正数的例子</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> str = <span class="string">&#x27;hello world&#x27;</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(str.<span class="title function_">slice</span>(<span class="number">3</span>)); <span class="comment">// lo world</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(str.<span class="title function_">substring</span>(<span class="number">3</span>));<span class="comment">// lo world</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(str.<span class="title function_">substr</span>(<span class="number">3</span>));<span class="comment">// lo world</span></span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(str.<span class="title function_">slice</span>(<span class="number">3</span>, <span class="number">7</span>)); <span class="comment">// lo w</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(str.<span class="title function_">substring</span>(<span class="number">3</span>, <span class="number">7</span>)); <span class="comment">// lo w</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(str.<span class="title function_">substr</span>(<span class="number">3</span>, <span class="number">7</span>));<span class="comment">//lo worl</span></span><br></pre></td></tr></table></figure><p><strong>参数为负数</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> str = <span class="string">&#x27;hello world&#x27;</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(str.<span class="title function_">slice</span>(-<span class="number">3</span>)); <span class="comment">// rld</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(str.<span class="title function_">substring</span>(-<span class="number">3</span>));<span class="comment">// hello world</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(str.<span class="title function_">substr</span>(-<span class="number">3</span>));<span class="comment">// rld</span></span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(str.<span class="title function_">slice</span>(<span class="number">3</span>, -<span class="number">4</span>)); <span class="comment">// lo w</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(str.<span class="title function_">substring</span>(<span class="number">3</span>, -<span class="number">4</span>)); <span class="comment">// hel</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(str.<span class="title function_">substr</span>(<span class="number">3</span>, -<span class="number">4</span>));<span class="comment">// &quot;&quot;(空字符串)</span></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/b0aeb6b939b0/</id>
    <link href="https://blog.zisheng.pro/posts/b0aeb6b939b0/"/>
    <published>2020-02-09T13:14:53.000Z</published>
    <summary>本文介绍了 slice、substring 和 substr 的区别</summary>
    <title>slice、substring 和 substr 的区别</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="JavaScript" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/JavaScript/"/>
    <category term="JavaScript" scheme="https://blog.zisheng.pro/tags/JavaScript/"/>
    <category term="apply" scheme="https://blog.zisheng.pro/tags/apply/"/>
    <category term="call" scheme="https://blog.zisheng.pro/tags/call/"/>
    <category term="bind" scheme="https://blog.zisheng.pro/tags/bind/"/>
    <content>
      <![CDATA[<p>这三个函数的存在意义是什么？答案是<strong>改变函数执行时的上下文</strong>，再具体一点就是改变函数运行时的 <code>this</code> 指向。</p><h2 id="在特定的作用域中调用函数"><a href="#在特定的作用域中调用函数" class="headerlink" title="在特定的作用域中调用函数"></a>在特定的作用域中调用函数</h2><p><code>apply()</code> 和 <code>call()</code> 方法的用途都是在特定的作用域中调用函数。</p><h3 id="apply"><a href="#apply" class="headerlink" title="apply"></a>apply</h3><blockquote><p><code>apply</code> 方法接收两个参数：一个是在其中运行函数的作用域，另一个是参数数组。其中，第二个参数可以是 Array 的实例，也可以是 <code>arguments</code> 对象。</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">sum</span>(<span class="params">num1, num2</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> num1 + num2</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">callSum1</span>(<span class="params">num1, num2</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> sum.<span class="title function_">apply</span>(<span class="variable language_">this</span>, <span class="variable language_">arguments</span>) <span class="comment">// 传入 arguments 对象</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">callSum2</span>(<span class="params">num1, num2</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> sum.<span class="title function_">apply</span>(<span class="variable language_">this</span>, [num1, num2]) <span class="comment">// 传入参数</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">callSum1</span>(<span class="number">10</span>, <span class="number">10</span>))</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">callSum2</span>(<span class="number">10</span>, <span class="number">10</span>))</span><br></pre></td></tr></table></figure><h2 id="call"><a href="#call" class="headerlink" title="call"></a>call</h2><blockquote><p><code>call()</code> 方法与 <code>apply()</code> 方法的作用相同，它们区别仅在于接受参数的方式不同。对于 <code>call()</code> 方法而言，第一个参数是 <code>this</code> 值没有变化，变化的是其余参数都直接传递给函数。</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">sum</span>(<span class="params">num1, num2</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> num1 + num2</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">callSum</span>(<span class="params">num1, num2</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> sum.<span class="title function_">call</span>(<span class="variable language_">this</span>, num1, num2)</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">callSum</span>(<span class="number">10</span>, <span class="number">10</span>))</span><br></pre></td></tr></table></figure><blockquote><p>不传递参数的情况下，使用 <code>call()</code> 方法语义跟明确</p></blockquote><h2 id="创建一个改变了上下文的函数"><a href="#创建一个改变了上下文的函数" class="headerlink" title="创建一个改变了上下文的函数"></a>创建一个改变了上下文的函数</h2><p><code>call()</code> 和 <code>apply()</code> 方法改变了函数的 <code>this</code> 上下文之后便立即执行该函数，而 <code>bind()</code> 方法会创建一个函数的实例并返回，其 <code>this</code> 值会被绑定到传递给 <code>bind()</code> 函数的值。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> color = <span class="string">&#x27;red&#x27;</span></span><br><span class="line"><span class="keyword">var</span> o = &#123;</span><br><span class="line">  <span class="attr">color</span>: <span class="string">&#x27;blue&#x27;</span>,</span><br><span class="line">  <span class="attr">sayColor</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">color</span>) <span class="comment">// 这个 this 的值是 o</span></span><br><span class="line">    <span class="built_in">setTimeout</span>(</span><br><span class="line">      <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">color</span>) <span class="comment">// 默认的值是 this 的值是 window</span></span><br><span class="line">      &#125;.<span class="title function_">bind</span>(<span class="variable language_">this</span>),</span><br><span class="line">      <span class="number">1000</span>,</span><br><span class="line">    )</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(o.<span class="title function_">sayColor</span>()) <span class="comment">// blue</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">sayColor</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="title function_">alert</span>(<span class="variable language_">this</span>.<span class="property">color</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> objectSayColor = sayColor.<span class="title function_">bind</span>(o)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">objectSayColor</span>()) <span class="comment">// blue</span></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/30547169b42e/</id>
    <link href="https://blog.zisheng.pro/posts/30547169b42e/"/>
    <published>2020-02-09T08:07:05.000Z</published>
    <summary>本文介绍了 JavaScript apply、call、bind 三兄弟的使用</summary>
    <title>JavaScript apply、call、bind 三兄弟</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="JavaScript" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/JavaScript/"/>
    <category term="ES6" scheme="https://blog.zisheng.pro/tags/ES6/"/>
    <category term="let" scheme="https://blog.zisheng.pro/tags/let/"/>
    <category term="const" scheme="https://blog.zisheng.pro/tags/const/"/>
    <content>
      <![CDATA[<p><code>let</code> 与 <code>const</code> 都是只在声明所在的块级作用域内有效。</p><p><code>let</code> 声明的变量可以改变，值和类型都可以改变，没有限制。<br><code>const</code> 声明的变量不得改变值，这意味着，<code>const</code> 一旦声明变量，就必须立即初始化，不能留到以后赋值。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> a <span class="comment">// Uncaught SyntaxError: Missing initializer in const declaration</span></span><br><span class="line"><span class="keyword">const</span> b = <span class="number">2</span></span><br><span class="line">b = <span class="number">3</span> <span class="comment">// Uncaught TypeError: Assignment to constant variable.</span></span><br></pre></td></tr></table></figure><p>对于引用类型的值，如数组和对象，变量保存的是值的引用。<code>const</code> 命令只是保证引用不变，并不保证值不变。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> names = []</span><br><span class="line">names = [<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>] <span class="comment">// Uncaught TypeError: Assignment to constant variable.</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//不会报错，因为names指向的地址不变，改变的只是内部数据</span></span><br><span class="line"><span class="keyword">const</span> names = []</span><br><span class="line">names[<span class="number">0</span>] = <span class="number">1</span></span><br><span class="line">names[<span class="number">1</span>] = <span class="number">2</span></span><br><span class="line">names[<span class="number">2</span>] = <span class="number">3</span></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/d9064012188b/</id>
    <link href="https://blog.zisheng.pro/posts/d9064012188b/"/>
    <published>2020-02-07T10:49:11.000Z</published>
    <summary>let 与 const 都是只在声明所在的块级作用域内有效。</summary>
    <title>let 和 const 的区别</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="JavaScript" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/JavaScript/"/>
    <category term="JavaScript" scheme="https://blog.zisheng.pro/tags/JavaScript/"/>
    <category term="浅拷贝" scheme="https://blog.zisheng.pro/tags/%E6%B5%85%E6%8B%B7%E8%B4%9D/"/>
    <category term="深拷贝" scheme="https://blog.zisheng.pro/tags/%E6%B7%B1%E6%8B%B7%E8%B4%9D/"/>
    <category term="基础类型" scheme="https://blog.zisheng.pro/tags/%E5%9F%BA%E7%A1%80%E7%B1%BB%E5%9E%8B/"/>
    <category term="引用类型" scheme="https://blog.zisheng.pro/tags/%E5%BC%95%E7%94%A8%E7%B1%BB%E5%9E%8B/"/>
    <content>
      <![CDATA[<h2 id="基础类型值和引用类型值的区别"><a href="#基础类型值和引用类型值的区别" class="headerlink" title="基础类型值和引用类型值的区别"></a>基础类型值和引用类型值的区别</h2><h3 id="保存方式"><a href="#保存方式" class="headerlink" title="保存方式"></a>保存方式</h3><blockquote><p>基础类型：变量 -&gt; 值<br>引用类型：变量 -&gt; 值的堆内存地址 -&gt; 值</p></blockquote><p>变量保存在栈内存中，栈内存是有序排列的，大小是固定的。在将一个值赋值给变量时，解析器必须确定这个值是基本类型值还是引用类型值。也就是说变量的值到底存的是实际值还是实际值的引用是根据实际值的类型决定的：基础类型的值是保存在变量中实际值，而引用类型的值是保存在堆内存中的堆内存是无序排列的，大小是不固定的），变量中存的值是实际值的堆内存地址。</p><h3 id="复制变量值"><a href="#复制变量值" class="headerlink" title="复制变量值"></a>复制变量值</h3><blockquote><p>基础类型：变量a -&gt; 值；变量b -&gt;值的副本<br>引用类型：变量b -&gt; 变量a -&gt; 值的堆内存地址 -&gt; 值</p></blockquote><p>在 ECMAScript 中，除了保存的方式不同之外，在从一个变量向另一个变量复制基本类型值和引用类型值时，也存在不同。</p><p>如果从一个变量向另一个变量复制基本类型的值，会在变量对象上创建一个新值，然后把该值复制到为新变量分配的位置上。新值只是一个副本。</p><p>当从一个变量向另一个变量复制引用类型的值时，同样也会将存储在变量对象上的值复制一份放到为新变量分配的空间上。不同的是，这个值的副本实际上是一个指针，而这个指针指向存储在堆中的一个对象。复制操作结束后，两个变量实际上将引用同一个对象。因此改变其中一个变量，就会影响另一个变量。</p><h3 id="传递参数"><a href="#传递参数" class="headerlink" title="传递参数"></a>传递参数</h3><p>ECMAScript 中所有函数的参数都是按值（变量的值，不一定是实际值）传递的。也就是说，把函数外部的值复制给函数内部参数，就和把值从一个变量复制给另一个变量是一样的。</p><p>在向参数传递基本类型的值时，被传递的值会被复制给一个局部变量（即命名参数，或者用 ECMAScript 的概念来说，就是 <code>arguments</code> 对象中的一个元素）。</p><p>在向参数传递引用类型的值时，会把这个值在内存中的地址复制给一个局部变量，因此这个局部变量的变化会反映在函数的外部。虽然变量是按值传递的，但是这个值它是实际值的引用，所以局部变量也是按照这个引用访问了同一个对象。</p><h2 id="浅拷贝"><a href="#浅拷贝" class="headerlink" title="浅拷贝"></a>浅拷贝</h2><p>浅复制只复制一层对象的属性，也就是说拷贝原对象的实例，但是对其内部的引用类型值，拷贝的是其引用</p><h3 id="自行实现浅拷贝"><a href="#自行实现浅拷贝" class="headerlink" title="自行实现浅拷贝"></a>自行实现浅拷贝</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">shallowCopy</span>(<span class="params">src</span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> dst = &#123;&#125;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">var</span> prop <span class="keyword">in</span> src) &#123;</span><br><span class="line">    <span class="keyword">if</span> (src.<span class="title function_">hasOwnProperty</span>(prop)) &#123;</span><br><span class="line">      dst[prop] = src[prop]</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> dst</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> obj = &#123; <span class="attr">a</span>:<span class="number">1</span>, <span class="attr">arr</span>: [<span class="number">2</span>,<span class="number">3</span>] &#125;</span><br><span class="line"><span class="keyword">var</span> shallowObj = <span class="title function_">shallowCopy</span>(obj)</span><br><span class="line"><span class="comment">// shallowObj.a = 1</span></span><br><span class="line"><span class="comment">// console.log(obj) // &#123; a:1, arr: [2,3] &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// shallowObj.arr[0] = 1</span></span><br><span class="line"><span class="comment">// console.log(obj) // &#123; a:1, arr: [1,3] &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// shallowObj.arr = [1,3]</span></span><br><span class="line"><span class="comment">// console.log(obj) // &#123; a:1, arr: [2,3] &#125;</span></span><br></pre></td></tr></table></figure><h3 id="工程化"><a href="#工程化" class="headerlink" title="工程化"></a>工程化</h3><ul><li><a href="https://www.lodashjs.com/docs/latest#_clonevalue">_.clone</a>: 创建一个 value 的浅拷贝。</li></ul><h2 id="深拷贝"><a href="#深拷贝" class="headerlink" title="深拷贝"></a>深拷贝</h2><p>深拷贝是对对象以及对象的所有子对象进行拷贝。我们可以使用 lodash 的 <code>cloneDeep</code> 方法，但是深拷贝大数据的时候，由于内部实现是递归，所以最好换实现思路。</p><h3 id="JSON-parse-JSON-stringify-深拷贝的局限"><a href="#JSON-parse-JSON-stringify-深拷贝的局限" class="headerlink" title="JSON.parse(JSON.stringify) 深拷贝的局限"></a><code>JSON.parse(JSON.stringify)</code> 深拷贝的局限</h3><ol><li>如果被拷贝的对象中有 <code>function</code>，则拷贝之后的对象就会<strong>丢失</strong>这个 <code>function</code></li><li>如果被拷贝的对象中有正则表达式，则拷贝之后的正则表达式会变成 <code>Object</code></li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = &#123;<span class="attr">name</span>: <span class="string">&#x27;youngjuning&#x27;</span>, <span class="attr">sayName</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;<span class="title function_">alert</span>(<span class="variable language_">this</span>.<span class="property">name</span>)&#125;, <span class="attr">numberReg</span>: <span class="regexp">/\d+/</span>&#125;</span><br><span class="line"><span class="keyword">let</span> b = <span class="title class_">JSON</span>.<span class="title function_">parse</span>(<span class="title class_">JSON</span>.<span class="title function_">stringify</span>(a))</span><br><span class="line"><span class="comment">// sayName丢失，numberReg变为了对象</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">dir</span>(b) <span class="comment">// &#123;name: &#x27;youngjuning&#x27;, numberReg: &#123;&#125;&#125;</span></span><br></pre></td></tr></table></figure><h2 id="深拷贝与浅拷贝的区别"><a href="#深拷贝与浅拷贝的区别" class="headerlink" title="深拷贝与浅拷贝的区别"></a>深拷贝与浅拷贝的区别</h2><ul><li>深拷贝：将 B 对象拷贝到 A 对象中，包括 B 里面的子对象，</li><li>浅拷贝：将 B 对象拷贝到 A 对象中，但不包括 B 里面的子对象</li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/97c7a5c526fe/</id>
    <link href="https://blog.zisheng.pro/posts/97c7a5c526fe/"/>
    <published>2020-02-07T04:00:57.000Z</published>
    <summary>本文介绍了 JavaScript 中的浅拷贝与深拷贝</summary>
    <title>JavaScript 浅拷贝与深拷贝</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="JavaScript" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/JavaScript/"/>
    <category term="笔记" scheme="https://blog.zisheng.pro/tags/%E7%AC%94%E8%AE%B0/"/>
    <category term="javascript" scheme="https://blog.zisheng.pro/tags/javascript/"/>
    <content>
      <![CDATA[<div class="note danger modern"><p>All in One Post 的学习笔记不可持续且可读性差，之后不会再做此类尝试。</p></div><h2 id="JavaScript-由哪些部分组成？"><a href="#JavaScript-由哪些部分组成？" class="headerlink" title="JavaScript 由哪些部分组成？"></a>JavaScript 由哪些部分组成？</h2><ul><li>ECMAScript：由 ECMA-262d 定义并提供核心功能。</li><li>文档对象模型（DOM，Document Object Model）：提供与网页交互的方法和接口</li><li>浏览器对象模型（BOM，Browser Object Model）：提供与浏览器交互的方法和接口</li></ul><h2 id="ECMA-262-到底定义了什么？"><a href="#ECMA-262-到底定义了什么？" class="headerlink" title="ECMA-262 到底定义了什么？"></a>ECMA-262 到底定义了什么？</h2><ul><li>语法</li><li>类型</li><li>语句</li><li>关键字</li><li>保留字</li><li>操作符</li><li>全局对象</li></ul><p>ECMAScript 只是对实现这个规范描述的所有方面的一个抽象，它并不是一个完整的语言。JavaScript 实现了 ECMAScrip，而 Adobe ActionScript 同样实现了 ECMAScript。</p><h2 id="JavaScript-标识符"><a href="#JavaScript-标识符" class="headerlink" title="JavaScript 标识符"></a>JavaScript 标识符</h2><p>所谓标识符，就是变量、函数、函数参数或对象属性的名称。标识符可以由一或多个下列字符组成：</p><ul><li>第一个字符必须是一个字符、下划线（_）或美元符合（$）；</li><li>剩下的其他字符可以是字母、下划线（_）、美元符号或数字。</li></ul><blockquote><p>注意：标识符中的字母可以是扩展 ASCII（Extended ASCII） 中的字母，也可以是 Unicode 的字母字符。</p></blockquote><blockquote><p>注意：关键字、保留字、true、false、null 不能作为标识符。</p></blockquote><h2 id="var-声明作用域"><a href="#var-声明作用域" class="headerlink" title="var 声明作用域"></a>var 声明作用域</h2><p>使用 var 操作符定义的变量会成为包含的函数的局部变量，在函数内部使用，函数结束后变量就会被销毁。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">test</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> message = <span class="string">&#x27;Hello World&#x27;</span>; <span class="comment">// 局部变量</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">test</span>();</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(message); <span class="comment">// ReferenceError: message is not defined</span></span><br></pre></td></tr></table></figure><blockquote><p>注意：去掉之前的 var 操作符之后，message 就变成了全局变量。只要调用一次函数 <code>test()</code>，就会定义这个变量，并且可以在函数外部访问到。</p></blockquote><h2 id="var-声明提升"><a href="#var-声明提升" class="headerlink" title="var 声明提升"></a>var 声明提升</h2><p>使用 <code>var</code> 声明的变量会被自动提升到函数作用域顶部：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">test</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(age); <span class="comment">// undefined</span></span><br><span class="line">  <span class="keyword">var</span> age = <span class="number">18</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>之所以不会报错，是因为 ECMAScript 运行时把它看成等价如下的代码：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">foo</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> age;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(age); <span class="comment">// undefined</span></span><br><span class="line">  age = <span class="number">18</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这就是所谓的“提升”（hoist），也就是把所有变量声明都拉到函数作用域的顶部。</p><h2 id="var-重复声明"><a href="#var-重复声明" class="headerlink" title="var 重复声明"></a>var 重复声明</h2><p>反复多次使用 <code>var</code> 声明同一个变量也没有问题：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">foo</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> age = <span class="number">16</span>;</span><br><span class="line">  <span class="keyword">var</span> age = <span class="number">26</span>;</span><br><span class="line">  <span class="keyword">var</span> age = <span class="number">36</span>;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(age); <span class="comment">// 36</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="JS的原始类型有哪几种？"><a href="#JS的原始类型有哪几种？" class="headerlink" title="JS的原始类型有哪几种？"></a>JS的原始类型有哪几种？</h2><p>在 JS 中，存在这6种原始值，分别是：</p><ul><li><code>number</code></li><li><code>string</code></li><li><code>boolean</code></li><li><code>undefined</code></li><li><code>null</code></li><li><code>symbol</code></li></ul><h2 id="null-是对象吗？"><a href="#null-是对象吗？" class="headerlink" title="null 是对象吗？"></a>null 是对象吗？</h2><p>对于 <code>null</code> 来说，很多人会认为他是个对象类型，其实这是错误的。虽然 <code>typeof null</code> 会输出 <code>object</code>，但是这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统，为了性能考虑使用低位存储变量的类型信息，<code>000</code> 开头代表对象，然而 <code>null</code> 表示为全零，所以将它错误地判断为 <code>object</code>。虽然现在的内部类型判断代码已经改变了，但是对于这个 Bug 却是一直流传下来。</p><h2 id="栈内存和堆内存的区别"><a href="#栈内存和堆内存的区别" class="headerlink" title="栈内存和堆内存的区别"></a>栈内存和堆内存的区别</h2><ul><li>栈内存：栈是一种先进后出（FILO，First-In-Last-Out）的数据结构。栈内存中存放变量</li><li>堆内存：堆是一种经过排序的树形数据结构，每个结点都有一个值。堆的存取是随意的。堆内存中存放复杂对象</li></ul><h2 id="基本类型和引用类型的值"><a href="#基本类型和引用类型的值" class="headerlink" title="基本类型和引用类型的值"></a>基本类型和引用类型的值</h2><p>ECMAScript 变量可能包含两种不同数据类型的值：基本类型值和引用类型值。<strong>基本类型值</strong>指的是简单的数据段，而<strong>引用类型值</strong>指那些可能由多个值构成的对象。</p><h3 id="保存方式"><a href="#保存方式" class="headerlink" title="保存方式"></a>保存方式</h3><p>在将一个值赋给变量时，解析器必须确定这个值是基本类型值还是引用类型值。基本类型的值是直接保存在变量中的，因此可以直接操作保存在变量中的实际值，所以基本类型的值是按值访问的。引用类型的值是保存在内存中的对象，在操作对象时，实际上是在操作对象的引用而不是实际的对象，但在为对象添加属性时，操作的是实际的对象，为此，引用类型的值是按引用访问的。</p><h3 id="动态的属性"><a href="#动态的属性" class="headerlink" title="动态的属性"></a>动态的属性</h3><p>基本类型的值是不可变的，是没有函数可以调用的。对于引用类型的值，它的属性则是动态的，我们可以为其添加属性和方法，也可以改变和删除其属性和方法。</p><h3 id="复制变量值"><a href="#复制变量值" class="headerlink" title="复制变量值"></a>复制变量值</h3><p>除了保存的方式不同之外，在从一个变量向另一个变量复制基本类型值和引用类型值时，也存在不同。如果从一个变量向另一个变量复制基本类型的值，会在变量对象上创建一个新值，然后把该值复制到为新变量分配的位置上。当从一个变量向另一个变量复制引用类型的值时，同样也会将存储在变量对象中的值复制一份放到为新变量分配的空间中。不同的是，这个值的副本实际上是一个指针，而这个指针指向存储在堆中的一个对象。复制操作结束后，两个变量实际上将引用同一个对象。因此，改变其中一个变量，就会影响另一个变量。</p><blockquote><p>对象类型和原始类型的不同之处？</p><p>在 JS 中，除了原始类型那么其他的都是对象类型了。对象类型和原始类型不同的是，原始类型存储的值，对象类型存储的是地址（指针）。当你创建了一个对象类型的时候，计算机会在内存中帮我们开辟一个空间来存放值，但是我们需要找到这个空间，这个空间会拥有一个地址（指针）。</p></blockquote><h2 id="函数参数是对象会发生什么问题？"><a href="#函数参数是对象会发生什么问题？" class="headerlink" title="函数参数是对象会发生什么问题？"></a>函数参数是对象会发生什么问题？</h2><p>在向参数传递基本类型的值时，被传递的值会被复制给一个局部变量（即命名参数，或者用 ECMAScript 的概念来说，就是 <code>arguments</code> 对象中的一个元素）。在向参数传递引用类型的值时，会把这个值在内存中的地址复制给一个局部变量，因此这个局部变量的变化会反映在函数的外部。当在函数内部重写引用类型的值时，这个变量引用的就是一个局部对象了。而这个局部对象会在函数执行完毕之后立即被销毁。</p><h2 id="typeof-是否能正确判断类型？"><a href="#typeof-是否能正确判断类型？" class="headerlink" title="typeof 是否能正确判断类型？"></a>typeof 是否能正确判断类型？</h2><p><code>typeof</code> 对于原始类型来说，除了 <code>null</code> 都可以显示正确的类型</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typeof</span> <span class="number">1</span> <span class="comment">// &#x27;number&#x27;</span></span><br><span class="line"><span class="keyword">typeof</span> <span class="string">&#x27;1&#x27;</span> <span class="comment">// &#x27;string&#x27;</span></span><br><span class="line"><span class="keyword">typeof</span> <span class="literal">true</span> <span class="comment">// &#x27;boolean&#x27;</span></span><br><span class="line"><span class="keyword">typeof</span> <span class="literal">undefined</span> <span class="comment">// &#x27;undefined&#x27;</span></span><br><span class="line"><span class="keyword">typeof</span> <span class="title class_">Symbol</span>() <span class="comment">// &#x27;symbol&#x27;</span></span><br><span class="line"><span class="keyword">typeof</span> <span class="literal">null</span> <span class="comment">// &#x27;object&#x27;</span></span><br></pre></td></tr></table></figure><p><code>typeof</code> 对于对象来说，除了函数都会显示 <code>object</code>，所以说 <code>typeof</code> 并不能准确判断变量到底是什么类型</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typeof</span> [] <span class="comment">// &#x27;object&#x27;</span></span><br><span class="line"><span class="keyword">typeof</span> &#123;&#125; <span class="comment">// &#x27;object&#x27;</span></span><br><span class="line"><span class="keyword">typeof</span> <span class="variable language_">console</span>.<span class="property">log</span> <span class="comment">// &#x27;function&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="instanceof-能正确判断对象的原理是什么？"><a href="#instanceof-能正确判断对象的原理是什么？" class="headerlink" title="instanceof 能正确判断对象的原理是什么？"></a>instanceof 能正确判断对象的原理是什么？</h2><p>如果我们想判断一个对象的正确类型，这时候可以考虑使用 <code>instanceof</code>，因为内部机制是通过原型链来判断的。</p><h2 id="类型转换"><a href="#类型转换" class="headerlink" title="类型转换"></a>类型转换</h2><table><thead><tr><th>值</th><th>转字符串</th><th>转数字</th><th>转布尔值</th><th>转对象</th></tr></thead><tbody><tr><td><code>undefined</code></td><td><code>”undefined“</code></td><td><code>NaN</code></td><td><code>false</code></td><td>throws TypeError</td></tr><tr><td><code>null</code></td><td><code>&quot;null&quot;</code></td><td><code>0</code></td><td><code>false</code></td><td>throws TypeError</td></tr><tr><td><code>true</code></td><td><code>&quot;true&quot;</code></td><td><code>1</code></td><td></td><td>new Boolean(true)</td></tr><tr><td><code>false</code></td><td><code>&quot;false&quot;</code></td><td><code>0</code></td><td></td><td>new Boolean(false)</td></tr><tr><td><code>&quot;&quot;</code>（空字符串）</td><td></td><td><code>0</code></td><td><code>false</code></td><td>new String(“”)</td></tr><tr><td><code>&quot;1.2&quot;</code>（非空数字字符串）</td><td></td><td><code>1.2</code></td><td><code>true</code></td><td>new String(“1.2”)</td></tr><tr><td><code>&quot;one&quot;</code>（非空非数字字符串）</td><td></td><td><code>NaN</code></td><td><code>true</code></td><td>new String(“one”)</td></tr><tr><td><code>0</code></td><td><code>&quot;0&quot;</code></td><td></td><td><code>false</code></td><td>new Number(0)</td></tr><tr><td><code>-0</code></td><td><code>&quot;-0&quot;</code></td><td></td><td><code>false</code></td><td>new Number(-0)</td></tr><tr><td><code>NaN</code></td><td><code>&quot;NaN&quot;</code></td><td></td><td><code>false</code></td><td>new Number(NaN)</td></tr><tr><td><code>Infinity</code></td><td><code>&quot;Infinity&quot;</code></td><td></td><td><code>true</code></td><td>new Number(Infinity)</td></tr><tr><td><code>-Infinity</code></td><td><code>&quot;-Infinity&quot;</code></td><td></td><td><code>true</code></td><td>new Number(-Infinity)</td></tr><tr><td><code>1</code>（非零无穷大）</td><td><code>&quot;1&quot;</code></td><td></td><td><code>true</code></td><td>new Number(1)</td></tr><tr><td><code>Symbol(&quot;name&quot;)</code></td><td>throws TypeError</td><td>throws TypeError</td><td><code>true</code></td><td></td></tr><tr><td><code>&#123;&#125;</code>（任意对象）</td><td><code>&quot;[object Object]&quot;</code></td><td><code>NaN</code></td><td><code>true</code></td><td></td></tr><tr><td><code>[]</code>（任意数组）</td><td><code>&quot;&quot;</code></td><td><code>0</code></td><td><code>true</code></td><td></td></tr><tr><td><code>[9]</code>（1个数字元素）</td><td><code>&quot;9&quot;</code></td><td><code>9</code></td><td><code>true</code></td><td></td></tr><tr><td><code>[&#39;a&#39;]</code>（其他数组）</td><td>使用<code>join</code> 方法</td><td><code>NaN</code></td><td><code>true</code></td><td></td></tr><tr><td><code>function() &#123;&#125;</code>（任意函数）</td><td>参考<strong>对象转基本类型</strong></td><td><code>NaN</code></td><td><code>true</code></td><td></td></tr></tbody></table><h3 id="转换为Boolean"><a href="#转换为Boolean" class="headerlink" title="转换为Boolean"></a>转换为Boolean</h3><p>除了 <code>0</code>、<code>-0</code>、<code>NaN</code>、<code>&#39;&#39;</code>、<code>false</code>、<code>undefined</code>、<code>null</code>，其他所有值都转为 <code>true</code>,包括对象</p><h3 id="对象转基本类型"><a href="#对象转基本类型" class="headerlink" title="对象转基本类型"></a>对象转基本类型</h3><p>对象在转换类型的时候，会调用内置的 <code>[[ToPrimitive]]</code> 对象，对于该函数来说，算法逻辑一般来说如下：</p><ul><li>如果已经是基本类型了，那就不需要转换了</li><li>如果需要转字符串类型就调用<code>x.toString()</code>，转换为基本类型的话就返回转换的值。不是字符串类型的话就先调用 <code>x.valueOf()</code>，如果转换为基本类型，就返回转换的值</li><li>如果都没有返回原始类型，就会报错</li></ul><p>当然你也可以重写 <code>Symbol.toPrimitive</code> ，该方法在转原始类型时调用优先级最高。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a = &#123;</span><br><span class="line">  <span class="title function_">valueOf</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="title function_">toString</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;1&#x27;</span></span><br><span class="line">  &#125;,</span><br><span class="line">  [<span class="title class_">Symbol</span>.<span class="property">toPrimitive</span>]() &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">2</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="number">1</span> + a <span class="comment">// =&gt; 3</span></span><br></pre></td></tr></table></figure><h2 id="加法运算符"><a href="#加法运算符" class="headerlink" title="加法运算符"></a>加法运算符</h2><p>加法运算符比较特别，它有以下几个特点：</p><ul><li>运算中其中一方为字符串，那么就会把另一方也转换为字符串</li><li>如果一方不是字符串或者数字，那么会将它转换为数字或者字符串</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="literal">undefined</span> + <span class="string">&#x27;&#x27;</span> <span class="comment">// -&gt; &quot;undefined&quot;</span></span><br><span class="line"><span class="literal">null</span> + <span class="string">&#x27;&#x27;</span> <span class="comment">// -&gt; &quot;null&quot;</span></span><br><span class="line"><span class="literal">true</span> + <span class="string">&#x27;&#x27;</span> <span class="comment">// -&gt; &quot;true&quot;</span></span><br><span class="line"><span class="literal">false</span> + <span class="string">&#x27;&#x27;</span> <span class="comment">// -&gt; &quot;false&quot;</span></span><br><span class="line"><span class="number">1</span> + <span class="string">&#x27;&#x27;</span> <span class="comment">// -&gt; &quot;1&quot;</span></span><br><span class="line"><span class="title class_">NaN</span> + <span class="string">&#x27;&#x27;</span></span><br><span class="line">(&#123;&#125;) +<span class="string">&#x27;&#x27;</span> <span class="comment">// -&gt; &quot;[object Object]&quot;</span></span><br><span class="line">&#123;&#125; + <span class="string">&#x27;&#x27;</span> <span class="comment">// -&gt; 0</span></span><br><span class="line">[] + <span class="string">&#x27;&#x27;</span> <span class="comment">// -&gt; &quot;&quot;</span></span><br><span class="line"><span class="literal">undefined</span> + <span class="number">1</span> <span class="comment">// -&gt; NaN</span></span><br><span class="line"><span class="literal">null</span> + <span class="number">1</span> <span class="comment">// -&gt; 1</span></span><br><span class="line"><span class="literal">true</span> + <span class="number">1</span> <span class="comment">// -&gt; 2</span></span><br><span class="line"><span class="literal">false</span> + <span class="number">1</span> <span class="comment">// -&gt; 1</span></span><br><span class="line">(&#123;&#125;) + <span class="number">2</span> <span class="comment">// -&gt; &quot;[object Object]2&quot;</span></span><br><span class="line">&#123;&#125; + <span class="number">2</span> <span class="comment">// -&gt; 2</span></span><br><span class="line">[] + <span class="number">1</span> <span class="comment">// -&gt; &quot;1&quot;</span></span><br><span class="line">+ <span class="string">&#x27;&#x27;</span> <span class="comment">// -&gt; 0</span></span><br><span class="line">+ <span class="string">&#x27;b&#x27;</span> <span class="comment">// -&gt; NaN</span></span><br><span class="line">+ &#123;&#125; <span class="comment">// -&gt; NaN</span></span><br><span class="line">+ [] <span class="comment">// -&gt; 0</span></span><br><span class="line"><span class="string">&#x27;a&#x27;</span> + + <span class="string">&#x27;b&#x27;</span> <span class="comment">// -&gt; &quot;aNaN&quot;</span></span><br></pre></td></tr></table></figure><h2 id="比较运算符"><a href="#比较运算符" class="headerlink" title="比较运算符"></a>比较运算符</h2><ol><li>如果是对象，就通过<code>[[toPrimitive]]</code> 转换对象</li><li>如果是字符串，就通过 <code>unicode</code> 字符串索引来比较</li></ol><h2 id="this对象"><a href="#this对象" class="headerlink" title="this对象"></a>this对象</h2><p><code>this</code> 对象是在运行时基于函数的执行环境绑定的：</p><ul><li>在全局函数中，<code>this</code> 等同于 <code>window</code></li><li>当函数被作为某个对象的方法调用时，<code>this</code> 等于那个对象</li><li>匿名函数的执行环境具有全局性，因此其 <code>this</code>对象通常指向 <code>window</code></li><li><code>new</code>：<code>this</code> 被固化到实例上，不会被任何方式改变</li><li><code>bind</code>、<code>apply</code>、<code>call</code>：<code>this</code> 对象是第一个参数</li><li>箭头函数其实是没有 <code>this</code> 的，箭头函数中的 <code>this</code> 只取决包裹箭头函数的第一个普通函数的 <code>this</code></li></ul><h2 id="和-有什么区别"><a href="#和-有什么区别" class="headerlink" title="&#x3D;&#x3D; 和 &#x3D;&#x3D;&#x3D; 有什么区别"></a>&#x3D;&#x3D; 和 &#x3D;&#x3D;&#x3D; 有什么区别</h2><p>对于 <code>==</code> 来说，如果对比双方的类型不一样的话，就会进行隐式类型转化。假如我们需要对比 <code>x</code> 和 <code>y</code> 是否相同，就会进行如下判断流程：</p><ol><li>首先会判断两者类型是否相同。相同的话就是比大小了</li><li>类型不同的话，那么就会进行类型转换</li><li>会先判断是否在对比 <code>null</code> 和 <code>undefined</code>，是的话就会返回 <code>true</code></li><li>判断两者类型是否为 <code>string</code> 和 <code>number</code>，是的话就会将字符串转为 <code>number</code></li><li>判断其中一方是否为 <code>boolean</code>，是的话就会把 <code>boolean</code> 转为 <code>number</code> 再进行判断</li><li>判断其中一方是否为 <code>object</code> 且另一方为 <code>string</code>、<code>number</code> 或者 <code>symbol</code>，是的话就会把 <code>object</code> 转为原始类型再进行判断</li></ol><blockquote><p><code>[] == ![紫升]</code> &#x3D;&gt; <code>true</code></p></blockquote><p><img src="https://i.loli.net/2020/04/30/inuaIUJvYQP5BjD.png" alt="紫升"></p><h2 id="闭包（Closure）"><a href="#闭包（Closure）" class="headerlink" title="闭包（Closure）"></a>闭包（Closure）</h2><h3 id="什么是闭包"><a href="#什么是闭包" class="headerlink" title="什么是闭包"></a>什么是闭包</h3><p><strong>函数A</strong>里面包含了<strong>函数B</strong>，而<strong>函数B</strong>里面使用了<strong>函数A</strong>的变量，那么<strong>函数B</strong>被称为闭包函数。在JS中，闭包的意义是能让你从内部函数访问外部函数作用域。</p><p>在计算机科学中，闭包（Closure），又称词法闭包（Lexical Closure）或函数闭包（Function Closure），是在支持头等函数的编程语言中实现词法绑定的一种技术。闭包实际上是一个结构体，它存储了一个函数和一个关联环境。环境里是若干对符号和值的对应关系，它既要包括约束变量（该函数内部的变量），也要包括自由变量（在函数外部定义但在函数内被引用）。闭包跟函数最大的不同在于，当捕捉闭包的时候，它的自由变量会在捕捉时被确定，这样即便脱离了捕捉时的上下文，它也能照常运行。</p><h3 id="闭包的副作用"><a href="#闭包的副作用" class="headerlink" title="闭包的副作用"></a>闭包的副作用</h3><ul><li>由于闭包会携带包含它的函数的作用域，因此会比其他函数占用更多的内存。</li><li>闭包只能取得包含函数中任何变量的最后一个值。闭包保存的是整个变量对象，而不是某个特殊的变量。</li><li>在IE9之前的浏览器中，如果闭包的作用域链中保存着HTML元素，那么就意味着该元素将无法被销毁。</li></ul><h3 id="循环中使用闭包解决-var-定义函数的问题"><a href="#循环中使用闭包解决-var-定义函数的问题" class="headerlink" title="循环中使用闭包解决 var 定义函数的问题"></a>循环中使用闭包解决 <code>var</code> 定义函数的问题</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span>(<span class="keyword">var</span> i = <span class="number">1</span>; i &lt;=<span class="number">5</span>: i++) &#123;</span><br><span class="line">  <span class="built_in">setTimeout</span>(<span class="keyword">function</span> <span class="title function_">timer</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(i)</span><br><span class="line">  &#125;, i * <span class="number">1000</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>首先因为 <code>setTimeout</code> 是个异步函数，所以会把循环全部执行完毕，这时候 <code>i</code> 就是 6 了，所以会输出 5 个 6。</p><h4 id="使用闭包解决"><a href="#使用闭包解决" class="headerlink" title="使用闭包解决"></a>使用闭包解决</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span>(<span class="keyword">var</span> i = <span class="number">1</span>; i &lt;= <span class="number">5</span>; i++) &#123;</span><br><span class="line">  ;(<span class="keyword">function</span>(<span class="params">j</span>) &#123;</span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="keyword">function</span> <span class="title function_">timer</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(j)</span><br><span class="line">    &#125;, j * <span class="number">1000</span>)</span><br><span class="line">  &#125;)(i)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在上述代码中，我们首先使用了立即执行函数将 <code>i</code> 传入函数内部，这个时候值就被固定在了参数 <code>j</code> 上面不会改变，当下次执行 <code>timer</code> 这个闭包的时候，就可以使用外部函数的变量 <code>j</code>，从而达到目的。</p><h4 id="使用-setTimeout-的第三个参数"><a href="#使用-setTimeout-的第三个参数" class="headerlink" title="使用 setTimeout 的第三个参数"></a>使用 setTimeout 的第三个参数</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">1</span>; i &lt;= <span class="number">5</span>; i++) &#123;</span><br><span class="line">  <span class="built_in">setTimeout</span>(</span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">timer</span>(<span class="params">j</span>) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(j)</span><br><span class="line">    &#125;,</span><br><span class="line">    i * <span class="number">1000</span>,</span><br><span class="line">    i</span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>第三个参数会被当成 <code>timer</code> 函数的参数传入。</p><h4 id="使用let定义i"><a href="#使用let定义i" class="headerlink" title="使用let定义i"></a>使用let定义i</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">1</span>; i &lt;= <span class="number">5</span>; i++) &#123;</span><br><span class="line">  <span class="built_in">setTimeout</span>(<span class="keyword">function</span> <span class="title function_">timer</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(i)</span><br><span class="line">  &#125;, i * <span class="number">1000</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="深浅拷贝"><a href="#深浅拷贝" class="headerlink" title="深浅拷贝"></a>深浅拷贝</h2><p>对于引用类型的值，复制操作结束后，两个变量实际上将引用同一个对象。因此，改变其中一个变量，就会影响另一个变量。通常在开发中我们不希望出现这样的问题，我们可以使用深浅拷贝来解决这个问题。</p><h3 id="浅拷贝"><a href="#浅拷贝" class="headerlink" title="浅拷贝"></a>浅拷贝</h3><p>浅复制只复制一层对象的属性，也就是说拷贝原对象的实例，但是对其内部的引用类型值，拷贝的是其引用。实现浅拷贝：</p><ul><li><code>Object.assign(&#123;&#125;, ...)</code></li><li><code>...</code> 扩展操作符</li><li>lodash <code>_.clone</code></li></ul><h3 id="深拷贝"><a href="#深拷贝" class="headerlink" title="深拷贝"></a>深拷贝</h3><p>深拷贝是对对象以及对象的所有子对象进行拷贝。我们可以使用 lodash 的 <code>cloneDeep</code> 方法，但是深拷贝大数据的时候，由于内部实现是递归，所以最好换实现思路。</p><p><strong><code>JSON.parse(JSON.stringify(object))</code> 深拷贝的局限：</strong></p><ol><li>会忽略 <code>undefined</code>、<code>symbol</code>、<code>function</code></li><li>如果被拷贝的对象中有正则表达式，则拷贝之后的正则表达式会变成 <code>Object</code></li><li>不能解决循环引用的对象</li></ol><h2 id="原型、原型链"><a href="#原型、原型链" class="headerlink" title="原型、原型链"></a>原型、原型链</h2><h3 id="原型"><a href="#原型" class="headerlink" title="原型"></a>原型</h3><p>每个 JS 对象都有 <code>__proto__</code> 属性，这个属性指向了原型。这个属性在现在来说已经不推荐直接去使用它了，这只是浏览器在早期为了让我们访问到内部属性 <code>[[prototype]]</code> 来实现的一个东西。原型也是一个对象，并且这个对象包含了很多函数。原型对象通过 <code>constructor</code> 指向了构造函数，构造函数又通过 <code>prototype</code> 指向了原型。</p><h3 id="原型链"><a href="#原型链" class="headerlink" title="原型链"></a>原型链</h3><p>原型链就是多个对象通过 <code>__proto__</code> 的方式连接了起来。</p><h2 id="执行环境及作用域链"><a href="#执行环境及作用域链" class="headerlink" title="执行环境及作用域链"></a>执行环境及作用域链</h2><h3 id="执行环境（execution-context）-作用域"><a href="#执行环境（execution-context）-作用域" class="headerlink" title="执行环境（execution context）&#x2F;作用域"></a>执行环境（execution context）&#x2F;作用域</h3><p>执行环境也称作用域，定义了变量或函数有权访问的其他数据，决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象（variable object），执行环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象，但解析器在处理数据时会在后台使用它。</p><p>在 JS 中，执行环境分为全局执行环境和函数执行环境：</p><ul><li>全局执行环境：最外围的执行环境，根据 ECMAScript 实现所在的宿主环境不同，表示执行环境的对象也不一样。某个执行环境中的所有代码执行完毕后，该环境被销毁，保存在其中的所有变量和函数定义也随之销毁。全局执行环境直到应用程序退出（例如关闭网页或浏览器）时才会被销毁</li><li>函数执行环境：每个函数都有自己的执行环境。当执行流进入一个函数时，函数的环境就会被推入一个环境栈中。而在函数执行之后，栈将其环境弹出，把控制权返回给之前的执行环境。</li></ul><h3 id="作用域链"><a href="#作用域链" class="headerlink" title="作用域链"></a>作用域链</h3><p>当代码在一个环境中执行时，会创建变量对象的一个作用域链（scope chain）。作用域链的用途，是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端，始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数，则将其活动对象（activation object）作为变量对象。活动对象在最开始只包含一个变量，即 arguments 对象（这个对象在全局执行环境中是不存在的）。作用域的下一个变量对象来自包含（外部）环境，而再下一个变量对象则来自下一个包含环境。这样，一直延续到全局执行环境；全局执行环境的变量对象始终都是作用域链中的最后一个对象。</p><p>标识符解析是沿着作用域链一级一级地搜索标识符的过程。搜索过程从作用域的前端开始，然后逐级地向后回溯，直到找到标识符为止（如果找不到标识符，通常会导致错误发生）。</p><h2 id="var、let-和-const-的区别"><a href="#var、let-和-const-的区别" class="headerlink" title="var、let 和 const 的区别"></a>var、let 和 const 的区别</h2><h3 id="声明提升（hoisting）"><a href="#声明提升（hoisting）" class="headerlink" title="声明提升（hoisting）"></a>声明提升（hoisting）</h3><ul><li>函数声明提升：解析器会率先读取函数声明，并使其在任何代码之前可用（可以访问）</li><li>变量声明提升：和函数声明一样，但是赋值操作是在代码执行时才进行的，所以函数表达式不能被提升</li><li>函数提升优先于变量提升，函数提升会把整个函数挪到作用域顶部，变量提升只会把声明挪到作用域顶部</li></ul><h3 id="暂时性死区（temporal-dead-zone）"><a href="#暂时性死区（temporal-dead-zone）" class="headerlink" title="暂时性死区（temporal dead zone）"></a>暂时性死区（temporal dead zone）</h3><p>在解析代码时，JavaScript 引擎也会注意出现在块后面的 <code>let</code> 声明，只不过在此之前不能以任何方式来引用未声明的变量。在 let 声明之前的执行瞬间被称为“暂时性死区”（temporal dead zone），在此阶段引用任何后面才声明的变量都会抛出 ReferenceError。</p><h3 id="let、const与-var-的区别"><a href="#let、const与-var-的区别" class="headerlink" title="let、const与 var 的区别"></a>let、const与 var 的区别</h3><ul><li>块级作用域：<code>let</code> 和 <code>const</code> 支持块级作用域</li><li>暂时性死区：<code>let</code> 和 <code>const</code> 因为存在暂时性死区所以不存在变量提升</li><li>声明冗余：使用 <code>let</code> 和 <code>const</code> 不能进行重复声明，且与 <code>var</code> 混用时也不允许声明冗余。</li><li>在全局作用域下使用 <code>let</code> 和 <code>const</code> 声明变量，变量并不会被挂在到 <code>window</code> 上。</li></ul><h3 id="let-和-const-的区别"><a href="#let-和-const-的区别" class="headerlink" title="let 和 const 的区别"></a>let 和 const 的区别</h3><p>使用 const 声明变量时必须同时初始化变量，且尝试修改 const 声明的变量会导致运行时错误。但是 const 声明的限制只适用于它指向的变量的引用。换句话说，如果 const 变量引用的是一个对象，那么修改这个对象内部的属性并不违反 const 的限制。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> person = &#123;&#125;;</span><br><span class="line">person.<span class="property">name</span> = <span class="string">&#x27;Matt&#x27;</span>;</span><br></pre></td></tr></table></figure><h2 id="事件代理"><a href="#事件代理" class="headerlink" title="事件代理"></a>事件代理</h2><p>事件代理（Event Delegation）,又称之为事件委托。是JavaScript中绑定事件的常用技巧。顾名思义，“事件代理”即是把原本需要绑定的事件委托给父元素，让父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。使用事件代理的好处是可以提高性能。</p><h2 id="标识符"><a href="#标识符" class="headerlink" title="标识符"></a>标识符</h2><p>所谓标识符，就是指变量、函数、属性的名字、或者函数的参数。</p><h2 id="变量"><a href="#变量" class="headerlink" title="变量"></a>变量</h2><p>ECMAScript 变量时松散类型的，所谓松散类型就是可以用来保存任何类型的数据。换句话说，每个变量仅仅是一个用于保存值的占位符而已。</p><blockquote><p>变量只是指向保存值的栈内存地址的指针而已</p></blockquote><h2 id="数据类型"><a href="#数据类型" class="headerlink" title="数据类型"></a>数据类型</h2><p>ECMAScript 中有 5 中简单的数据类型（也称为基本数据类型）: <code>Undefined</code>、<code>Null</code>、<code>Boolean</code>、<code>Number</code> 和 <code>String</code>。还有 1 中复杂数据类型（也称为引用数据类型）—— <code>Object</code>，<code>Object</code> 本质上是由一组无序名值对组成的。</p><h2 id="对象"><a href="#对象" class="headerlink" title="对象"></a>对象</h2><p>ECMAScript 对象其实就是一组数据和功能的集合。对象可以通过执行 <code>new</code> 操作符跟要创建的对象类型的名称来创建。</p><h2 id="操作符"><a href="#操作符" class="headerlink" title="操作符"></a>操作符</h2><p>ECMA-262 描述了一组用于操作数据值的操作符，包括算术操作符、位操作符、关系操作符和相等操作符。ECMAScript 操作符的与众不同之处在于，它们能够适用于很多值，例如字符串、数字值、布尔值，甚至对象。不过在应用于对象时，相应地操作符通常都会调用对象的 <code>valueOf()</code> 和(或)</p><h2 id="函数"><a href="#函数" class="headerlink" title="函数"></a>函数</h2><p>函数对任何语言来说都是一个核心概念。通过函数可以封装任意多条语句，而且可以再任何地方、任何时候调用执行。ECMAScript 中的函数使用 <code>function</code> 关键字来声明，后跟一组参数以及函数体。</p><h2 id="传递参数"><a href="#传递参数" class="headerlink" title="传递参数"></a>传递参数</h2><p>ECMAScript 中所有的函数的参数都是按值传递的。也就是说，把函数外部的值复制给函数内部的参数，就和把值从一个变量复制给另一个变量一样。<strong>基本类型值</strong>的传递如同基本类型变量的复制一样，而<strong>引用类型值</strong>的传递则如同引用类型变量的复制一样。</p><p>在向参数传递基本类型的值时，被传递的值会被复制给一个局部变量（即命名参数，或者用 ECMAScript 的概念来说，就是 arguments 对象中的一个元素）。在向参数传递引用类型的值时，会把这个值在内存地址中的地址复制给一个局部变量，因此这个局部变量的变化会反应在函数的外部。</p><h2 id="执行环境及作用域"><a href="#执行环境及作用域" class="headerlink" title="执行环境及作用域"></a>执行环境及作用域</h2><p>执行环境（execution context，为简单起见，有时也称为”环境“）是 JavaScript 中最为重要的一个概念。执行环境定义了变量和函数有权访问的其他数据，决定了它们各自的行为。每个执行环境都有一个与之关联的<strong>变量对象</strong>（variable object）。环境中定义的所有的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象，但解析器在处理数据时会在后台使用它。</p><p>全局执行环境是最外围的一个执行环境。根据 ECMAScript 实现所在的宿主环境不同，表示执行环境的对象也不一样。在 Web 浏览器中,全局执行环境被认为是 <code>window</code> 对象，因此所有全局变量和函数都是作为 <code>window</code> 对象的属性和方法创建的。某个执行环境中的所有代码执行完毕后，该环境被销毁，保存在其中的所有变量和函数定义也随之销毁（全局执行环境直到应用程序退出——例如关闭网页或浏览器——时才会被销毁）。</p><p>每个函数都有自己的执行环境。当执行流进入一个函数时，函数的环境就会被推入一个环境栈中。而在函数执行之后，栈将其环境弹出，把控制权返回给之前的执行环境。ECMAScript 程序中的执行流正是由这个方便的机制控制着。</p><p>当代码在一个执行环境中执行时，会创建变量对象的一个作用域链。作用域链的用途，是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端，始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数，则将其活动对象作为变量对象。活动对象在最开始时只包含一个变量，即 <code>arguments</code> 对象（这个对象在全局环境中是不存在的）。作用域链中的下一个变量对象来自包含（外部）环境，而下一个变量对象则来自下一个包含环境。这样，一直延续到全局执行环境；全局执行环境的变量对象始终都是作用域链中的最后一个对象。</p><p>标识符解析是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始，然后逐级地向后回溯，直至找到标识符为止（如果找不到标识符，通常会导致错误发生）。</p><h2 id="查询标识符"><a href="#查询标识符" class="headerlink" title="查询标识符"></a>查询标识符</h2><p>当在某个环境中为了读取或写入而引入一个标识符时，必须通过搜索来确定该标识符实际代表什么。搜索过程从作用域的前端开始，向上逐级查询与给定名字匹配的标识符。如果在局部环境中找到了该标识符，搜索过程停止，变量就绪。如果在局部环境中没有找到该变量名，则继续沿作用域链向上搜索。搜索过程将一直追溯到全局环境的变量对象。如果在全局环境中也没找到这个标识符，则意味着该变量尚未声明。</p><h2 id="垃圾收集"><a href="#垃圾收集" class="headerlink" title="垃圾收集"></a>垃圾收集</h2><p>JavaScript 具有自动垃圾收集机制，也就是说，执行环境会负责管理代码执行过程中使用的内存。而在 C 和 C++ 之类的语言中，开发人员的一项基本任务就是手工跟踪内存的使用情况，这是造成许多问题的一个根源。在编写 JavaScript 程序时。开发人员不再关心内存使用问题，所需内存的分配以及无用内存的回收完全实现了自动管理。这种垃圾收集机制的原理其实很简单：找出那些不再使用的变量，然后释放其占用的内存。为此，垃圾收集器会按照固定的时间间隔（或代码执行中预定的收集时间），周期性地执行这一操作。</p><h2 id="引用类型"><a href="#引用类型" class="headerlink" title="引用类型"></a>引用类型</h2><p>引用类型的值（对象）是引用类型的一个实例。在 ECMAScript 中，引用类型是一种数据结构，用于将数据和功能组织在一起。引用类型有时也被称为对象定义，因为它们描述的是一类对象所具有的属性和方法。</p><blockquote><p>虽然引用类型与类看起来相似，但它们并不是相同的概念。</p></blockquote><p>对象是某个特定引用类型的实例。新对象是使用 <code>new</code> 操作符后跟一个构造函数来创建。构造函数本身就是一个函数，只不过这个函数是处于创建新对象的目的而定义的。请看下面这行代码：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> person = <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br></pre></td></tr></table></figure><p>这行代码创建了 <code>Object</code> 引用类型的一个新实例，然后把该实例保存在了变量 <code>person</code> 中。使用的构造函数是 <code>Object</code>，它只为新对象定义了默认的属性和方法。 ECMAScript 提供了很多原生引用类型（例如 <code>Object</code>），以便开发人员用以实现常见的计算任务。</p><h3 id="Object-类型"><a href="#Object-类型" class="headerlink" title="Object 类型"></a>Object 类型</h3><p>到目前为止，我们看到的大多数引用类型值都是 <code>Object</code> 类型的实例；而且，<code>Object</code> 也是 ECMAScript 中使用最多的一个类型。虽然 <code>Object</code> 的实例不具备多少功能，但对于在应用程序中存储和传输数据而言，它们确实是非常理想的选择。</p><h3 id="Array-类型"><a href="#Array-类型" class="headerlink" title="Array 类型"></a>Array 类型</h3><p>除了 <code>Object</code> 之外，<code>Array</code> 类型恐怕是 ECMAScript 中最常用的类型了。而且，ECMAScript 中的数组与其他多数语言中的数组有着相当大的区别。虽然 ECMAScript 数组与其他语言中的数组都是数据的有序集合，但与其他语言不同的是，ECMAScript 数组的每一项可以保存任何类型的数据。也就是说，可以用数组的第一个位置来保存字符串，用第二个位置保存数值，用第三个位置保存对象，以此类推。而且 ECMAScript 数组的大小是可以动态调整的，即可以随着数据的添加自动增长以容纳新增数据。</p><h3 id="Date-类型"><a href="#Date-类型" class="headerlink" title="Date 类型"></a>Date 类型</h3><blockquote><p>推荐使用 <a href="https://github.com/iamkun/dayjs">dayjs</a> 处理时间相关的操作。</p></blockquote><p>ECMAScript 中的 <code>Date</code> 类型是在早期 Java 中的 <code>java.util.Date</code> 类型基础上构建的。为此，<code>Date</code> 类型使用自 UTC 1970 年 1 月 1 日午夜（零时）开始经过的毫秒数来保存日期。在使用这种数据存储格式的条件下，<code>Date</code> 类型保存的日期能够精确到 1970 年 1 月 1 日之前或之后的 2851616 年。</p><p>日期字符串在不同浏览器有不同的实现，例如 <code>new Date(&#39;1970-1-1&#39;)</code> 在 IE、Safari、Android 上是 Invalid Date，而在 Chrome 上是能正常解析的。请务必使用 <code>new Date(&#39;1970/1/1&#39;)</code> 或 <code>new Date(1970, 1, 1)</code> 代替 <code>new Date(&#39;1970-1-1&#39;)</code></p><h3 id="RegExp-类型"><a href="#RegExp-类型" class="headerlink" title="RegExp 类型"></a>RegExp 类型</h3><blockquote><p>推荐在线课程 <a href="https://www.imooc.com/learn/706">JavaScript 正则表达式</a></p></blockquote><p>ECMAScript 通过 RegExp 类型来支持正则表达式。使用下面类似 Perl 的语法，就可以创建一个正则表达式。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> expression = <span class="regexp">/ pattern /</span> flags</span><br></pre></td></tr></table></figure><p>其中的模式（pattern）部分可以是任何简单或复杂的正则表达式，可以包含字符类、限定符、分组、向前查找以及反向引用。每个正则表达式都可带有一个或多个标志（flags），用以标明正则表达式的行为。正则表达式的匹配模式支持下列 3 个标志：</p><ul><li><code>g</code>: 表示全局（global）模式，即模式将被应用于所有字符串，而非在发现第一个匹配项时立即停止；</li><li><code>i</code>: 表示不区分大小写（case-insensitive）模式，即在确定匹配项时忽略模式与字符串的大小写；</li><li><code>m</code>: 表示多行（multiline）模式，即在到达一行文本末尾时还会继续朝查找下一行中是否存在与模式匹配的项。</li></ul><h3 id="Function-类型"><a href="#Function-类型" class="headerlink" title="Function 类型"></a>Function 类型</h3><p>说起来 ECMAScript 中什么最有意思，我想莫过于函数了——而有意思的根源，则在于函数实际上是对象。每个函数都是 <code>Function</code> 类型的实例，而且都与其他引用类型一样具有属性和方法。由于函数是对象，因此函数名实际上也是一个指向函数对象的指针，不会与某个函数绑定。函数通常是使用函数声明语法定义的，如下面的例子所示：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">sum</span>(<span class="params">num1, num2</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> num1 + num2;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这与下面使用函数表达式定义函数的方式几乎相差无几：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> sum = <span class="keyword">function</span>(<span class="params">num1, num2</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> num1 + num2;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="函数的属性和方法"><a href="#函数的属性和方法" class="headerlink" title="函数的属性和方法"></a>函数的属性和方法</h2><ul><li><code>length</code>: 表示函数希望接收的命名参数的个数；</li><li><code>prototype</code>: 在 ECMAScript 中，<code>prototype</code> 属性时不可枚举的，因此使用 <code>for-in</code> 无法发现</li><li><code>apply</code>: <code>apply</code> 方法接收两个参数，一个是在其中运行函数的作用域，另一个是参数数组。</li><li><code>call</code>: <code>call</code> 方法与 <code>apply</code> 方法的作用相同，它们的区别仅在于接收参数的方式不同。对于 <code>call()</code> 方法而言，第一个参数是 <code>this</code> 值没有变化，变化的是其余参数都直接传递给函数。</li><li><code>bind</code>: 这个方法会创建一个函数实例，其中 <code>this</code> 值会被绑定到传给 <code>bind()</code> 函数的值。</li></ul><h2 id="toString、toLocalString、valueOf"><a href="#toString、toLocalString、valueOf" class="headerlink" title="toString、toLocalString、valueOf"></a>toString、toLocalString、valueOf</h2><table><thead><tr><th></th><th>toString</th><th>toLocalString</th><th>valueOf</th></tr></thead><tbody><tr><td>Array</td><td>由数组中每个值的字符串形式拼接而成的一个以逗号分隔的字符串</td><td>同 toString</td><td>返回的还是数组</td></tr><tr><td>Date</td><td>返回带有时区信息的日期和时间</td><td>会按照与浏览器设置的地区相适应的格式返回日期和时间</td><td>返回日期的毫秒表示</td></tr><tr><td>RegExp</td><td>返回正则表达式的字面量</td><td>同 toString</td><td>返回正则表达式本身</td></tr><tr><td>Function</td><td>返回函数的代码</td><td>同 toString</td><td>同 toString</td></tr></tbody></table><h2 id="访问对象属性"><a href="#访问对象属性" class="headerlink" title="访问对象属性"></a>访问对象属性</h2><p>一般来说，访问对象属性时使用的都是点表示法，这也是很多面向对象语言中通用的语法。不过，在 JavaScript 中也可以使用方括号表示法来访问对象的属性。在使用方括号语法时，应该将要访问的属性以字符串的形式放在方括号中，如下面的例子所示：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> person = &#123; <span class="attr">name</span>: <span class="string">&quot;张三&quot;</span> &#125;;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(person.<span class="property">name</span>); <span class="comment">// 张三</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(person[<span class="string">&quot;name&quot;</span>]); <span class="comment">// 张三</span></span><br></pre></td></tr></table></figure><p>从功能上看，这两种访问对象属性的方法没有任何区别。但方括号语法的主要优点是可以通过变量来访问属性，例如：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> propertyName = <span class="string">&quot;name&quot;</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(person[propertyName]); <span class="comment">// &quot;张三&quot;</span></span><br></pre></td></tr></table></figure><p>如果属性名中包含会导致语法错误的字符，或者属性名使用的是关键字或保留字，也可以使用方括号表示法：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">person[<span class="string">&quot;first name&quot;</span>] = <span class="string">&quot;张三&quot;</span>;</span><br></pre></td></tr></table></figure><p>由于 <code>&quot;first name&quot;</code> 中包含一个空格，所以不能使用点表示法来访问它。然后，属性名中是可以包含非字母非数字的，这时候就可以使用方括号表示法来访问他们。</p><p>通常，除非必须使用变量来访问属性，否则我们建议使用点表示法。</p><h2 id="引用类型与基本包装类型的区别"><a href="#引用类型与基本包装类型的区别" class="headerlink" title="引用类型与基本包装类型的区别"></a>引用类型与基本包装类型的区别</h2><p>引用类型与基本包装类型的主要区别就是对象的生存期。使用 <code>new</code> 操作符创建的引用类型的实例，在执行流离开当前作用域之前都一直保存在内存中。而自动创建的基本包装类型的对象，则只存在于一行代码的执行瞬间，然后立即被销毁。这意味着我们不能在运行时为基本类型添加属性和方法。</p><h2 id="单体内置对象"><a href="#单体内置对象" class="headerlink" title="单体内置对象"></a>单体内置对象</h2><p>ECMAScript-262 对内置对象的定义是：”由 ECMAScript 实现提供的、不依赖于宿主环境的对象，这些对象在 ECMAScript 程序执行之前就已经存在了。“意思就是说，开发人员不必显式地实例化内置对象，因为它们已经实例化了。ECMAScript 定义了两个单体内置对象：Global 和 Math。</p><h2 id="面向对象"><a href="#面向对象" class="headerlink" title="面向对象"></a>面向对象</h2><p>面向对象（Object-Oriented，OO）的语言有一个标志，那就是它们都有类的概念，而通过类可以创建任意多个具有属性和方法的对象。ECMAScript 中没有类的概念，因此它的对象也与基于类的语言中的对象有所不同。</p><p>ECMA-262 把对象定义为：”无序属性的集合，其属性可以包含基础值、对象或者函数。“ 严格来讲，这就相当于说是对象时一组没有特定顺序的值。对象的每个属性或方法都有一个名字，而每个名字都映射到一个值。正因为这样，我们可以把 ECMAScript 的对象想象成散列表：无非就是一组键值对，其中值可以是数据或函数。</p><p>每个对象都是基于一个引用类型创建的，这个引用类型可以是原生类型，也可以是开发人员定义的类型。</p><h3 id="构造函数"><a href="#构造函数" class="headerlink" title="构造函数"></a>构造函数</h3><p>构造函数与其他函数的唯一区别，就在于调用它们的方式不同。不过，构造函数毕竟也是函数，不存在定义构造函数的特殊语法。任何函数，只要通过 <code>new</code> 操作符来调用，那他就可以作为构造函数；而任何函数，如果不通过 <code>new</code> 操作符来调用，那它跟普通的函数也不会有什么两样。例如：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span>(<span class="params">name</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">name</span> = name;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">sayName</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="title function_">alert</span>(name);</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 当作普通函数调用</span></span><br><span class="line"><span class="keyword">var</span> person = <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&quot;紫升&quot;</span>);</span><br><span class="line">person.<span class="title function_">sayName</span>(); <span class="comment">// 紫升</span></span><br><span class="line"><span class="comment">// 当作普通函数调用</span></span><br><span class="line"><span class="title class_">Person</span>(<span class="string">&quot;紫升&quot;</span>); <span class="comment">// 添加到 window</span></span><br><span class="line"><span class="variable language_">window</span>.<span class="title function_">sayName</span>(); <span class="comment">// 紫升</span></span><br><span class="line"><span class="comment">// 在另一个对象的作用域中调用</span></span><br><span class="line"><span class="keyword">var</span> o = <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line"><span class="title class_">Person</span>.<span class="title function_">call</span>(o, <span class="string">&quot;洛竹&quot;</span>);</span><br><span class="line">o.<span class="title function_">sayName</span>(); <span class="comment">// 洛竹</span></span><br></pre></td></tr></table></figure><h3 id="原型-prototype"><a href="#原型-prototype" class="headerlink" title="原型 prototype"></a>原型 prototype</h3><p>我们创建的每个函数都有一个 <code>prototype</code>(原型)属性，这个属性是一个指针,指向一个对象，而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。如果按照字面意思来理解，那么 <code>prototype</code> 就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。换句话说，不必在构造函数中定义对象实例的信息，而是将这些信息直接添加到原型对象中。</p><h3 id="查找对象属性"><a href="#查找对象属性" class="headerlink" title="查找对象属性"></a>查找对象属性</h3><p>每当代码读取某个对象的属性时，都会执行一次搜索，目标是具有给定名字的属性。搜索首先从对象实例本身开始。如果在实例中找到了具有给定名字的属性，则返回该属性的值；如果没有找到，则继续搜索指针指向的原型对象，在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这个属性，则返回该属性的值。这就是多个对象实例共享原型所保存的属性和方法的原理。</p><blockquote><p>原型对象最初只包含 <code>constructor</code> 属性，而该属性也是共享的，因此可以通过对象实例访问。</p></blockquote><p>虽然可以通过对象实例访问保存在原型中的值，但却不能通过对象实例重写原型中的值。如果我们在实例中添加了一个属性，而该属性与实例原型中的一个属性同名，那我们就在实例中创建该属性，该属性将会屏蔽原型中的那个属性。换句话说，添加这个属性只会阻止我们访问原型中的那个属性，但不会修改那个属性。即使将这个属性设置为 <code>null</code>，也只会在实例中设置这个属性，而不会回复其指向原型的连接。不过，使用 <code>delete</code> 操作符则可以完全删除实例属性，从而让我们能够重新访问原型中的属性。</p><h3 id="proto-、Object-getPrototypeOf"><a href="#proto-、Object-getPrototypeOf" class="headerlink" title="__proto__、Object.getPrototypeOf"></a><code>__proto__</code>、<code>Object.getPrototypeOf</code></h3><p>创建了自定义的构造函数之后，其原型对象默认只会取得 <code>constructor</code> 属性；至于其他方法，则都是从 <code>Object</code> 集成而来的。当调用构造函数创建一个新实例后，该实例的内部将包含一个指针（内部属性），指向构造函数的原型对象。ECMAScript-262 第 5 版中管这个指针叫 <code>[[Prototype]]</code>。虽然在脚本中没有标准的方式访问 <code>[[Prototype]]</code>，但 Firefox、Safari 和 Chrome 在每个对象上都支持一个属性 <code>__proto__</code>；而在其他实现中，这个属性对脚本则是完全不可见的。不过要明确的真正重要得一点就是，这个连接存在于实例与构造函数的原型对象之间，而不是实例于构造函数之间。</p><p>我们可以随时为原型添加属性和方法，并且修改能够立即在所有对象实例上反映出来，但如果是重写整个原型对象，那么情况就不一样了。我们知道，调用构造函数时会为实例添加一个指向最初原型的 <code>[[Prototype]]</code> 指针，而把原型修改为另外一个对象就等于切断了构造函数与最初原型之间的联系。请记住：实例中的指针仅指向原型，而不指向构造函数。</p><p>虽然在所有的实现中都无法访问到 <code>[[Prototype]]</code>，但可以通过 <code>Object.getPrototypeOf()</code> 方法来确定对象之间是否存在这种关系。从本质上讲，如果 <code>[[Prototype]]</code> 指向调用 <code>isPrototypeOf()</code> 方法的对象，那么这个方法就返回 true。</p><p>ECMAScript 5 增加了一个新方法，叫 <code>Object.getPrototypeOf()</code>，在所有支持的实现中，这个方法返回 <code>[[Prototype]]</code> 的值。</p><h3 id="组合使用构造函数模式和原型模式"><a href="#组合使用构造函数模式和原型模式" class="headerlink" title="组合使用构造函数模式和原型模式"></a>组合使用构造函数模式和原型模式</h3><p>创建自定义类型的最常见方式，就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性，而原型模式用于定义方法和共享属性。结果，每个实例都会有自己的一份实例属性的副本，但同时有共享着对方法的引用，最大限度地节省了内存。另外，这种混杂模式还支持向构造函数传递参数；可谓是集两种模式之长。下面的代码重写了前面的例子。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span>(<span class="params">name, age, job</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">name</span> = name;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">age</span> = age;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">job</span> = job;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">friends</span> = [<span class="string">&quot;Shelby&quot;</span>, <span class="string">&quot;Court&quot;</span>];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span> = &#123;</span><br><span class="line">  <span class="attr">constructor</span>: <span class="title class_">Person</span>,</span><br><span class="line">  <span class="attr">sayName</span>: <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="title function_">alert</span>(<span class="variable language_">this</span>.<span class="property">name</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> person1 = <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&quot;Nicholas&quot;</span>, <span class="number">29</span>, <span class="string">&quot;Software Engineer&quot;</span>);</span><br><span class="line"><span class="keyword">var</span> person2 = <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&quot;Greg&quot;</span>, <span class="number">27</span>, <span class="string">&quot;Doctor&quot;</span>);</span><br><span class="line"></span><br><span class="line">person1.<span class="property">friends</span>.<span class="title function_">push</span>(<span class="string">&quot;Van&quot;</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(person1.<span class="property">friends</span>); <span class="comment">// [&quot;Shelby&quot;, &quot;Court&quot;, &quot;Van&quot;]</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(person2.<span class="property">friends</span>); <span class="comment">// [&quot;Shelby&quot;, &quot;Court&quot;]</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(person1.<span class="property">friends</span> === person2.<span class="property">friends</span>); <span class="comment">// false</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(person1.<span class="property">sayName</span> === person2.<span class="property">sayName</span>); <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>在这个例子中，实例属性都是在构造函数中定义的，而由所有实例共享的属性 <code>constructor</code> 和方法 <code>sayName()</code> 则是在原型中定义的。而修改了 <code>person1.friends</code>(向其中添加一个新字符串)，并不会影响到 <code>person2.friends</code>，因为它们分别引用了不同的数组。</p><p>这种构造函数与原型混成的模式，是目前在 ECMAScript 中使用最广泛、认同度最高的一种创建自定义类型的方法。可以说，这是用来定义引用类型的一种默认模式。</p><h3 id="动态原型模式"><a href="#动态原型模式" class="headerlink" title="动态原型模式"></a>动态原型模式</h3><p>有其他 OO 语言经验的开发人员在看到独立的构造函数和原型时，很可能会感到非常困惑。动态原型模式正式致力于解决这个问题的一个方案，它把所有信息都封装在了构造函数中，而通过在构造函数中初始化原型（仅在必要的情况下），又保持了同时使用构造函数和原型的优点。换句话说，可以通过检查某个应该存在的方法是否有效，来决定是否需要初始化原型。来看一个例子。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Person</span>(<span class="params">name, age, job</span>) &#123;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">name</span> = name</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">age</span> = age</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">job</span> = job</span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">typeof</span> <span class="variable language_">this</span>.<span class="property">sayName</span> !== <span class="string">&quot;function&quot;</span>) &#123;</span><br><span class="line">    <span class="title class_">Person</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">sayName</span> = <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">name</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> friend = <span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&#x27;Nicholas&#x27;</span>, <span class="number">29</span>, <span class="string">&#x27;Software Engineer&#x27;</span>)</span><br><span class="line">friend.<span class="title function_">sayName</span>() <span class="comment">// &quot;Nicholas&quot;</span></span><br></pre></td></tr></table></figure><h3 id="继承"><a href="#继承" class="headerlink" title="继承"></a>继承</h3><p>继承是 OO 语言中的一个最为人津津乐道的概念。许多 OO 语言都支持两种继承方式：接口与继承和实现继承。接口继承只继承方法签名，而实现继承则继承实际的方法。如前所述，由于函数没有签名，在 ECMAScript 中无法实现接口继承。ECMAScript 只支持实现继承，而且其实实现继承主要是依靠原型链来实现的</p><h3 id="继承-1"><a href="#继承-1" class="headerlink" title="继承"></a>继承</h3><h4 id="原型链-1"><a href="#原型链-1" class="headerlink" title="原型链"></a>原型链</h4><p>ECMAScript 描述了原型链的概念，并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。简单回顾一下构造函数、原型和实例的关系：每个构造函数都有一个原型对象，原型对象都包含一个指向构造函数的指针，而实例都包含一个指向原型对象的内部指针 <code>[[Prototype]]</code>。那么，假如我们让原型对象等于另一个类型的实例，结果会怎么样？显然，此时的原型对象将包含指向另一个原型的指针，相应地，另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例，那么上述关系依然成立，如此层层递进，就构成了实例于原型的链条。这就是所谓原型链的基本概念。</p><h4 id="借用构造函数"><a href="#借用构造函数" class="headerlink" title="借用构造函数"></a>借用构造函数</h4><p>在解决原型中包含引用类型值所带来的问题的过程中，开发人员开始使用一种叫做借用构造函数（constructor stealing）的技术（有时候也叫做伪造对象或经典继承）。别忘了，函数只不过是在特定环境中执行代码的对象，因此通过使用 <code>apply()</code> 和 <code>call()</code> 方法也可以在（将来）新创建的对象上执行构造函数。</p><h4 id="组合继承"><a href="#组合继承" class="headerlink" title="组合继承"></a>组合继承</h4><p>组合继承（combination inheritance），有时候也叫做伪经典继承，指的是将来原型链和借用构造函数的技术组合到一块，从而发挥二者之长的一种集成模式。其背后的思路是使用原型链实现对原型属性和方法的继承，而通过借用构造函数来实现对实例属性的继承。这样，既通过在原型上定义方法实现了函数复用，又能够保证每个实例都有它自己的属性。</p><p>组合继承避免了原型链和借用构造函数的缺陷，融合了它们的优点，称为 JavaScript 中最常用的继承模式。而且，<code>instanceOf</code> 和 <code>isPrototypeOf()</code> 也能够用于识别基于组合继承创建的对象。</p><h2 id="深入"><a href="#深入" class="headerlink" title="深入"></a>深入</h2><ul><li><code>var</code>、<code>let</code>、<code>const</code></li><li><code>null</code> 和 <code>undefined</code></li><li><a href="https://www.jianshu.com/p/07ba2b0c8fca">Object.defineProperty()</a></li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/689ef57f4320/</id>
    <link href="https://blog.zisheng.pro/posts/689ef57f4320/"/>
    <published>2020-02-06T06:33:23.000Z</published>
    <summary>本文是《JavaScript高级程序设计》的学习笔记，主要记录了一些重要的知识点。</summary>
    <title>《JavaScript高级程序设计》学习笔记</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="JavaScript" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/JavaScript/"/>
    <category term="JavaScript 浅比较" scheme="https://blog.zisheng.pro/tags/JavaScript-%E6%B5%85%E6%AF%94%E8%BE%83/"/>
    <category term="JavaScript 深比较" scheme="https://blog.zisheng.pro/tags/JavaScript-%E6%B7%B1%E6%AF%94%E8%BE%83/"/>
    <category term="JavaScript 基本类型" scheme="https://blog.zisheng.pro/tags/JavaScript-%E5%9F%BA%E6%9C%AC%E7%B1%BB%E5%9E%8B/"/>
    <category term="JavaScript 引用类型" scheme="https://blog.zisheng.pro/tags/JavaScript-%E5%BC%95%E7%94%A8%E7%B1%BB%E5%9E%8B/"/>
    <category term="JavaScript 原始类型" scheme="https://blog.zisheng.pro/tags/JavaScript-%E5%8E%9F%E5%A7%8B%E7%B1%BB%E5%9E%8B/"/>
    <category term="JavaScript 原始值" scheme="https://blog.zisheng.pro/tags/JavaScript-%E5%8E%9F%E5%A7%8B%E5%80%BC/"/>
    <content>
      <![CDATA[<h2 id="基本类型和引用类型的值"><a href="#基本类型和引用类型的值" class="headerlink" title="基本类型和引用类型的值"></a>基本类型和引用类型的值</h2><p>ECMAScript 变量可能包含两种不同数据类型的值：基本类型值和引用类型值。<strong>基本类型值</strong>指的是简单的数据段，而<strong>引用类型的值</strong>指那些可能由多个值构成的对象。</p><p>在将一个值赋给变量时，解析器必须确定这个值是基本类型值还是引用类型值。</p><p>ECMAScript 中有五种基本类型: Undefined、Null、Boolean、Number 和 String。这五种基本数据类型是按值访问的，因为可以操作保存在变量中的实际的值。</p><p>引用类型的值是保存在内存中的对象。与其他语言不同，JavaScript 不允许直接访问内存中的位置。也就是说不能直接操作对象的内存空间。在复制保存着对象的某个变量时，操作的是对象的引用。但在为对象添加属性时，操作的是实际的对象。</p><blockquote><p>在很多语言中，字符串以对象的形式来表示，因此被认为是引用类型的。ECMAScript 放弃了这一传统</p></blockquote><h2 id="浅比较"><a href="#浅比较" class="headerlink" title="浅比较"></a>浅比较</h2><p><strong>浅比较</strong>也称引用相等，在 javascript 中， <code>===</code> 是作<strong>浅比较</strong>,只检查左右两边是否是同一个对象的引用：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> m = &#123; <span class="attr">a</span>: <span class="number">1</span> &#125;</span><br><span class="line"><span class="keyword">var</span> n = &#123; <span class="attr">a</span>: <span class="number">1</span> &#125;</span><br><span class="line"><span class="keyword">var</span> x = m</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(m === n) <span class="comment">// false</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(m === n) <span class="comment">// true</span></span><br></pre></td></tr></table></figure><blockquote><p>变量名只是指向栈内存的指针，也就是给这个栈内存取得别名</p></blockquote><h2 id="深比较"><a href="#深比较" class="headerlink" title="深比较"></a>深比较</h2><p><strong>深比较</strong>也称原值相等，深比较是指检查两个<strong>对象</strong>的所有属性是否<strong>都相等</strong>,深比较需要以递归的方式遍历两个对象的所有属性，操作比较耗时，深比较不管这两个对象是不是同一对象的引用。</p><h3 id="lodash-isEqual"><a href="#lodash-isEqual" class="headerlink" title="lodash.isEqual"></a>lodash.isEqual</h3><blockquote><p><code>_.isEqual</code>: 执行深比较来确定两者的值是否相等。<br>注意: 这个方法支持比较 <code>arrays</code>, <code>array buffers</code>, <code>booleans</code>, <code>date objects</code>, <code>error objects</code>, <code>maps</code>, <code>numbers</code>, <code>Object objects</code>, <code>regexes</code>, <code>sets</code>, <code>strings</code>, <code>symbols</code>, 以及 <code>typed arrays</code>. Object对象值比较自身的属性，不包括继承的和可枚举的属性。不支持函数和DOM节点比较。</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> _ <span class="keyword">from</span> <span class="string">&#x27;lodash&#x27;</span></span><br><span class="line"><span class="keyword">const</span> m = &#123;<span class="attr">a</span>:<span class="number">1</span>&#125;</span><br><span class="line"><span class="keyword">const</span> n = &#123;<span class="attr">a</span>:<span class="number">1</span>&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(_.<span class="title function_">isEqual</span>(m, n)) <span class="comment">// true</span></span><br></pre></td></tr></table></figure><h3 id="fast-deep-equal"><a href="#fast-deep-equal" class="headerlink" title="fast-deep-equal"></a>fast-deep-equal</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> deepEqual <span class="keyword">from</span> <span class="string">&#x27;fast-deep-equal&#x27;</span></span><br><span class="line"><span class="title function_">deepEqual</span>(&#123;<span class="attr">name</span>: <span class="string">&#x27;紫升&#x27;</span>&#125;,&#123;<span class="attr">name</span>: <span class="string">&#x27;紫升&#x27;</span>&#125;) <span class="comment">// true</span></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/cc30b413f4a8/</id>
    <link href="https://blog.zisheng.pro/posts/cc30b413f4a8/"/>
    <published>2020-02-05T06:33:23.000Z</published>
    <summary>本文介绍了 JavaScript 中的浅比较和深比较</summary>
    <title>JavaScript 浅比较和深比较</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="JavaScript" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/JavaScript/"/>
    <category term="JavaScript" scheme="https://blog.zisheng.pro/tags/JavaScript/"/>
    <category term="ES6" scheme="https://blog.zisheng.pro/tags/ES6/"/>
    <category term="SameValue" scheme="https://blog.zisheng.pro/tags/SameValue/"/>
    <category term="SameValueZero" scheme="https://blog.zisheng.pro/tags/SameValueZero/"/>
    <category term="NaN" scheme="https://blog.zisheng.pro/tags/NaN/"/>
    <content>
      <![CDATA[<p>JavaScript 对值的相等判断有很多不同的算法，<code>==</code> 只是最常用的一种。由于 <code>NaN</code> 和正负零的涉入，除了 <code>==</code> 和 <code>===</code> 之外还有 SameValue 和 SameValueZero 算法。</p><h2 id="、-、SameValue"><a href="#、-、SameValue" class="headerlink" title="==、===、SameValue"></a><code>==</code>、<code>===</code>、SameValue</h2><table><thead><tr><th></th><th>&#x3D;&#x3D;</th><th>&#x3D;&#x3D;&#x3D;</th><th>SameValue（Object.is）</th></tr></thead><tbody><tr><td><code>NaN</code></td><td><code>NaN == NaN</code> <code>false</code></td><td><code>NaN === NaN</code> <code>false</code></td><td><code>Object.is(NaN, NaN)</code> <code>true</code></td></tr><tr><td><code>0</code>、<code>-0</code></td><td><code>0 == -0</code> <code>true</code></td><td><code>0 === -0</code> <code>true</code></td><td><code>Object.is(0, -0)</code> <code>false</code></td></tr></tbody></table><h2 id="SameValueZero"><a href="#SameValueZero" class="headerlink" title="SameValueZero"></a>SameValueZero</h2><p>但是你在使用es6中有没有疑惑这种情况：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> set = <span class="keyword">new</span> <span class="title class_">Set</span>()</span><br><span class="line">set.<span class="title function_">add</span>(<span class="number">0</span>)</span><br><span class="line">set.<span class="title function_">add</span>(<span class="title class_">NaN</span>)</span><br><span class="line">set.<span class="title function_">has</span>(-<span class="number">0</span>) <span class="comment">// true</span></span><br><span class="line">set.<span class="title function_">has</span>(<span class="title class_">NaN</span>) <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>是不是与上述的三种算法的表现多不一样，这就是第四种比较算法 SameValueZero，它与 SameValue 的区别主要在于 <code>0</code> 与 <code>-0</code> 是否相等。</p><p>所以你在实践 <code>includes</code> 方法时，遇到：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> array = [<span class="number">0</span>, <span class="title class_">NaN</span>]</span><br><span class="line">array.<span class="title function_">includes</span>(-<span class="number">0</span>) <span class="comment">// true</span></span><br><span class="line">array.<span class="title function_">includes</span>(<span class="title class_">NaN</span>) <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>就不用大惊小怪了，因为 <code>includes</code> 内部使用的比较算法就是SameValueZero。</p>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/39d6576dcc94/</id>
    <link href="https://blog.zisheng.pro/posts/39d6576dcc94/"/>
    <published>2020-02-05T02:35:30.000Z</published>
    <summary>JavaScript 对值的相等判断有很多不同的算法，== 只是最常用的一种。由于 NaN 和正负零的涉入，除了 == 和 === 之外还有 SameValue 和 SameValueZero 算法。</summary>
    <title>ES 标准中相等比较算法 SameValue 和 SameValueZero</title>
    <updated>2026-07-12T09:21:36.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="JavaScript" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/JavaScript/"/>
    <category term="JavaScript" scheme="https://blog.zisheng.pro/tags/JavaScript/"/>
    <category term="JavaScript 异步编程" scheme="https://blog.zisheng.pro/tags/JavaScript-%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B/"/>
    <category term="JavaScript Promise" scheme="https://blog.zisheng.pro/tags/JavaScript-Promise/"/>
    <category term="JavaScript async/await" scheme="https://blog.zisheng.pro/tags/JavaScript-async-await/"/>
    <category term="JavaScript Generator" scheme="https://blog.zisheng.pro/tags/JavaScript-Generator/"/>
    <category term="JavaScript async generator" scheme="https://blog.zisheng.pro/tags/JavaScript-async-generator/"/>
    <category term="JavaScript async iterator" scheme="https://blog.zisheng.pro/tags/JavaScript-async-iterator/"/>
    <category term="JavaScript async generator function" scheme="https://blog.zisheng.pro/tags/JavaScript-async-generator-function/"/>
    <category term="JavaScript Callback" scheme="https://blog.zisheng.pro/tags/JavaScript-Callback/"/>
    <content>
      <![CDATA[<p>围绕异步编程领域，现代软件设计正在加速旋转，就为了让程序在一个时间内做更多的事情。当你使用更新更强大的 API 时，你会发现在更多的情况下，使用异步编程是唯一的途径。以前写异步代码很困难，现在也需要你来适应，但是已经变容易了很多。</p><h2 id="回调函数（Callback）"><a href="#回调函数（Callback）" class="headerlink" title="回调函数（Callback）"></a>回调函数（Callback）</h2><p>回调函数是异步操作最基本的方法。以下代码就是一个回调函数的例子：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">ajax</span>(url, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// 处理逻辑</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>但是回调函数有一个致命的弱点，就是容易写出回调地狱（Callback hell）。假设多个请求存在依赖性，你可能就会写出如下代码：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">ajax</span>(url, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// 处理逻辑</span></span><br><span class="line">  <span class="title function_">ajax</span>(url1, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 处理逻辑</span></span><br><span class="line">    <span class="title function_">ajax</span>(url2, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="comment">// 处理逻辑</span></span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>回调函数的优点是简单、容易理解和实现，缺点是不利于代码的阅读和维护，各个部分之间高度耦合，使得程序结构混乱、流程难以追踪（尤其是多个回调函数嵌套的情况），而且每个任务只能指定一个回调函数。此外它不能使用 try catch 捕获错误，不能直接 return。</p><h2 id="Promise"><a href="#Promise" class="headerlink" title="Promise"></a>Promise</h2><h3 id="Promise-状态"><a href="#Promise-状态" class="headerlink" title="Promise 状态"></a>Promise 状态</h3><ul><li>pending: 初始状态，既不是成功，也不是失败状态。</li><li>fulfilled: 意味着操作成功完成。</li><li>rejected: 意味着操作失败。</li><li>Settled: Promise 要么被完成，要么被拒绝。Promise 一旦达成，它的状态就不再改变。</li></ul><h3 id="基本用法"><a href="#基本用法" class="headerlink" title="基本用法"></a>基本用法</h3><p>ES6 规定，Promise 对象是一个构造函数，用来生成 Promise 实例。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> promise = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// ... some code</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="comment">/* 异步操作成功 */</span>)&#123;</span><br><span class="line">    <span class="title function_">resolve</span>(value)</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="title function_">reject</span>(error)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h3 id="短路特性"><a href="#短路特性" class="headerlink" title="短路特性"></a>短路特性</h3><ul><li><code>Promise.all()</code>: 如果参数中 promise 有一个失败（rejected），此实例回调失败（reject）</li><li><code>Promise.race()</code>: 如果参数中某个 promise 解决或拒绝，返回的 promise 就会解决或拒绝。</li></ul><h3 id="Promise-all-于-Array-prototype-map"><a href="#Promise-all-于-Array-prototype-map" class="headerlink" title="Promise.all 于 Array.prototype.map"></a>Promise.all 于 Array.prototype.map</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> urls = [<span class="string">&#x27;http://example.com/first.txt&#x27;</span>, <span class="string">&#x27;http://example.com/second.txt&#x27;</span>]</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> promises = urls.<span class="title function_">map</span>(<span class="function"><span class="params">url</span> =&gt;</span> <span class="title function_">downloadText</span>(url))</span><br><span class="line"></span><br><span class="line"><span class="title class_">Promise</span>.<span class="title function_">all</span>(promises).<span class="title function_">then</span>(<span class="function"><span class="params">arr</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(arr)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h3 id="Promise-allSettled"><a href="#Promise-allSettled" class="headerlink" title="Promise.allSettled()"></a>Promise.allSettled()</h3><p><code>Promise.allSettled</code> 这一特性是由 Jason Williams，Robert Pamely 和 Mathias Bynens 提出。</p><p>它返回一个 Array 的 Promise，其元素具有以下类型特征：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> <span class="title class_">SettlementObject</span>&lt;T&gt; = <span class="title class_">FulfillmentObject</span>&lt;T&gt; | <span class="title class_">RejectionObject</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">FulfillmentObject</span>&lt;T&gt; &#123;</span><br><span class="line">  <span class="attr">status</span>: <span class="string">&#x27;fulfilled&#x27;</span></span><br><span class="line">  <span class="attr">value</span>: T</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">RejectionObject</span> &#123;</span><br><span class="line">  <span class="attr">status</span>: <span class="string">&#x27;rejected&#x27;</span></span><br><span class="line">  <span class="attr">reason</span>: <span class="built_in">any</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/60882b7c0e5d/</id>
    <link href="https://blog.zisheng.pro/posts/60882b7c0e5d/"/>
    <published>2020-01-30T09:15:25.000Z</published>
    <summary>围绕异步编程领域，现代软件设计正在加速旋转，就为了让程序在一个时间内做更多的事情。当你使用更新更强大的 API 时，你会发现在更多的情况下，使用异步编程是唯一的途径。以前写异步代码很困难，现在也需要你来适应，但是已经变容易了很多。</summary>
    <title>JavaScript 异步编程</title>
    <updated>2026-07-12T09:21:36.899Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React-Native/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/tags/React-Native/"/>
    <category term="文档" scheme="https://blog.zisheng.pro/tags/%E6%96%87%E6%A1%A3/"/>
    <category term="翻译" scheme="https://blog.zisheng.pro/tags/%E7%BF%BB%E8%AF%91/"/>
    <content>
      <![CDATA[<h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add react-native-scrollable-tab-view</span><br><span class="line"><span class="comment"># support AndroidX</span></span><br><span class="line">$ yarn add @react-native-community/viewpager</span><br></pre></td></tr></table></figure><hr><h2 id="renderTabBar"><a href="#renderTabBar" class="headerlink" title="renderTabBar"></a>renderTabBar</h2><p>TabBar 的样式，系统提供了两种默认的，分别是 <code>DefaultTabBar</code> 和 <code>ScrollableTabBar</code>。当然，我们也可以自定义一个。</p><h3 id="DefaultTabBar"><a href="#DefaultTabBar" class="headerlink" title="DefaultTabBar"></a>DefaultTabBar</h3><p>Tab 会平分在水平方向的空间</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Text</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;react-native&#x27;</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">ScrollableTabView</span>, &#123; <span class="title class_">DefaultTabBar</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;react-native-scrollable-tab-view&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">ScrollableTabView</span> <span class="attr">renderTabBar</span>=<span class="string">&#123;()</span> =&gt;</span> <span class="tag">&lt;<span class="name">DefaultTabBar</span> /&gt;</span>&#125;&gt;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Text</span> <span class="attr">tabLabel</span>=<span class="string">&quot;Tab #1&quot;</span>&gt;</span>My<span class="tag">&lt;/<span class="name">Text</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Text</span> <span class="attr">tabLabel</span>=<span class="string">&quot;Tab #2&quot;</span>&gt;</span>favorite<span class="tag">&lt;/<span class="name">Text</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Text</span> <span class="attr">tabLabel</span>=<span class="string">&quot;Tab #3&quot;</span>&gt;</span>project<span class="tag">&lt;/<span class="name">Text</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">ScrollableTabView</span>&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="ScrollableTabBar"><a href="#ScrollableTabBar" class="headerlink" title="ScrollableTabBar"></a>ScrollableTabBar</h3><p>Tab 可以超过屏幕范围，滚动可以显示</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Text</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;react-native&#x27;</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">ScrollableTabView</span>, &#123; <span class="title class_">ScrollableTabsExample</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;react-native-scrollable-tab-view&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">ScrollableTabView</span> <span class="attr">renderTabBar</span>=<span class="string">&#123;()</span> =&gt;</span> <span class="tag">&lt;<span class="name">ScrollableTabsExample</span> /&gt;</span>&#125;&gt;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Text</span> <span class="attr">tabLabel</span>=<span class="string">&quot;Tab #1&quot;</span>&gt;</span>My<span class="tag">&lt;/<span class="name">Text</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Text</span> <span class="attr">tabLabel</span>=<span class="string">&quot;Tab #2&quot;</span>&gt;</span>favorite<span class="tag">&lt;/<span class="name">Text</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Text</span> <span class="attr">tabLabel</span>=<span class="string">&quot;Tab #3&quot;</span>&gt;</span>project<span class="tag">&lt;/<span class="name">Text</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">ScrollableTabView</span>&gt;</span></span></span><br><span class="line">  )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="children"><a href="#children" class="headerlink" title="children"></a>children</h3><p>表示所有子视图的数组，请设置 <code>tabLabel</code> 属性，你可以实现一个简单地包装组件:</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">Props</span> &#123;</span><br><span class="line">  <span class="attr">tabLabel</span>: <span class="built_in">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">ScrollableTabViewItem</span>: <span class="title class_">React</span>.<span class="property">SFC</span>&lt;<span class="title class_">Props</span>&gt; = <span class="function"><span class="params">props</span> =&gt;</span> <span class="language-xml"><span class="tag">&lt;&gt;</span>&#123;props.children&#125;<span class="tag">&lt;/&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">ScrollableTabViewItem</span></span><br></pre></td></tr></table></figure><h3 id="onChangeTab"><a href="#onChangeTab" class="headerlink" title="onChangeTab"></a>onChangeTab</h3><p>Tab 切换之后会触发此方法，包含一个参数（<code>Object</code>类型），这个对象有两个参数</p><ul><li><code>i</code>: 被选中的 Tab 的下标（从 0 开始）</li><li><code>ref</code>: 被选中的 Tab 对象（基本用不到）</li></ul><h2 id="其他-Props"><a href="#其他-Props" class="headerlink" title="其他 Props"></a>其他 Props</h2><ul><li><code>tabBarPosition</code>(String) 默认值 <code>&#39;top&#39;</code><ul><li><code>top</code>：位于屏幕顶部</li><li><code>bottom</code>：位于屏幕底部</li><li><code>overlayTop</code>：位于屏幕顶部，悬浮在内容视图之上（看颜色区分：视图有颜色，Tab 栏没有颜色）</li><li><code>overlayBottom</code>：位于屏幕底部，悬浮在内容视图之上（看颜色区分：视图有颜色，Tab 栏没有颜色）</li></ul></li><li><code>onScroll</code> (Function): 视图正在滑动的时候触发此方法，包含一个 Float 类型的数字，范围是<code>[0, tab数量-1]</code></li><li><code>locked</code>(Bool): 表示手指是否能拖动视图，默认为 <code>false</code>（表示可以拖动）。设为 <code>true</code> 的话，我们只能“点击”Tab 来切换视图。</li><li><code>initialPage</code>(Integer): 初始化时被选中的 Tab 下标，默认是 0（即第一页）</li><li><code>tabBarUnderlineStyle</code>(style): 设置 <code>DefaultTabBar</code> 和<code>ScrollableTabBarTab</code> 选中时下方横线的样式</li><li><code>tabBarBackgroundColor</code>(String): 设置整个 Tab 这一栏的背景颜色</li><li><code>tabBarActiveTextColor</code>(String): 设置选中 Tab 的文字颜色</li><li><code>tabBarInactiveTextColor(String)</code>: 设置未选中 Tab 的文字颜色</li><li><code>tabBarTextStyle(Object)</code>: 设置 Tab 文字的样式，比如字号、字体等</li><li><code>style</code>: 系统 View 都拥有的属性，基本不会涉及到。</li><li><code>contentProps</code>: 这里要稍微说下 react-native-scrollable-tab-view 的实现，其实在 Android 平台底层用的是 <code>ViewPagerAndroid</code>，iOS 平台用的是 <code>ScrollView</code>。这个属性的意义是：比如我们设置了某个属性，最后这个属性会被应用在 <code>ScrollView</code>&#x2F;<code>ViewPagerAndroid</code>，这样会覆盖库里面默认的，通常官方不建议我们去使用。</li><li><code>scrollWithoutAnimation</code>(Bool，默认为 <code>false</code>): 设置“点击”Tab 时，视图切换是否有动画，默认为 <code>false</code>（即：有动画效果）。</li><li><code>prerenderingSiblingsNumber</code> (Integer): 预加载相近的兄弟节点数量，<code>Infinity</code> 表示渲染所有的兄弟节点， 默认值是 <code>0</code> 表示渲染当前页面</li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/af0984676cf5/</id>
    <link href="https://blog.zisheng.pro/posts/af0984676cf5/"/>
    <published>2020-01-30T07:19:19.000Z</published>
    <summary>react-native-scrollable-tab-view 中文文档</summary>
    <title>react-native-scrollable-tab-view</title>
    <updated>2026-07-12T09:21:36.903Z</updated>
  </entry>
  <entry>
    <author>
      <name>紫升</name>
    </author>
    <category term="前端" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/categories/%E5%89%8D%E7%AB%AF/React-Native/"/>
    <category term="React Native" scheme="https://blog.zisheng.pro/tags/React-Native/"/>
    <category term="Mobx" scheme="https://blog.zisheng.pro/tags/Mobx/"/>
    <content>
      <![CDATA[<p>Mobx 是简单、可扩展的状态管理，React 和 MobX 是一对强力组合。React 通过提供机制把应用状态转换为可渲染组件树并对其进行渲染。而 MobX 提供机制来存储和更新应用状态供 React 使用。</p><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add mobx</span><br><span class="line"><span class="comment"># React 绑定库</span></span><br><span class="line">$ yarn add mobx-react</span><br></pre></td></tr></table></figure><h2 id="启用装饰器语法"><a href="#启用装饰器语法" class="headerlink" title="启用装饰器语法"></a>启用装饰器语法</h2><h3 id="TypeScript"><a href="#TypeScript" class="headerlink" title="TypeScript"></a>TypeScript</h3><p>在 <code>tsconfig.json</code> 中启用编译器选项 <code>&quot;experimentalDecorators&quot;: true</code> 。</p><h3 id="Babel"><a href="#Babel" class="headerlink" title="Babel"></a>Babel</h3><p>在 Babel 中配置 MobX 的方式是使用 mobx preset，这种方式更方便，其中包含了装饰器及其他几个经常与 mobx 一起使用的插件:</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yarn add babel-preset-mobx -D</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">presets</span>: [<span class="string">&#x27;module:metro-react-native-babel-preset&#x27;</span>, <span class="string">&#x27;mobx&#x27;</span>],</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="核心概念"><a href="#核心概念" class="headerlink" title="核心概念"></a>核心概念</h2><h3 id="observable-可观察的状态"><a href="#observable-可观察的状态" class="headerlink" title="@observable: 可观察的状态"></a><code>@observable</code>: 可观察的状态</h3><p>装饰器可以在 ES7 或者 TypeScript 类属性中属性使用，将其转换成可观察的。 <code>@observable</code> 可以在实例字段和属性 <code>getter</code> 上使用。 对于对象的哪部分需要成为可观察的，<code>@observable</code> 提供了细粒度的控制。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; observable, computed &#125; <span class="keyword">from</span> <span class="string">&#x27;mobx&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">OrderLine</span> &#123;</span><br><span class="line">  @observable price = <span class="number">0</span></span><br><span class="line">  @observable amount = <span class="number">1</span></span><br><span class="line"></span><br><span class="line">  @computed <span class="keyword">get</span> <span class="title function_">total</span>() &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">price</span> * <span class="variable language_">this</span>.<span class="property">amount</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="computed-计算值"><a href="#computed-计算值" class="headerlink" title="@computed: 计算值"></a><code>@computed</code>: 计算值</h3><blockquote><p>黄金法则: 如果你想创建一个基于当前状态的值时，请使用 <code>computed</code>。</p></blockquote><p>计算值(computed values)是可以根据现有的状态或其它计算值衍生出的值。 概念上来说，它们与 excel 表格中的公式十分相似。 不要低估计算值，因为它们有助于使实际可修改的状态尽可能的小。 此外计算值还是高度优化过的，所以尽可能的多使用它们。</p><p>不要把 <code>computed</code> 和 <code>autorun</code> 搞混。它们都是响应式调用的表达式，但是，如果你想响应式的产生一个可以被其它 <code>observer</code> 使用的值，请使用 <code>@computed</code>，如果你不想产生一个新值，而想要达到一个效果，请使用 <code>autorun</code>。 举例来说，效果是像打印日志、发起网络请求等这样命令式的副作用。</p><h3 id="action-修改可观察状态"><a href="#action-修改可观察状态" class="headerlink" title="@action: 修改可观察状态"></a><code>@action</code>: 修改可观察状态</h3><p>只有在严格模式(默认是不启用)下使用 MobX 时才需要 <code>action</code> 包装。 建议使用 <code>action</code>，因为它将帮助你更好地组织应用，并表达出一个函数修改状态的意图。 同时,</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Ticker</span> &#123;</span><br><span class="line">  @observable tick = <span class="number">0</span></span><br><span class="line"></span><br><span class="line">  @action increment = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">tick</span>++</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> ticker = <span class="keyword">new</span> <span class="title class_">Ticker</span>()</span><br><span class="line"><span class="built_in">setInterval</span>(ticker.<span class="property">increment</span>, <span class="number">1000</span>)</span><br></pre></td></tr></table></figure><h3 id="runInAction-异步-Action"><a href="#runInAction-异步-Action" class="headerlink" title="runInAction: 异步 Action"></a><code>runInAction</code>: 异步 Action</h3><p><code>runInAction</code> 是个简单的工具函数，它接收代码块并在(异步的)动作中执行。这对于即时创建和执行动作非常有用，例如在异步过程中。<code>runInAction(f)</code> 是 <code>action(f)()</code> 的语法糖。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Store</span> &#123;</span><br><span class="line">  @observable data = []</span><br><span class="line"></span><br><span class="line">  asyncAction = <span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> getData = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="title function_">resolve</span>(&#123; <span class="attr">data</span>: [] &#125;)</span><br><span class="line">      &#125;, <span class="number">2000</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">    <span class="keyword">const</span> &#123; data &#125; = <span class="keyword">await</span> <span class="title function_">getData</span>()</span><br><span class="line">    <span class="title function_">runInAction</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">data</span> = data</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><ul><li>不要传递基础类型值给子组件，使用对象包裹或将整个 store 传给子组件</li><li>在所有渲染 <code>@observable</code> 的组件上使用 <code>@observer</code></li><li>不要拷贝可观察属性并存储在本地</li><li>Render 回调函数不是 render 方法的一部分</li><li>间接引用值尽可能晚的使用</li><li>使用大量的小组件</li><li>在专用组件中渲染列表项</li></ul>]]>
    </content>
    <id>https://blog.zisheng.pro/posts/581baf20cc1c/</id>
    <link href="https://blog.zisheng.pro/posts/581baf20cc1c/"/>
    <published>2020-01-29T12:46:38.000Z</published>
    <summary>Mobx 是简单、可扩展的状态管理，React 和 MobX 是一对强力组合。React 通过提供机制把应用状态转换为可渲染组件树并对其进行渲染。而 MobX 提供机制来存储和更新应用状态供 React 使用。</summary>
    <title>在 React Native 中使用 Mobx</title>
    <updated>2026-07-12T09:21:36.907Z</updated>
  </entry>
</feed>
