<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>H</title>
  
  <subtitle>精通多种语言 Hello World</subtitle>
  <link href="https://blog.hal.wang/atom.xml" rel="self"/>
  
  <link href="https://blog.hal.wang/"/>
  <updated>2026-03-18T15:50:10.586Z</updated>
  <id>https://blog.hal.wang/</id>
  
  <author>
    <name>hal.wang</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Windows 服务器常见服务开启自启</title>
    <link href="https://blog.hal.wang/eb0253e5/"/>
    <id>https://blog.hal.wang/eb0253e5/</id>
    <published>2026-03-18T23:37:39.000Z</published>
    <updated>2026-03-18T15:50:10.586Z</updated>
    
    <content type="html"><![CDATA[<p>包含内容：</p><ul><li>Nginx</li><li>Redis</li></ul><span id="more"></span><h2 id="Redis"><a href="#Redis" class="headerlink" title="Redis"></a>Redis</h2><ol><li>安装服务</li></ol><p>在 redis 目录下以管理员身份执行</p><figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">redis-server --service-install redis.windows.conf</span><br></pre></td></tr></table></figure><ol start="2"><li>启动 Redis 服务</li></ol><figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">redis-server --service-<span class="built_in">start</span></span><br><span class="line"></span><br><span class="line">或任意位置</span><br><span class="line"></span><br><span class="line"><span class="built_in">net</span> <span class="built_in">start</span> nginx</span><br></pre></td></tr></table></figure><ol start="3"><li>停止服务（如需）</li></ol><figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">redis-server --service-stop</span><br><span class="line"></span><br><span class="line">或任意位置</span><br><span class="line"></span><br><span class="line"><span class="built_in">net</span> stop nginx</span><br></pre></td></tr></table></figure><h2 id="Nginx"><a href="#Nginx" class="headerlink" title="Nginx"></a>Nginx</h2><p>Nginx 使用 WinSW 进行托管</p><ol><li>下载对应版本（x86&#x2F;x64）的 WinSW <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL3dpbnN3L3dpbnN3L3JlbGVhc2Vz">https://github.com/winsw/winsw/releases<i class="fa fa-external-link-alt"></i></span></li><li>将下载的 <code>WinSW-X86(X64).exe</code> 重命名为 <code>nginx-service.exe</code> 并和 <code>nginx.exe</code> 放一起</li><li>新建文件 <code>nginx-service.xml</code>，并和 <code>nginx-service.exe</code> 放一起，内容如下</li></ol><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">service</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">id</span>&gt;</span>nginx<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>Nginx<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">description</span>&gt;</span>Nginx Service<span class="tag">&lt;/<span class="name">description</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">executable</span>&gt;</span>nginx.exe<span class="tag">&lt;/<span class="name">executable</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">stopexecutable</span>&gt;</span>nginx.exe -s stop<span class="tag">&lt;/<span class="name">stopexecutable</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">logpath</span>&gt;</span>logs/<span class="tag">&lt;/<span class="name">logpath</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">service</span>&gt;</span></span><br></pre></td></tr></table></figure><ol start="4"><li>在 nginx 目录下以管理员身份执行</li></ol><figure class="highlight cmd"><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">nginx-service.exe install</span><br><span class="line"></span><br><span class="line"><span class="built_in">net</span> <span class="built_in">start</span> nginx</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;包含内容：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Nginx&lt;/li&gt;
&lt;li&gt;Redis&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="记录" scheme="https://blog.hal.wang/categories/%E8%AE%B0%E5%BD%95/"/>
    
    
    <category term="Windows" scheme="https://blog.hal.wang/tags/Windows/"/>
    
  </entry>
  
  <entry>
    <title>Asp.Net Core 在 Windows 下后台自动重启与故障重启</title>
    <link href="https://blog.hal.wang/8b05d23b/"/>
    <id>https://blog.hal.wang/8b05d23b/</id>
    <published>2025-08-02T22:28:15.000Z</published>
    <updated>2026-03-18T15:50:10.582Z</updated>
    
    <content type="html"><![CDATA[<p>在 linux 下后台自动启动和故障重启可以用 systemd 实现，也较容易.</p><p>但在 windows 下文档并没有说如何实现，这里说下本人在 windows 下的使用方法和经验。</p><p>以下有两种实现方式，都亲测稳定使用。</p><span id="more"></span><h2 id="方法-1：任务计划管理程序"><a href="#方法-1：任务计划管理程序" class="headerlink" title="方法 1：任务计划管理程序"></a>方法 1：任务计划管理程序</h2><p>在 <code>计算机管理 -&gt; 系统工具 -&gt; 任务计划程序 -&gt; 任务计划程序库</code> 中添加一个任务</p><ul><li><code>常规</code> 选项卡<ul><li>填写名称，任意即可，如 <code>YourApi</code></li><li>勾选 <code>不管用户是否...</code></li><li>勾选 <code>不存储密码...</code></li><li>勾选 <code>使用最高权限...</code></li><li>勾选 <code>隐藏</code></li></ul></li><li><code>触发器</code> 选项卡，添加触发器，设为 <code>在系统启动时</code></li><li><code>操作</code> 选项卡，新建操作，设置程序的路径和启动参数，如<ul><li><code>程序或脚本</code> 设置为 <code>E:\YourApi\YourApi.exe</code></li></ul></li><li><code>条件</code> 选择卡，取消勾选 <code>只有在计算机使用交流电源...</code></li><li><code>设置</code> 选项卡<ul><li>勾选 <code>允许按需允许任务</code></li><li>勾选 <code>如果任务失败，按以下频率重新启动</code>，按需设置间隔和次数</li><li>取消勾选 <code>如果任务允许时间超过...</code></li><li>取消勾选 <code>如果请求后任务还在运行...</code></li></ul></li></ul><p>保存后，右键单击新建的条目，再点 <code>运行</code> 即可。</p><p>后续电脑重启都会自动运行，或者在任务计划程序这里通过点击右键菜单<code>运行</code>和<code>结束</code>来控制运行。</p><p><em>实测中文路径可能会影响自动启动，建议使用无特殊字符的英文路径。</em></p><h2 id="方法-2：使用系统自带-sc-exe"><a href="#方法-2：使用系统自带-sc-exe" class="headerlink" title="方法 2：使用系统自带 sc.exe"></a>方法 2：使用系统自带 sc.exe</h2><p>这种方式可以将程序作为服务运行，需要修改代码才能支持</p><h3 id="在-Program-cs-中添加代码"><a href="#在-Program-cs-中添加代码" class="headerlink" title="在 Program.cs 中添加代码"></a>在 <code>Program.cs</code> 中添加代码</h3><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">builder.Host.UseWindowsService();</span><br></pre></td></tr></table></figure><p>如</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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">using</span> Microsoft.Extensions.Hosting.WindowsServices;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> builder = WebApplication.CreateBuilder(<span class="keyword">new</span> WebApplicationOptions</span><br><span class="line">&#123;</span><br><span class="line">    Args = <span class="keyword">args</span>,</span><br><span class="line">    ContentRootPath = WindowsServiceHelpers.IsWindowsService()</span><br><span class="line">        ? AppContext.BaseDirectory</span><br><span class="line">        : <span class="literal">default</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 添加 Windows 服务支持</span></span><br><span class="line">builder.Host.UseWindowsService();</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> app = builder.Build();</span><br><span class="line">app.Run();</span><br></pre></td></tr></table></figure><h3 id="编译后在目标计算机的控制台执行"><a href="#编译后在目标计算机的控制台执行" class="headerlink" title="编译后在目标计算机的控制台执行"></a>编译后在目标计算机的控制台执行</h3><figure class="highlight bat"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sc create &lt;name&gt; binPath=&quot;&lt;.exe 路径&gt;&quot; <span class="built_in">start</span>=auto</span><br></pre></td></tr></table></figure><p><code>&lt;name&gt;</code> 是自定义服务名， <code>.exe 路径</code> 是编译后的可执行程序的绝对路径</p><p>如</p><figure class="highlight bat"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sc create YourService binPath=&quot;D:\YourApi\YourApi.exe&quot; <span class="built_in">start</span>=auto</span><br></pre></td></tr></table></figure><p>此时如果打开 <code>服务</code> 就能看到刚才新增的服务了。</p><p>打开 <code>服务</code> 窗口方式：win+R 运行输入 <code>services.msc</code></p><h3 id="设置故障重启"><a href="#设置故障重启" class="headerlink" title="设置故障重启"></a>设置故障重启</h3><p>上面脚本已经开启了自动启动，因此这里只用设置故障重启即可。</p><p>可以使用 <code>服务</code> 窗口图形化设置，右键单击服务后点击 <code>属性</code>，在 <code>恢复</code> 选项卡中设置失败策略</p><p>也可以用脚本设置，如</p><figure class="highlight bat"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sc failure &quot;服务名&quot; actions= restart/<span class="number">60000</span> reset= <span class="number">86400</span></span><br></pre></td></tr></table></figure><p>这表示服务失败后会自动重启（延迟 60 秒），如果 24 小时(86400 秒)内没有再次失败，则失败计数重置。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;在 linux 下后台自动启动和故障重启可以用 systemd 实现，也较容易.&lt;/p&gt;
&lt;p&gt;但在 windows 下文档并没有说如何实现，这里说下本人在 windows 下的使用方法和经验。&lt;/p&gt;
&lt;p&gt;以下有两种实现方式，都亲测稳定使用。&lt;/p&gt;</summary>
    
    
    
    <category term="记录" scheme="https://blog.hal.wang/categories/%E8%AE%B0%E5%BD%95/"/>
    
    
    <category term="C#" scheme="https://blog.hal.wang/tags/C/"/>
    
    <category term=".netcore" scheme="https://blog.hal.wang/tags/netcore/"/>
    
    <category term="Windows" scheme="https://blog.hal.wang/tags/Windows/"/>
    
  </entry>
  
  <entry>
    <title>frp 在 Windows 下后台自动重启与故障重启</title>
    <link href="https://blog.hal.wang/b17a6507/"/>
    <id>https://blog.hal.wang/b17a6507/</id>
    <published>2025-08-02T21:52:37.000Z</published>
    <updated>2026-03-18T15:50:10.586Z</updated>
    
    <content type="html"><![CDATA[<p>frp 在 linux 下后台自动启动和故障重启可以用 systemd 实现，也较容易，文档中也有说明。</p><p>但在 windows 下文档并没有说如何实现，这里说下本人在 windows 下的使用方法和经验。</p><p>以下有两种实现方式，都亲测稳定使用。</p><span id="more"></span><h2 id="方法-1：任务计划管理程序"><a href="#方法-1：任务计划管理程序" class="headerlink" title="方法 1：任务计划管理程序"></a>方法 1：任务计划管理程序</h2><p>在 <code>计算机管理 -&gt; 系统工具 -&gt; 任务计划程序 -&gt; 任务计划程序库</code> 中添加一个任务</p><ul><li><code>常规</code> 选项卡<ul><li>填写名称，任意即可，如 <code>frp</code></li><li>勾选 <code>不管用户是否...</code></li><li>勾选 <code>不存储密码...</code></li><li>勾选 <code>使用最高权限...</code></li><li>勾选 <code>隐藏</code></li></ul></li><li><code>触发器</code> 选项卡，添加触发器，设为 <code>在系统启动时</code></li><li><code>操作</code> 选项卡，新建操作，设置 frp 的路径和启动参数，如<ul><li><code>程序或脚本</code> 设置为 <code>E:\frp\frpc.exe</code></li><li><code>添加参数（可选）</code> 设置为 <code>-c &quot;E:\frp\frpc.ini&quot;</code></li></ul></li><li><code>条件</code> 选择卡，取消勾选 <code>只有在计算机使用交流电源...</code></li><li><code>设置</code> 选项卡<ul><li>勾选 <code>允许按需允许任务</code></li><li>勾选 <code>如果任务失败，按以下频率重新启动</code>，按需设置间隔和次数</li><li>取消勾选 <code>如果任务允许时间超过...</code></li><li>取消勾选 <code>如果请求后任务还在运行...</code></li></ul></li></ul><p>保存后，右键单击新建的条目，再点 <code>运行</code> 即可。</p><p>后续电脑重启都会自动运行，或者在任务计划程序这里通过点击右键菜单<code>运行</code>和<code>结束</code>来控制运行。</p><p><em>实测中文路径可能会影响自动启动，建议使用无特殊字符的英文路径。</em></p><h2 id="方法-2：使用-pm2"><a href="#方法-2：使用-pm2" class="headerlink" title="方法 2：使用 pm2"></a>方法 2：使用 pm2</h2><p>pm2 虽然是管理 nodejs 的，但实测也可以托管 frp 程序，且运行稳定。</p><p>这个方法需要 nodejs 环境，如果没有，自行搜索安装方式。</p><p>下面直接使用</p><h3 id="安装-npm-包"><a href="#安装-npm-包" class="headerlink" title="安装 npm 包"></a>安装 npm 包</h3><figure class="highlight bat"><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 install -g pm2</span><br><span class="line">npm install -g pm2-windows-startup</span><br></pre></td></tr></table></figure><h3 id="添加并启动服务"><a href="#添加并启动服务" class="headerlink" title="添加并启动服务"></a>添加并启动服务</h3><figure class="highlight bat"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pm2 <span class="built_in">start</span> -n &lt;name&gt; &lt;frp.exe 路径&gt; -- -c &quot;&lt;配置文件路径&gt;&quot;</span><br></pre></td></tr></table></figure><p>frp 参数是服务名称，可自定义，frpc.exe 和 frpc.ini 的路径替换为你的实际路径。</p><p>如</p><figure class="highlight bat"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pm2 <span class="built_in">start</span> -n frp E:\frp\frpc.exe -- -c &quot;E:\frp\frpc.ini&quot;</span><br></pre></td></tr></table></figure><h3 id="设置自动启动"><a href="#设置自动启动" class="headerlink" title="设置自动启动"></a>设置自动启动</h3><p>上面脚本默认是有故障重启的，因此这里只用设置开机自启即可。</p><p>控制台执行语句</p><figure class="highlight bat"><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">pm2-startup install</span><br><span class="line">pm2 save</span><br></pre></td></tr></table></figure><h2 id="其他守护程序"><a href="#其他守护程序" class="headerlink" title="其他守护程序"></a>其他守护程序</h2><p>本人也尝试过其他守护程序，主流的如 <code>nssm</code> 和 <code>WinSW</code> 目前都已经不再维护。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;frp 在 linux 下后台自动启动和故障重启可以用 systemd 实现，也较容易，文档中也有说明。&lt;/p&gt;
&lt;p&gt;但在 windows 下文档并没有说如何实现，这里说下本人在 windows 下的使用方法和经验。&lt;/p&gt;
&lt;p&gt;以下有两种实现方式，都亲测稳定使用。&lt;/p&gt;</summary>
    
    
    
    <category term="记录" scheme="https://blog.hal.wang/categories/%E8%AE%B0%E5%BD%95/"/>
    
    
    <category term="C#" scheme="https://blog.hal.wang/tags/C/"/>
    
    <category term="Windows" scheme="https://blog.hal.wang/tags/Windows/"/>
    
    <category term="frp" scheme="https://blog.hal.wang/tags/frp/"/>
    
  </entry>
  
  <entry>
    <title>成为 TypeScript 高手 - 快来做体操</title>
    <link href="https://blog.hal.wang/7c21479a/"/>
    <id>https://blog.hal.wang/7c21479a/</id>
    <published>2024-07-01T13:50:36.000Z</published>
    <updated>2026-03-18T15:50:10.596Z</updated>
    
    <content type="html"><![CDATA[<p>快来做体操 —— 本文带你感受 ts 类型系统的魅力，提升 ts 的能力。</p><p>ts 类型体操，是对 ts 类型计算的一种戏称，因为 ts 的类型非常灵活，且复杂度够深。</p><p>做操之前，先保证了解这些：</p><ul><li><span class="exturl" data-url="aHR0cHM6Ly9kZXZlbG9wZXIubW96aWxsYS5vcmcvemgtQ04vZG9jcy9XZWIvSmF2YVNjcmlwdC9HdWlkZQ==">JavaScript 语法<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly93d3cudHlwZXNjcmlwdGxhbmcub3JnLw==">TypeScript 语法<i class="fa fa-external-link-alt"></i></span></li></ul><span id="more"></span><h2 id="基础"><a href="#基础" class="headerlink" title="基础"></a>基础</h2><p>TypeScript 的类型系统也可以做很多运算，现在我们来了解一些基础逻辑运算。</p><h3 id="交叉"><a href="#交叉" class="headerlink" title="交叉 &amp;"></a>交叉 <code>&amp;</code></h3><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">type</span> <span class="title class_">NewType</span> = &#123; <span class="attr">a</span>: <span class="built_in">number</span> &#125; &amp; &#123; <span class="attr">b</span>: <span class="built_in">string</span> &#125;;</span><br></pre></td></tr></table></figure><h3 id="联合"><a href="#联合" class="headerlink" title="联合 |"></a>联合 <code>|</code></h3><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">type</span> <span class="title class_">Union</span> = <span class="string">&quot;a&quot;</span> | <span class="string">&quot;b&quot;</span> | <span class="number">1</span> | <span class="number">2</span>;</span><br></pre></td></tr></table></figure><h3 id="约束-extends"><a href="#约束-extends" class="headerlink" title="约束 extends"></a>约束 <code>extends</code></h3><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">Base</span> &#123;</span><br><span class="line">  <span class="attr">length</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="comment">// T extends Base 限制了 T 必须包含 length，而且 length 的类型必须是 number</span></span><br><span class="line"><span class="keyword">function</span> fn1&lt;T <span class="keyword">extends</span> <span class="title class_">Base</span>&gt;(<span class="attr">arg</span>: T): <span class="built_in">number</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> arg.<span class="property">length</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="条件-T-extends-U-X-Y"><a href="#条件-T-extends-U-X-Y" class="headerlink" title="条件 T extends U ? X : Y"></a>条件 <code>T extends U ? X : Y</code></h3><p>类似三元表达式，即如果 T 是 U 的子类型，那么类型是 X，否则是 Y</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="keyword">type</span> <span class="title class_">IsString</span>&lt;T&gt; = T <span class="keyword">extends</span> <span class="built_in">string</span> ? <span class="built_in">string</span> : <span class="built_in">number</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> t1 = <span class="title class_">IsString</span>&lt;<span class="number">1</span>&gt;; <span class="comment">// number</span></span><br><span class="line"><span class="keyword">type</span> t2 = <span class="title class_">IsString</span>&lt;<span class="string">&quot;abc&quot;</span>&gt;; <span class="comment">// string</span></span><br></pre></td></tr></table></figure><h3 id="索引查询-keyof-T"><a href="#索引查询-keyof-T" class="headerlink" title="索引查询 keyof T"></a>索引查询 <code>keyof T</code></h3><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">const</span> obj = &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&quot;string&quot;</span>,</span><br><span class="line">  <span class="attr">age</span>: <span class="number">1</span>,</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">type</span> t2 = keyof <span class="keyword">typeof</span> obj; <span class="comment">// &#x27;name&#x27; | &#x27;age&#x27;</span></span><br></pre></td></tr></table></figure><h3 id="索引访问-T-K"><a href="#索引访问-T-K" class="headerlink" title="索引访问 T[K]"></a>索引访问 <code>T[K]</code></h3><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> <span class="title class_">TestInterface</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">type</span> t = <span class="title class_">TestInterface</span>[<span class="string">&quot;name&quot;</span>]; <span class="comment">// string</span></span><br></pre></td></tr></table></figure><p>配合 <code>keyof</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">type</span> <span class="title class_">TestInterface</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">type</span> t = <span class="title class_">TestInterface</span>[keyof <span class="title class_">TestInterface</span>]; <span class="comment">// string | number</span></span><br></pre></td></tr></table></figure><h3 id="遍历索引-in"><a href="#遍历索引-in" class="headerlink" title="遍历索引 in"></a>遍历索引 <code>in</code></h3><p>用于遍历联合类型，常配合 keyof 使用，可以根据已有类型创建新类型</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> obj = &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&quot;string&quot;</span>,</span><br><span class="line">  <span class="attr">age</span>: <span class="number">1</span>,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> t = &#123;</span><br><span class="line">  [P <span class="keyword">in</span> keyof <span class="keyword">typeof</span> obj]: <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">// type t = &#123;</span></span><br><span class="line"><span class="comment">//   name: boolean;</span></span><br><span class="line"><span class="comment">//   age: boolean;</span></span><br><span class="line"><span class="comment">// &#125;;</span></span><br></pre></td></tr></table></figure><p>再比如复制出一份只读类型，给所有索引都加上 <code>readonly</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">type</span> <span class="title class_">Readonly</span>&lt;T&gt; = &#123;</span><br><span class="line">  <span class="keyword">readonly</span> [P <span class="keyword">in</span> keyof T]: T[P];</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>或去掉 readonly</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">type</span> <span class="title class_">NotReadonly</span>&lt;T&gt; = &#123;</span><br><span class="line">  -<span class="keyword">readonly</span> [P <span class="keyword">in</span> keyof T]: T[P];</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="索引重映射-as"><a href="#索引重映射-as" class="headerlink" title="索引重映射 as"></a>索引重映射 <code>as</code></h3><p>在索引后面加个 as 语句，可以对索引类型做过滤和转换</p><p>比如仅保留类型为 <code>number</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">type</span> <span class="title class_">FilterNumber</span>&lt;T&gt; = &#123;</span><br><span class="line">  [P <span class="keyword">in</span> keyof T <span class="keyword">as</span> T[P] <span class="keyword">extends</span> <span class="built_in">number</span> ? P : <span class="built_in">never</span>]: T[P];</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> t1 = &#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">type</span> t2 = <span class="title class_">FilterNumber</span>&lt;t1&gt;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等同于</span></span><br><span class="line"><span class="comment">// type t2 = &#123;</span></span><br><span class="line"><span class="comment">//   age: number;</span></span><br><span class="line"><span class="comment">// &#125;;</span></span><br></pre></td></tr></table></figure><p>再比如给索引都加上前缀 <code>prefix-</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">type</span> <span class="title class_">AddPrefix</span>&lt;T&gt; = &#123;</span><br><span class="line">  [P <span class="keyword">in</span> keyof T <span class="keyword">as</span> <span class="string">`prefix-<span class="subst">$&#123;P &amp; <span class="built_in">string</span>&#125;</span>`</span>]: T[P];</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> t1 = &#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">type</span> t2 = <span class="title class_">AddPrefix</span>&lt;t1&gt;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等同于</span></span><br><span class="line"><span class="comment">// type t2 = &#123;</span></span><br><span class="line"><span class="comment">//   &quot;prefix-name&quot;: string;</span></span><br><span class="line"><span class="comment">//   &quot;prefix-age&quot;: number;</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br></pre></td></tr></table></figure><p>再如，交换类型的 key 和 value</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">type</span> <span class="title class_">Flip</span>&lt;T <span class="keyword">extends</span> <span class="title class_">Record</span>&lt;<span class="built_in">any</span>, <span class="built_in">any</span>&gt;&gt; = &#123;</span><br><span class="line">  [<span class="title class_">Key</span> <span class="keyword">in</span> keyof T <span class="keyword">as</span> <span class="string">`<span class="subst">$&#123;T[Key]&#125;</span>`</span>]: <span class="title class_">Key</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> t1 = &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&quot;wang&quot;</span>;</span><br><span class="line">  <span class="attr">age</span>: <span class="number">18</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> t2 = <span class="title class_">Flip</span>&lt;t1&gt;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等同于</span></span><br><span class="line"><span class="comment">// type t2 = &#123;</span></span><br><span class="line"><span class="comment">//     wang: &quot;name&quot;;</span></span><br><span class="line"><span class="comment">//     18: &quot;age&quot;;</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br></pre></td></tr></table></figure><h2 id="TypeScript-内置高级类型"><a href="#TypeScript-内置高级类型" class="headerlink" title="TypeScript 内置高级类型"></a>TypeScript 内置高级类型</h2><p>内置高级类型是利用基础语法，做出各种变换，从而支持高级类型。类型体操也是同样的过程和操作</p><h3 id="Readonly"><a href="#Readonly" class="headerlink" title="Readonly"></a>Readonly</h3><p>把索引改为只读，用 in 操作符遍历索引，映射为一个新类型，并将索引改为 readonly</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">type</span> <span class="title class_">Readonly</span>&lt;T&gt; = &#123;</span><br><span class="line">  <span class="keyword">readonly</span> [P <span class="keyword">in</span> keyof T]: T[P];</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> t1 = &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&quot;string&quot;</span>;</span><br><span class="line">  <span class="attr">age</span>: <span class="number">18</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> t2 = <span class="title class_">Readonly</span>&lt;t1&gt;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等同于</span></span><br><span class="line"><span class="comment">// type t2 = &#123;</span></span><br><span class="line"><span class="comment">//   readonly name: &#x27;string&#x27;;</span></span><br><span class="line"><span class="comment">//   readonly age: 18;</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br></pre></td></tr></table></figure><h3 id="Partial"><a href="#Partial" class="headerlink" title="Partial"></a>Partial</h3><p>把索引变为可选，用 in 操作符遍历索引，映射为一个新类型，给索引加上 <code>?</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">type</span> <span class="title class_">Partial</span>&lt;T&gt; = &#123;</span><br><span class="line">  [P <span class="keyword">in</span> keyof T]?: T[P];</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> t1 = &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&quot;string&quot;</span>;</span><br><span class="line">  <span class="attr">age</span>: <span class="number">18</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> t2 = <span class="title class_">Partial</span>&lt;t1&gt;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等同于</span></span><br><span class="line"><span class="comment">// type t2 = &#123;</span></span><br><span class="line"><span class="comment">//   name?: &quot;string&quot; | undefined;</span></span><br><span class="line"><span class="comment">//   age?: 18 | undefined;</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br></pre></td></tr></table></figure><h3 id="Required"><a href="#Required" class="headerlink" title="Required"></a>Required</h3><p>把索引变为必选，用 in 操作符遍历索引，映射为一个新类型，给索引移除 &#96;?</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">type</span> <span class="title class_">Required</span>&lt;T&gt; = &#123;</span><br><span class="line">  [P <span class="keyword">in</span> keyof T]-?: T[P];</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> t1 = &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&quot;string&quot;</span>;</span><br><span class="line">  <span class="attr">age</span>?: <span class="number">18</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> t2 = <span class="title class_">Required</span>&lt;t1&gt;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等同于</span></span><br><span class="line"><span class="comment">// type t2 = &#123;</span></span><br><span class="line"><span class="comment">//   name: &#x27;string&#x27;;</span></span><br><span class="line"><span class="comment">//   age: 18;</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br></pre></td></tr></table></figure><h3 id="Pick"><a href="#Pick" class="headerlink" title="Pick"></a>Pick</h3><p>用 in 遍历自定义参数，配合联合类型，生成新的映射类型</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">type</span> <span class="title class_">Pick</span>&lt;T, K <span class="keyword">extends</span> keyof T&gt; = &#123;</span><br><span class="line">  [P <span class="keyword">in</span> K]: T[P];</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> t1 = &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&quot;string&quot;</span>;</span><br><span class="line">  <span class="attr">age</span>: <span class="number">18</span>;</span><br><span class="line">  <span class="attr">email</span>: <span class="string">&quot;string&quot;</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> t2 = <span class="title class_">Pick</span>&lt;t1, <span class="string">&quot;name&quot;</span> | <span class="string">&quot;age&quot;</span>&gt;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等同于</span></span><br><span class="line"><span class="comment">// type t2 = &#123;</span></span><br><span class="line"><span class="comment">//   name: &quot;string&quot;;</span></span><br><span class="line"><span class="comment">//   age: 18;</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br></pre></td></tr></table></figure><h3 id="Record"><a href="#Record" class="headerlink" title="Record"></a>Record</h3><p>用 in 遍历自定义参数，创建新的映射类型</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">type</span> <span class="title class_">Record</span>&lt;K <span class="keyword">extends</span> keyof <span class="built_in">any</span>, T&gt; = &#123;</span><br><span class="line">  [P <span class="keyword">in</span> K]: T;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> t1 = &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&quot;string&quot;</span>;</span><br><span class="line">  <span class="attr">age</span>: <span class="number">18</span>;</span><br><span class="line">  <span class="attr">email</span>: <span class="string">&quot;string&quot;</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> t2 = <span class="title class_">Record</span>&lt;<span class="string">&quot;name&quot;</span> | <span class="string">&quot;age&quot;</span>, <span class="built_in">boolean</span>&gt;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等同于</span></span><br><span class="line"><span class="comment">// type t2 = &#123;</span></span><br><span class="line"><span class="comment">//   name: boolean;</span></span><br><span class="line"><span class="comment">//   age: boolean;</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br></pre></td></tr></table></figure><h3 id="Exclude"><a href="#Exclude" class="headerlink" title="Exclude"></a>Exclude</h3><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> <span class="title class_">Exclude</span>&lt;T, U&gt; = T <span class="keyword">extends</span> U ? <span class="built_in">never</span> : T;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> t1 = <span class="title class_">Exclude</span>&lt;<span class="string">&quot;name&quot;</span> | <span class="string">&quot;age&quot;</span>, <span class="string">&quot;name&quot;</span>&gt;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等同于</span></span><br><span class="line"><span class="comment">// type t1 = &quot;age&quot;;</span></span><br></pre></td></tr></table></figure><h3 id="Extract"><a href="#Extract" class="headerlink" title="Extract"></a>Extract</h3><p>取联合类型的部分类型，与 <code>Exclude</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">type</span> <span class="title class_">Extract</span>&lt;T, U&gt; = T <span class="keyword">extends</span> U ? T : <span class="built_in">never</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> t1 = <span class="title class_">Extract</span>&lt;<span class="string">&quot;name&quot;</span> | <span class="string">&quot;age&quot;</span>, <span class="string">&quot;name&quot;</span>&gt;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等同于</span></span><br><span class="line"><span class="comment">// type t1 = &quot;name&quot;;</span></span><br></pre></td></tr></table></figure><h3 id="Omit"><a href="#Omit" class="headerlink" title="Omit"></a>Omit</h3><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">type</span> <span class="title class_">Omit</span>&lt;T, K <span class="keyword">extends</span> keyof <span class="built_in">any</span>&gt; = <span class="title class_">Pick</span>&lt;T, <span class="title class_">Exclude</span>&lt;keyof T, K&gt;&gt;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> t1 = &#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&quot;string&quot;</span>;</span><br><span class="line">  <span class="attr">age</span>: <span class="number">18</span>;</span><br><span class="line">  <span class="attr">email</span>: <span class="string">&quot;string&quot;</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> t2 = <span class="title class_">Omit</span>&lt;t1, <span class="string">&quot;name&quot;</span> | <span class="string">&quot;age&quot;</span>&gt;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等同于</span></span><br><span class="line"><span class="comment">// type t2 = &#123;</span></span><br><span class="line"><span class="comment">//   email: &quot;string&quot;;</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br></pre></td></tr></table></figure><h2 id="简单体操"><a href="#简单体操" class="headerlink" title="简单体操"></a>简单体操</h2><p><em>未完待续</em></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;快来做体操 —— 本文带你感受 ts 类型系统的魅力，提升 ts 的能力。&lt;/p&gt;
&lt;p&gt;ts 类型体操，是对 ts 类型计算的一种戏称，因为 ts 的类型非常灵活，且复杂度够深。&lt;/p&gt;
&lt;p&gt;做操之前，先保证了解这些：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cHM6Ly9kZXZlbG9wZXIubW96aWxsYS5vcmcvemgtQ04vZG9jcy9XZWIvSmF2YVNjcmlwdC9HdWlkZQ==&quot;&gt;JavaScript 语法&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cHM6Ly93d3cudHlwZXNjcmlwdGxhbmcub3JnLw==&quot;&gt;TypeScript 语法&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>C# 多线程总结</title>
    <link href="https://blog.hal.wang/3189c6ac/"/>
    <id>https://blog.hal.wang/3189c6ac/</id>
    <published>2024-05-07T10:48:04.000Z</published>
    <updated>2026-03-18T15:50:10.582Z</updated>
    
    <content type="html"><![CDATA[<p>内容概要</p><ul><li>前台线程&#x2F;后台线程</li><li>线程优先级</li><li>实现多线程的方法 Thread &#x2F; ThreadPool &#x2F; Parallel &#x2F; Task &#x2F; BackgroundWorker</li><li>线程同步，线程锁</li></ul><span id="more"></span><h2 id="C-中的多线程"><a href="#C-中的多线程" class="headerlink" title="C# 中的多线程"></a>C# 中的多线程</h2><p>在操作系统中创建进程 <code>Process</code> 比较耗资源，因此 CLI 引入了 <code>AppDomain</code></p><p>AppDomain 并不存在于操作系统，只存在于 .NET CLI 中，并且不能脱离于 Process</p><p>一个 Process 中可以有多个 AppDomain，从而可以使用 AppDomain 隔离同一进程下的资源</p><p>多个 AppDomain 之间不能互相访问代码。在程序层面，一个 AppDomain 相当于一个独立的程序</p><p>每个 AppDomain 下可以拥有多个 Thread</p><p>在多线程程序开发中，一般使用的都是 Thread 或其封装类</p><h2 id="前台-后台线程"><a href="#前台-后台线程" class="headerlink" title="前台&#x2F;后台线程"></a>前台&#x2F;后台线程</h2><p>线程按功能分为两类</p><ul><li>前台线程</li><li>后台线程</li></ul><h3 id="前台线程"><a href="#前台线程" class="headerlink" title="前台线程"></a>前台线程</h3><p>前台线程一般用来处理输入输出、响应事件、处理消息</p><p>UI 线程属于前台线程</p><p>如果前台线程都已经结束，那么这个程序就不再运行。反之只要有前台线程在运行，程序就在运行</p><p>如果程序退出后，因为异常而没有退出干净，在任务管理器中仍然能看到，就是因为存在没有结束的前台线程</p><h3 id="后台线程"><a href="#后台线程" class="headerlink" title="后台线程"></a>后台线程</h3><p>后台线程一般用于处理耗时的任务，不会造成界面卡顿</p><p>程序退出时，所有后台线程会被强制关闭。因此退出程序可以不用手动停止后台线程。</p><h2 id="优先级"><a href="#优先级" class="headerlink" title="优先级"></a>优先级</h2><p>在多线程并发环境中，多线程的执行顺序是随机不可预测的</p><p>线程开始后，只是被放在线程池中等待 CPU 调度</p><p>优先级越高的线程， CPU 分配给该线程的时间片就更多</p><p>因此优先级高并不代表肯定先执行，只是先执行的机会更大，先执行结束的可能性也更大</p><h2 id="实现多线程的方法"><a href="#实现多线程的方法" class="headerlink" title="实现多线程的方法"></a>实现多线程的方法</h2><p>实现多线程最基础的方法是用 Thread，除此之外还有几种抽象出来的类也可以实现多线程</p><ul><li>Thread 线程</li><li>ThreadPool 线程池</li><li>Parallel</li><li>Task</li><li>BackgroundWorker</li></ul><h3 id="Thread"><a href="#Thread" class="headerlink" title="Thread"></a>Thread</h3><p>使用多线程最基础的类</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><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> thread = <span class="keyword">new</span> Thread(()=&gt;&#123;</span><br><span class="line">  <span class="comment">// DO</span></span><br><span class="line">&#125;);</span><br><span class="line">thread.Start();</span><br></pre></td></tr></table></figure><h4 id="传参"><a href="#传参" class="headerlink" title="传参"></a>传参</h4><p><code>Start</code> 函数也可以传参</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><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> thread = <span class="keyword">new</span> Thread((obj)=&gt;&#123;</span><br><span class="line">  <span class="comment">// DO</span></span><br><span class="line">&#125;);</span><br><span class="line">thread.Start(<span class="string">&quot;Thread&quot;</span>);</span><br></pre></td></tr></table></figure><h4 id="线程命名"><a href="#线程命名" class="headerlink" title="线程命名"></a>线程命名</h4><p>Thread 有 Name 属性，赋值后有利于代码调试</p><p>Name 只能赋值一次</p><h4 id="前台-后台线程-1"><a href="#前台-后台线程-1" class="headerlink" title="前台&#x2F;后台线程"></a>前台&#x2F;后台线程</h4><p>Thread 类默认创建为前台线程，可通过 IsBackground 属性，设置或获取该线程属于前台线程还是后台线程</p><h3 id="ThreadPool"><a href="#ThreadPool" class="headerlink" title="ThreadPool"></a>ThreadPool</h3><p><code>ThreadPool</code>（线程池）维护了多个线程，用于执行小任务，防止频繁创建线程</p><p>在线程池中的线程执行完后不会自动移除，而是处于挂起的状态，如果再次向线程池发出请求，那么挂起的线程会被激活，不用创建新的线程就可以执行任务，可以节约创建和销毁线程的开销</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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">using</span> System.Diagnostics;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="built_in">int</span> count = <span class="number">1000</span>;</span><br><span class="line"></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">var</span> watch = <span class="keyword">new</span> Stopwatch();</span><br><span class="line">    watch.Start();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; count; i++)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">new</span> Thread(() =&gt; &#123; &#125;).Start();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    watch.Stop();</span><br><span class="line">    Console.WriteLine(<span class="string">$&quot;Thread 创建 <span class="subst">&#123;count&#125;</span> 个线程需要花费时间 (Ticks)：<span class="subst">&#123;watch.ElapsedTicks&#125;</span>&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">var</span> watch = <span class="keyword">new</span> Stopwatch();</span><br><span class="line">    watch.Start();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; count; i++)</span><br><span class="line">    &#123;</span><br><span class="line">        ThreadPool.QueueUserWorkItem(<span class="keyword">new</span> WaitCallback((obj) =&gt; &#123; &#125;));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    watch.Stop();</span><br><span class="line">    Console.WriteLine(<span class="string">$&quot;ThreadPool 创建 <span class="subst">&#123;count&#125;</span> 个线程需要花费时间 (Ticks)：<span class="subst">&#123;watch.ElapsedTicks&#125;</span>&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">Console.ReadKey();</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></pre></td><td class="code"><pre><span class="line">Thread 创建 1000 个线程需要花费时间 (Ticks)：744179</span><br><span class="line">ThreadPool 创建 1000 个线程需要花费时间 (Ticks)：814</span><br></pre></td></tr></table></figure><p>可以看出 ThreadPool 创建线程消耗的时间是非常小的</p><h4 id="加入任务"><a href="#加入任务" class="headerlink" title="加入任务"></a>加入任务</h4><figure class="highlight cs"><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">public</span> <span class="keyword">static</span> <span class="built_in">bool</span> <span class="title">QueueUserWorkItem</span>(<span class="params">WaitCallback callBack</span>)</span>;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">bool</span> <span class="title">QueueUserWorkItem</span>(<span class="params">WaitCallback callBack, <span class="built_in">object</span>? state</span>)</span>;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">bool</span> <span class="title">QueueUserWorkItem</span>&lt;<span class="title">TState</span>&gt;(<span class="params">Action&lt;TState&gt; callBack, TState state, <span class="built_in">bool</span> preferLocal</span>)</span>;</span><br></pre></td></tr></table></figure><h4 id="线程池的限制"><a href="#线程池的限制" class="headerlink" title="线程池的限制"></a>线程池的限制</h4><ul><li>线程池中的线程均为后台线程</li><li>不能设置优先级和名称</li><li>更适合执行时间短的任务，不应该阻塞线程池中的线程</li><li>最大允许 2048 个工作线程</li></ul><h4 id="线程数量"><a href="#线程数量" class="headerlink" title="线程数量"></a>线程数量</h4><p>线程池有最大线程数量和最小线程数量</p><h5 id="最大线程数量"><a href="#最大线程数量" class="headerlink" title="最大线程数量"></a>最大线程数量</h5><p>如果线程的数量超出最大数量，会移除之前的线程</p><figure class="highlight cs"><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">public</span> <span class="keyword">static</span> <span class="built_in">bool</span> <span class="title">SetMaxThreads</span>(<span class="params"><span class="built_in">int</span> workerThreads, <span class="built_in">int</span> completionPortThreads</span>)</span>;</span><br></pre></td></tr></table></figure><ul><li>workerThreads 线程池中辅助线程的最大数目</li><li>completionPortThreads 线程池中异步 I&#x2F;O 线程的最大数目</li></ul><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">WriteCount</span>()</span></span><br><span class="line">&#123;</span><br><span class="line">    ThreadPool.GetMaxThreads(<span class="keyword">out</span> <span class="keyword">var</span> workerThreads, <span class="keyword">out</span> <span class="keyword">var</span> completionPortThreads);</span><br><span class="line">    Console.WriteLine(<span class="string">$&quot;线程池中辅助线程的最大数为：<span class="subst">&#123;workerThreads&#125;</span>；线程池中异步I/O线程的最大数为：<span class="subst">&#123;completionPortThreads&#125;</span>&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Console.Write(<span class="string">&quot;设置前，&quot;</span>);</span><br><span class="line">WriteCount();</span><br><span class="line"><span class="keyword">if</span> (ThreadPool.SetMaxThreads(<span class="number">30000</span>, <span class="number">500</span>))</span><br><span class="line">&#123;</span><br><span class="line">    ThreadPool.QueueUserWorkItem(<span class="keyword">new</span> WaitCallback((obj) =&gt;</span><br><span class="line">    &#123;</span><br><span class="line">        Console.WriteLine(<span class="string">&quot;执行线程池&quot;</span>);</span><br><span class="line">    &#125;));</span><br><span class="line"></span><br><span class="line">    Console.Write(<span class="string">&quot;设置后，&quot;</span>);</span><br><span class="line">    WriteCount();</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">&#123;</span><br><span class="line">    Console.WriteLine(<span class="string">&quot;没有设置&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Console.ReadLine();</span><br></pre></td></tr></table></figure><h5 id="最小线程数量"><a href="#最小线程数量" class="headerlink" title="最小线程数量"></a>最小线程数量</h5><p>线程池维持的最小的可用线程数，便于队列任务可以立即启动</p><figure class="highlight cs"><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">public</span> <span class="keyword">static</span> <span class="built_in">bool</span> <span class="title">SetMinThreads</span>(<span class="params"><span class="built_in">int</span> workerThreads, <span class="built_in">int</span> completionPortThreads</span>)</span>;</span><br></pre></td></tr></table></figure><ul><li>workerThreads 当前由线程池维护的空闲辅助线程的最小数目</li><li>completionPortThreads 当前由线程池维护的空闲异步 I&#x2F;O 线程的最小数目</li></ul><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">WriteCount</span>()</span></span><br><span class="line">&#123;</span><br><span class="line">    ThreadPool.GetMinThreads(<span class="keyword">out</span> <span class="keyword">var</span> minWorker, <span class="keyword">out</span> <span class="keyword">var</span> minCompletionPort);</span><br><span class="line">    Console.WriteLine(<span class="string">$&quot;线程池维护的空闲辅助线程的最小数目为：<span class="subst">&#123;minWorker&#125;</span>；线程池维护的空闲异步 I/O 线程的最小数目为：<span class="subst">&#123;minCompletionPort&#125;</span>&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Console.Write(<span class="string">&quot;设置前，&quot;</span>);</span><br><span class="line">WriteCount();</span><br><span class="line"><span class="keyword">if</span> (ThreadPool.SetMinThreads(<span class="number">10</span>, <span class="number">2</span>))</span><br><span class="line">&#123;</span><br><span class="line">    Console.Write(<span class="string">&quot;设置后，&quot;</span>);</span><br><span class="line">    WriteCount();</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">&#123;</span><br><span class="line">    Console.WriteLine(<span class="string">&quot;没有设置&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Console.ReadLine();</span><br></pre></td></tr></table></figure><h3 id="Parallel"><a href="#Parallel" class="headerlink" title="Parallel"></a>Parallel</h3><p><code>Parallel</code> 类位于 <code>System.Threading.Task</code> 命名空间中，命名空间与 <code>Task</code> 相同</p><p>Parallel 是对 Thread 和 ThreadPool 的封装和抽象</p><p>Parallel 提供下面几个函数</p><ul><li>For</li><li>ForAsync</li><li>ForEach</li><li>ForEachAsync</li><li>Invoke</li></ul><h4 id="Parallel-For"><a href="#Parallel-For" class="headerlink" title="Parallel.For"></a>Parallel.For</h4><p>类似于 for 循环，可以并行迭代</p><p>以最简单的重载函数为例</p><figure class="highlight cs"><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">public</span> <span class="keyword">static</span> ParallelLoopResult <span class="title">For</span>(<span class="params"><span class="built_in">int</span> fromInclusive, <span class="built_in">int</span> toExclusive, Action&lt;<span class="built_in">int</span>&gt; body</span>)</span>;</span><br></pre></td></tr></table></figure><p>前两个参数定义了循环的开始和结束</p><p>第三个参数是任务函数体，函数的参数是循环迭代的次数</p><p>还有一些重载版本，在重载版本中支持中断和线程初始化，后面会有介绍</p><h4 id="Parallel-ForEach"><a href="#Parallel-ForEach" class="headerlink" title="Parallel.ForEach"></a>Parallel.ForEach</h4><p>用异步的方式遍历 <code>IEnumerable</code> 集合，不确定遍历顺序</p><p>以最简单的重载函数为例</p><figure class="highlight cs"><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">public</span> <span class="keyword">static</span> ParallelLoopResult <span class="title">ForEach</span>&lt;<span class="title">TSource</span>&gt;(<span class="params">IEnumerable&lt;TSource&gt; source, Action&lt;TSource&gt; body</span>)</span>;</span><br></pre></td></tr></table></figure><p>第一个参数是 <code>IEnumerable&lt;T&gt;</code> 列表，第二个参数是一个 Action 委托，每个元素会执行一次委托</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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="built_in">string</span>[] data = [<span class="string">&quot;1&quot;</span>, <span class="string">&quot;2&quot;</span>, <span class="string">&quot;3&quot;</span>, <span class="string">&quot;4&quot;</span>, <span class="string">&quot;5&quot;</span>, <span class="string">&quot;6&quot;</span>, <span class="string">&quot;7&quot;</span>, <span class="string">&quot;8&quot;</span>, <span class="string">&quot;9&quot;</span>, <span class="string">&quot;10&quot;</span>, <span class="string">&quot;11&quot;</span>, <span class="string">&quot;12&quot;</span>, <span class="string">&quot;13&quot;</span>];</span><br><span class="line">Parallel.ForEach(data, (s) =&gt;</span><br><span class="line">&#123;</span><br><span class="line">    Console.WriteLine(s);</span><br><span class="line">&#125;);</span><br><span class="line">Console.ReadKey();</span><br></pre></td></tr></table></figure><p>还有一些重载版本，在重载版本中支持中断和线程初始化，后面会有介绍</p><h4 id="Parallel-Invoke"><a href="#Parallel-Invoke" class="headerlink" title="Parallel.Invoke"></a>Parallel.Invoke</h4><p>支持执行不同的方法，类似 <code>Task.WhenAll</code></p><figure class="highlight cs"><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">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">Invoke</span>(<span class="params"><span class="keyword">params</span> Action[] actions</span>)</span>;</span><br></pre></td></tr></table></figure><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">Foo</span>()</span></span><br><span class="line">&#123;</span><br><span class="line">    Console.WriteLine(<span class="string">&quot;Foo&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">Bar</span>()</span></span><br><span class="line">&#123;</span><br><span class="line">    Console.WriteLine(<span class="string">&quot;Bar&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Parallel.Invoke(Foo, Bar);</span><br><span class="line">Console.ReadKey();</span><br></pre></td></tr></table></figure><p>参数是一个 Action 委托数组</p><h4 id="异步版本"><a href="#异步版本" class="headerlink" title="异步版本"></a>异步版本</h4><ul><li>ForAsync</li><li>ForEachAsync</li></ul><p>用法类似，只是返回 <code>Task</code>，可以等待执行结束</p><h4 id="中断执行"><a href="#中断执行" class="headerlink" title="中断执行"></a>中断执行</h4><p>回调函数的重载版本，有的支持参数 <code>ParallelLoopState</code></p><p>该对象有函数 <code>Break()</code> 和 <code>Stop</code>，可以中断任务的执行</p><figure class="highlight cs"><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">public</span> <span class="keyword">static</span> ParallelLoopResult <span class="title">For</span>(<span class="params"><span class="built_in">int</span> fromInclusive, <span class="built_in">int</span> toExclusive, Action&lt;<span class="built_in">int</span>, ParallelLoopState&gt; body</span>)</span>;</span><br></pre></td></tr></table></figure><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><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">var</span> result = Parallel.For(<span class="number">0</span>, <span class="number">100</span>, (i, state) =&gt;</span><br><span class="line">&#123;</span><br><span class="line">    Console.WriteLine(<span class="string">$&quot;i:<span class="subst">&#123;i&#125;</span>, thread id: <span class="subst">&#123;Thread.CurrentThread.ManagedThreadId&#125;</span>&quot;</span>);</span><br><span class="line">    <span class="keyword">if</span> (i &gt; <span class="number">5</span>) state.Break();</span><br><span class="line">    Thread.Sleep(<span class="number">10</span>);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">Console.WriteLine(<span class="string">$&quot;IsCompleted: <span class="subst">&#123;result.IsCompleted&#125;</span>&quot;</span>);</span><br><span class="line">Console.WriteLine(<span class="string">$&quot;LowestBreakIteration: <span class="subst">&#123;result.LowestBreakIteration&#125;</span>&quot;</span>);</span><br><span class="line"></span><br><span class="line">Console.ReadKey();</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line">i:24, thread id: 12</span><br><span class="line">i:0, thread id: 1</span><br><span class="line">i:54, thread id: 17</span><br><span class="line">i:60, thread id: 18</span><br><span class="line">i:48, thread id: 16</span><br><span class="line">i:18, thread id: 11</span><br><span class="line">i:30, thread id: 13</span><br><span class="line">i:72, thread id: 20</span><br><span class="line">i:42, thread id: 15</span><br><span class="line">i:36, thread id: 14</span><br><span class="line">i:6, thread id: 5</span><br><span class="line">i:12, thread id: 8</span><br><span class="line">i:66, thread id: 19</span><br><span class="line">i:78, thread id: 21</span><br><span class="line">i:84, thread id: 22</span><br><span class="line">i:96, thread id: 24</span><br><span class="line">i:90, thread id: 23</span><br><span class="line">i:3, thread id: 11</span><br><span class="line">i:1, thread id: 1</span><br><span class="line">i:4, thread id: 11</span><br><span class="line">i:2, thread id: 1</span><br><span class="line">i:5, thread id: 11</span><br><span class="line">IsCompleted: False</span><br><span class="line">LowestBreakIteration: 6</span><br></pre></td></tr></table></figure><h4 id="线程初始化"><a href="#线程初始化" class="headerlink" title="线程初始化"></a>线程初始化</h4><p>Parallel 下的模板函数重载版本，可以对每个线程进行初始化，如</p><figure class="highlight cs"><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">public</span> <span class="keyword">static</span> ParallelLoopResult <span class="title">For</span>&lt;<span class="title">TLocal</span>&gt;(<span class="params"><span class="built_in">int</span> fromInclusive, <span class="built_in">int</span> toExclusive, Func&lt;TLocal&gt; localInit, Func&lt;<span class="built_in">int</span>, ParallelLoopState, TLocal, TLocal&gt; body, Action&lt;TLocal&gt; localFinally</span>)</span>;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> ParallelLoopResult <span class="title">ForEach</span>&lt;<span class="title">TSource</span>, <span class="title">TLocal</span>&gt;(<span class="params">IEnumerable&lt;TSource&gt; source, Func&lt;TLocal&gt; localInit, Func&lt;TSource, ParallelLoopState, TLocal, TLocal&gt; body, Action&lt;TLocal&gt; localFinally</span>)</span></span><br></pre></td></tr></table></figure><ul><li><p>localInit <code>localInit</code> 参数是一个 <code>Func</code> 委托，该委托会返回一个模板类型的值，回调的函数体可以通过参数获取到这个值。每个线程都会执行一次 <code>localInit</code> 委托，因此 <code>localInit</code> 可能会执行多次，也可能是 1 次</p></li><li><p>localFinally <code>localFinally</code> 参数是一个 <code>Action</code> 委托，该委托会在每个线程完成后执行</p></li></ul><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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">Parallel.For(</span><br><span class="line">    <span class="number">0</span>,</span><br><span class="line">    <span class="number">10</span>,</span><br><span class="line">    () =&gt;</span><br><span class="line">    &#123;</span><br><span class="line">        Console.WriteLine(<span class="string">$&quot;init thread <span class="subst">&#123;Environment.CurrentManagedThreadId&#125;</span>,\t task <span class="subst">&#123;Task.CurrentId&#125;</span>&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="string">$&quot;t<span class="subst">&#123;Environment.CurrentManagedThreadId&#125;</span>&quot;</span>;</span><br><span class="line">    &#125;,</span><br><span class="line">    (i, pls, str) =&gt;</span><br><span class="line">    &#123;</span><br><span class="line">        Console.WriteLine(<span class="string">&quot;body i &#123;0&#125; \t str &#123;1&#125; \t thread &#123;2&#125; \t task &#123;3&#125;&quot;</span>, i, str, Environment.CurrentManagedThreadId, Task.CurrentId);</span><br><span class="line">        Thread.Sleep(<span class="number">10</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="string">$&quot;i \t<span class="subst">&#123;i&#125;</span>&quot;</span>;</span><br><span class="line">    &#125;,</span><br><span class="line">    (str) =&gt;</span><br><span class="line">    &#123;</span><br><span class="line">        Console.WriteLine(<span class="string">$&quot;finally\t <span class="subst">&#123;str&#125;</span>&quot;</span>);</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">Console.ReadKey();</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><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">init thread 17,  task 15</span><br><span class="line">init thread 16,  task 14</span><br><span class="line">init thread 8,   task 8</span><br><span class="line">init thread 15,  task 13</span><br><span class="line">init thread 13,  task 11</span><br><span class="line">init thread 1,   task 6</span><br><span class="line">init thread 5,   task 7</span><br><span class="line">body i 1         str t5          thread 5        task 7</span><br><span class="line">init thread 14,  task 12</span><br><span class="line">body i 6         str t14         thread 14       task 12</span><br><span class="line">body i 9         str t17         thread 17       task 15</span><br><span class="line">body i 8         str t16         thread 16       task 14</span><br><span class="line">init thread 12,  task 10</span><br><span class="line">body i 4         str t12         thread 12       task 10</span><br><span class="line">body i 7         str t15         thread 15       task 13</span><br><span class="line">body i 5         str t13         thread 13       task 11</span><br><span class="line">body i 0         str t1          thread 1        task 6</span><br><span class="line">init thread 11,  task 9</span><br><span class="line">body i 3         str t11         thread 11       task 9</span><br><span class="line">body i 2         str t8          thread 8        task 8</span><br><span class="line">finally  i      5</span><br><span class="line">finally  i      6</span><br><span class="line">finally  i      0</span><br><span class="line">finally  i      4</span><br><span class="line">finally  i      7</span><br><span class="line">finally  i      3</span><br><span class="line">finally  i      8</span><br><span class="line">finally  i      9</span><br><span class="line">finally  i      2</span><br><span class="line">finally  i      1</span><br></pre></td></tr></table></figure><h3 id="Task"><a href="#Task" class="headerlink" title="Task"></a>Task</h3><p><code>Task</code> 提供的多线程更加灵活</p><ul><li>支持并行任务</li><li>支持连续任务，即上一个任务结束后再决定是否执行下一个任务</li><li>支持任务嵌套，一个任务中可以再开启多个任务</li><li>可以获取任务的返回值</li><li>任务基于线程，最终执行是需要线程来执行的</li><li>任务和线程不是一对一关系，一个线程可能有多个任务</li><li>相比于线程，任务的开销更小，控制更精确</li></ul><h4 id="启动任务"><a href="#启动任务" class="headerlink" title="启动任务"></a>启动任务</h4><p>有多种方式启动一个任务</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">_ = Task.Run(() =&gt;</span><br><span class="line">&#123;</span><br><span class="line">    Console.WriteLine(<span class="string">&quot;Foo&quot;</span>);</span><br><span class="line">&#125;);</span><br><span class="line">Console.ReadKey();</span><br></pre></td></tr></table></figure><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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> task = <span class="keyword">new</span> Task(() =&gt;</span><br><span class="line">&#123;</span><br><span class="line">    Console.WriteLine(<span class="string">&quot;Foo&quot;</span>);</span><br><span class="line">&#125;);</span><br><span class="line">task.Start();</span><br><span class="line">Console.ReadKey();</span><br></pre></td></tr></table></figure><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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> tf = <span class="keyword">new</span> TaskFactory();</span><br><span class="line"><span class="keyword">var</span> task = tf.StartNew(() =&gt;</span><br><span class="line">&#123;</span><br><span class="line">    Console.WriteLine(<span class="string">&quot;Foo&quot;</span>);</span><br><span class="line">&#125;);</span><br><span class="line">Console.ReadKey();</span><br></pre></td></tr></table></figure><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">_ = Task.Factory.StartNew(() =&gt;</span><br><span class="line">&#123;</span><br><span class="line">    Console.WriteLine(<span class="string">&quot;Foo&quot;</span>);</span><br><span class="line">&#125;);</span><br><span class="line">Console.ReadKey();</span><br></pre></td></tr></table></figure><p>上述方法也都支持给任务传参</p><h4 id="任务的层次"><a href="#任务的层次" class="headerlink" title="任务的层次"></a>任务的层次</h4><p>在任务的内部，也可以创建多个子任务</p><p>父任务被取消时，子任务也会被取消</p><p>如果在任务内想创建父级任务，在创建任务时，为 <code>TaskCreationOptions</code> 赋值为 <code>TaskCreationOptions.DenyChildAttach</code></p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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> task = <span class="keyword">new</span> Task(() =&gt;</span><br><span class="line">&#123;</span><br><span class="line">    Console.WriteLine(<span class="string">&quot;Foo&quot;</span>);</span><br><span class="line">&#125;, TaskCreationOptions.DenyChildAttach);</span><br><span class="line">task.Start();</span><br><span class="line">Console.ReadKey();</span><br></pre></td></tr></table></figure><h4 id="长任务"><a href="#长任务" class="headerlink" title="长任务"></a>长任务</h4><p>默认 Task 更适合短时间运行的任务，如果要创建长时间运行的任务，应该使用 <code>TaskCreationOptions.LongRunning</code></p><p>这样就不再使用线程池中的线程，会告诉任务管理器创建一个新的线程</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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> task = <span class="keyword">new</span> Task(() =&gt;</span><br><span class="line">&#123;</span><br><span class="line">    Console.WriteLine(<span class="string">&quot;Foo&quot;</span>);</span><br><span class="line">&#125;, TaskCreationOptions.LongRunning);</span><br><span class="line">task.Start();</span><br><span class="line">Console.ReadKey();</span><br></pre></td></tr></table></figure><h4 id="任务控制"><a href="#任务控制" class="headerlink" title="任务控制"></a>任务控制</h4><h5 id="task-Wait"><a href="#task-Wait" class="headerlink" title="task.Wait"></a>task.Wait</h5><p>调用函数 <code>Task.Wait</code> 可以等待任务执行完毕，即 <code>Task</code> 的状态变为 <code>Completed</code></p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Wait</span>()</span>;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Wait</span>(<span class="params">CancellationToken cancellationToken</span>)</span>;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="built_in">bool</span> <span class="title">Wait</span>(<span class="params"><span class="built_in">int</span> millisecondsTimeout</span>)</span>;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="built_in">bool</span> <span class="title">Wait</span>(<span class="params"><span class="built_in">int</span> millisecondsTimeout, CancellationToken cancellationToken</span>)</span>;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="built_in">bool</span> <span class="title">Wait</span>(<span class="params">TimeSpan timeout</span>)</span>;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="built_in">bool</span> <span class="title">Wait</span>(<span class="params">TimeSpan timeout, CancellationToken cancellationToken</span>)</span>;</span><br></pre></td></tr></table></figure><h5 id="Task-WaitAll"><a href="#Task-WaitAll" class="headerlink" title="Task.WaitAll"></a>Task.WaitAll</h5><p>等待所有任务执行完毕，参数接收多个 Task 对象</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">WaitAll</span>(<span class="params"><span class="keyword">params</span> Task[] tasks</span>)</span>;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">bool</span> <span class="title">WaitAll</span>(<span class="params">Task[] tasks, <span class="built_in">int</span> millisecondsTimeout, CancellationToken cancellationToken</span>)</span>;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">WaitAll</span>(<span class="params">Task[] tasks, CancellationToken cancellationToken</span>)</span>;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">bool</span> <span class="title">WaitAll</span>(<span class="params">Task[] tasks, TimeSpan timeout</span>)</span>;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">bool</span> <span class="title">WaitAll</span>(<span class="params">Task[] tasks, <span class="built_in">int</span> millisecondsTimeout</span>)</span>;</span><br></pre></td></tr></table></figure><h5 id="Task-WatiAny"><a href="#Task-WatiAny" class="headerlink" title="Task.WatiAny"></a>Task.WatiAny</h5><p>类似 <code>Task.WaitAll</code>，但只需要任一任务完成，就不再等待</p><h5 id="task-ContinueWith"><a href="#task-ContinueWith" class="headerlink" title="task.ContinueWith"></a>task.ContinueWith</h5><p>任务完成后自动执行下一个 Task，实现链式执行</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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">Task task1 = Task.Run(() =&gt;</span><br><span class="line">&#123;</span><br><span class="line">    Console.WriteLine(<span class="string">&quot;Current Task id = &#123;0&#125;&quot;</span>, Task.CurrentId);</span><br><span class="line">    Console.WriteLine(<span class="string">&quot;执行任务1\r\n&quot;</span>);</span><br><span class="line">    Thread.Sleep(<span class="number">10</span>);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">Task task2 = task1.ContinueWith((t) =&gt;</span><br><span class="line">&#123;</span><br><span class="line">    Console.WriteLine(<span class="string">&quot;Last Task id = &#123;0&#125;&quot;</span>, t.Id);</span><br><span class="line">    Console.WriteLine(<span class="string">&quot;Current Task id = &#123;0&#125;&quot;</span>, Task.CurrentId);</span><br><span class="line">    Console.WriteLine(<span class="string">&quot;执行任务2\r\n&quot;</span>);</span><br><span class="line">    Thread.Sleep(<span class="number">10</span>);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">Task task3 = task2.ContinueWith((t) =&gt;</span><br><span class="line">&#123;</span><br><span class="line">    Console.WriteLine(<span class="string">&quot;Last Task id = &#123;0&#125;&quot;</span>, t.Id);</span><br><span class="line">    Console.WriteLine(<span class="string">&quot;Current Task id = &#123;0&#125;&quot;</span>, Task.CurrentId);</span><br><span class="line">    Console.WriteLine(<span class="string">&quot;执行任务3\r\n&quot;</span>);</span><br><span class="line">&#125;, TaskContinuationOptions.OnlyOnRanToCompletion);</span><br><span class="line"></span><br><span class="line">Console.ReadKey();</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></pre></td><td class="code"><pre><span class="line">Current Task id = 9</span><br><span class="line">执行任务1</span><br><span class="line"></span><br><span class="line">Last Task id = 9</span><br><span class="line">Current Task id = 10</span><br><span class="line">执行任务2</span><br><span class="line"></span><br><span class="line">Last Task id = 10</span><br><span class="line">Current Task id = 11</span><br><span class="line">执行任务3</span><br></pre></td></tr></table></figure><h5 id="task-RunSynchronously"><a href="#task-RunSynchronously" class="headerlink" title="task.RunSynchronously"></a>task.RunSynchronously</h5><p>在当前线程上执行任务</p><p>如果当前线程是 UI 线程，这个操作会造成界面卡顿</p><h4 id="任务取消"><a href="#任务取消" class="headerlink" title="任务取消"></a>任务取消</h4><p>启动任务时传入 Token，在任务中的各个适当位置判断 Tokan 状态并退出任务</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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">var</span> tokenSource = <span class="keyword">new</span> CancellationTokenSource();</span><br><span class="line"><span class="keyword">var</span> token = tokenSource.Token;</span><br><span class="line"><span class="keyword">var</span> task = Task.Run(() =&gt;</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; <span class="number">1000</span>; i++)</span><br><span class="line">    &#123;</span><br><span class="line">        Console.WriteLine(i);</span><br><span class="line">        System.Threading.Thread.Sleep(<span class="number">1000</span>);</span><br><span class="line">        <span class="keyword">if</span> (token.IsCancellationRequested)</span><br><span class="line">        &#123;</span><br><span class="line">            Console.WriteLine(<span class="string">&quot;Abort mission success!&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;, token);</span><br><span class="line">token.Register(() =&gt;</span><br><span class="line">&#123;</span><br><span class="line">    Console.WriteLine(<span class="string">&quot;Canceled&quot;</span>);</span><br><span class="line">&#125;);</span><br><span class="line">Console.WriteLine(<span class="string">&quot;Press enter to cancel task...&quot;</span>);</span><br><span class="line">Console.ReadKey();</span><br><span class="line">tokenSource.Cancel();</span><br><span class="line">Console.ReadKey();</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></pre></td><td class="code"><pre><span class="line">Press enter to cancel task...</span><br><span class="line">0</span><br><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">Canceled</span><br><span class="line">Abort mission success!</span><br></pre></td></tr></table></figure><h3 id="BackgroundWorker"><a href="#BackgroundWorker" class="headerlink" title="BackgroundWorker"></a>BackgroundWorker</h3><p><code>BackgroundWorker</code> 实现多线程运算更安全、更简单</p><ul><li>提供几种事件，可以在任务的各个阶段触发</li><li>提供了 <code>CancleAsync</code> 方法，方便取消任务</li><li>能后台异步操作的同时，也能通知 UI 线程进度</li></ul><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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">using</span> System.ComponentModel;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> worker = <span class="keyword">new</span> BackgroundWorker()</span><br><span class="line">&#123;</span><br><span class="line">    WorkerSupportsCancellation = <span class="literal">true</span>,</span><br><span class="line">    WorkerReportsProgress = <span class="literal">true</span>,</span><br><span class="line">&#125;;</span><br><span class="line">worker.DoWork += Worker_DoWork;</span><br><span class="line">worker.ProgressChanged += Worker_ProgressChanged;</span><br><span class="line">worker.RunWorkerCompleted += Worker_RunWorkerCompleted;</span><br><span class="line">worker.RunWorkerAsync();</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Worker_RunWorkerCompleted</span>(<span class="params"><span class="built_in">object</span>? sender, RunWorkerCompletedEventArgs e</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    Console.WriteLine(<span class="string">$&quot;RunWorkerCompleted, Cancelled: <span class="subst">&#123;e.Cancelled&#125;</span>&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Worker_ProgressChanged</span>(<span class="params"><span class="built_in">object</span>? sender, ProgressChangedEventArgs e</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    Console.WriteLine(<span class="string">$&quot;ProgressChanged, ProgressPercentage:<span class="subst">&#123;e.ProgressPercentage&#125;</span>&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Worker_DoWork</span>(<span class="params"><span class="built_in">object</span>? sender, DoWorkEventArgs e</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">var</span> times = <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span> (!worker.CancellationPending)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (times &gt;= <span class="number">6</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            worker.CancelAsync();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        worker.ReportProgress(times);</span><br><span class="line">        Console.WriteLine(<span class="string">$&quot;DoWork <span class="subst">&#123;times++&#125;</span>&quot;</span>);</span><br><span class="line">        Thread.Sleep(<span class="number">100</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Console.ReadKey();</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></pre></td><td class="code"><pre><span class="line">DoWork 1</span><br><span class="line">ProgressChanged, ProgressPercentage:1</span><br><span class="line">DoWork 2</span><br><span class="line">ProgressChanged, ProgressPercentage:2</span><br><span class="line">DoWork 3</span><br><span class="line">ProgressChanged, ProgressPercentage:3</span><br><span class="line">DoWork 4</span><br><span class="line">ProgressChanged, ProgressPercentage:4</span><br><span class="line">DoWork 5</span><br><span class="line">ProgressChanged, ProgressPercentage:5</span><br><span class="line">DoWork 6</span><br><span class="line">ProgressChanged, ProgressPercentage:6</span><br><span class="line">RunWorkerCompleted, Cancelled: False</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><ul><li>Mutex 互斥锁</li><li>SpinLock 自旋锁</li><li>Monitor</li><li>lock</li></ul><h4 id="Mutex"><a href="#Mutex" class="headerlink" title="Mutex"></a>Mutex</h4><p>互斥锁，可以用于共享资源每次只能被一个线程访问的情况</p><p>Mutex 消耗的资源较大，不适合频繁操作</p><p>Mutex 可以跨进程，是系统级的。比如可以用 Mutex 实现一个程序不能多次打开</p><p>如果锁已被其他线程获取，第二次获取的线程将挂起，直到第一个线程释放互斥锁</p><ul><li>WaitOne() 阻止线程，直至收到信号，参数可以指定超时时间</li><li>ReleaseMutex() 释放一次锁（其他线程即获取锁）</li><li>OpenExisting() 获取指定命名的互斥锁</li></ul><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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">var</span> mutex = <span class="keyword">new</span> Mutex();</span><br><span class="line"><span class="built_in">bool</span> executed = <span class="literal">false</span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Execute</span>(<span class="params"><span class="built_in">int</span> num</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span> (mutex.WaitOne())</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">try</span></span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">if</span> (!executed)</span><br><span class="line">            &#123;</span><br><span class="line">                Console.WriteLine(<span class="string">$&quot;<span class="subst">&#123;num&#125;</span>：已执行&quot;</span>);</span><br><span class="line">                executed = <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">            &#123;</span><br><span class="line">                Console.WriteLine(<span class="string">$&quot;<span class="subst">&#123;num&#125;</span>：跳过执行&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">finally</span></span><br><span class="line">        &#123;</span><br><span class="line">            mutex.ReleaseMutex();</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">for</span> (<span class="built_in">int</span> i = <span class="number">1</span>; i &lt; <span class="number">10</span>; i++)</span><br><span class="line">&#123;</span><br><span class="line">    _ = Task.Factory.StartNew(obj =&gt; &#123; Execute((<span class="built_in">int</span>)obj!); &#125;, i);</span><br><span class="line">&#125;</span><br><span class="line">Console.ReadLine();</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></pre></td><td class="code"><pre><span class="line">2：已执行</span><br><span class="line">3：跳过执行</span><br><span class="line">4：跳过执行</span><br><span class="line">1：跳过执行</span><br><span class="line">5：跳过执行</span><br><span class="line">7：跳过执行</span><br><span class="line">8：跳过执行</span><br><span class="line">9：跳过执行</span><br><span class="line">6：跳过执行</span><br></pre></td></tr></table></figure><h4 id="SpinLock"><a href="#SpinLock" class="headerlink" title="SpinLock"></a>SpinLock</h4><p>当一个线程获取锁对象时，如果锁已经被其他线程获取，那么这个线程就会循环等待（自旋），并且不断的获取锁，直至获取到锁</p><p>因此自旋锁不会让线程处于等待状态，有助于避免阻塞</p><p>一般用于短时间锁定的场景，如果有大量阻塞，旋转过多会影响性能</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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">var</span> spinLock = <span class="keyword">new</span> SpinLock();</span><br><span class="line"><span class="built_in">bool</span> executed = <span class="literal">false</span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Execute</span>(<span class="params"><span class="built_in">int</span> num</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="built_in">bool</span> lockTaken = <span class="literal">false</span>;</span><br><span class="line">    <span class="keyword">try</span></span><br><span class="line">    &#123;</span><br><span class="line">        spinLock.Enter(<span class="keyword">ref</span> lockTaken);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (!executed)</span><br><span class="line">        &#123;</span><br><span class="line">            Console.WriteLine(<span class="string">$&quot;<span class="subst">&#123;num&#125;</span>：已执行&quot;</span>);</span><br><span class="line">            executed = <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">        &#123;</span><br><span class="line">            Console.WriteLine(<span class="string">$&quot;<span class="subst">&#123;num&#125;</span>：跳过执行&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">finally</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (lockTaken)</span><br><span class="line">        &#123;</span><br><span class="line">            spinLock.Exit();</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">for</span> (<span class="built_in">int</span> i = <span class="number">1</span>; i &lt; <span class="number">10</span>; i++)</span><br><span class="line">&#123;</span><br><span class="line">    _ = Task.Factory.StartNew(obj =&gt; &#123; Execute((<span class="built_in">int</span>)obj!); &#125;, i);</span><br><span class="line">&#125;</span><br><span class="line">Console.ReadLine();</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></pre></td><td class="code"><pre><span class="line">2：已执行</span><br><span class="line">3：跳过执行</span><br><span class="line">1：跳过执行</span><br><span class="line">4：跳过执行</span><br><span class="line">7：跳过执行</span><br><span class="line">5：跳过执行</span><br><span class="line">6：跳过执行</span><br><span class="line">8：跳过执行</span><br><span class="line">9：跳过执行</span><br></pre></td></tr></table></figure><h4 id="Monitor"><a href="#Monitor" class="headerlink" title="Monitor"></a>Monitor</h4><p>Monitor 是一种轻量级的互斥锁，能确保同一时刻只有一个线程执行代码块</p><p>Monitor.Enter 方法锁定对象<br>Monitor.Exit 方法释放对象<br>Monitor.TryEnter 可以指定获取对象锁的最长时间，能避免出现死锁</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><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> obj = <span class="keyword">new</span> <span class="built_in">object</span>();</span><br><span class="line">Monitor.Enter(obj);</span><br><span class="line"><span class="keyword">try</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">// TODO</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">finally</span></span><br><span class="line">&#123;</span><br><span class="line">    Monitor.Exit(obj);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="lock"><a href="#lock" class="headerlink" title="lock"></a>lock</h4><p>lock 的本质就是 Monitor 实现的，因此在 lock 块中可以使用 Monitor 的所有方法</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><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> obj = <span class="keyword">new</span> <span class="built_in">object</span>();</span><br><span class="line"><span class="keyword">lock</span>(obj)&#123;</span><br><span class="line">  <span class="comment">// TODO</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>等同于</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><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> obj = <span class="keyword">new</span> <span class="built_in">object</span>();</span><br><span class="line">Monitor.Enter(obj);</span><br><span class="line"><span class="keyword">try</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">// TODO</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">finally</span></span><br><span class="line">&#123;</span><br><span class="line">    Monitor.Exit(obj);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="事件"><a href="#事件" class="headerlink" title="事件"></a>事件</h3><p>有两种可以实现线程同步的事件</p><ul><li>AutoResetEvent</li><li>ManualResetEvent</li></ul><p>可以控制是否阻塞线程</p><ul><li>WaitOne() 阻塞当前线程，直到收到释放信号，可以传参指定超时时长，避免死锁</li><li>Set() 释放阻塞</li><li>Reset() 将状态改为非终止状态，即阻塞所有的 WaitOne</li></ul><h4 id="AutoResetEvent"><a href="#AutoResetEvent" class="headerlink" title="AutoResetEvent"></a>AutoResetEvent</h4><p>AutoResetEvent 收到 Set 只会有一个线程的 WaitOne 被处理，其他线程继续等待</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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">var</span> resetEvent = <span class="keyword">new</span> AutoResetEvent(<span class="literal">false</span>);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Execute</span>(<span class="params"><span class="built_in">int</span> num</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    Console.WriteLine(<span class="string">$&quot;<span class="subst">&#123;num&#125;</span> 等待&quot;</span>);</span><br><span class="line">    resetEvent.WaitOne();</span><br><span class="line"></span><br><span class="line">    Console.WriteLine(<span class="string">$&quot;<span class="subst">&#123;num&#125;</span> 执行&quot;</span>);</span><br><span class="line"></span><br><span class="line">    Thread.Sleep(<span class="number">1000</span>);</span><br><span class="line">    Console.WriteLine(<span class="string">$&quot;<span class="subst">&#123;num&#125;</span> 结束&quot;</span>);</span><br><span class="line">    resetEvent.Set();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">1</span>; i &lt; <span class="number">5</span>; i++)</span><br><span class="line">&#123;</span><br><span class="line">    _ = Task.Factory.StartNew(obj =&gt; &#123; Execute((<span class="built_in">int</span>)obj!); &#125;, i);</span><br><span class="line">    Thread.Sleep(<span class="number">50</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resetEvent.Set();</span><br><span class="line">Thread.Sleep(<span class="number">7000</span>);</span><br><span class="line">resetEvent.WaitOne();</span><br><span class="line">_ = Task.Factory.StartNew(obj =&gt; &#123; Execute((<span class="built_in">int</span>)obj!); &#125;, <span class="number">10</span>);</span><br><span class="line"></span><br><span class="line">Thread.Sleep(<span class="number">1000</span>);</span><br><span class="line">resetEvent.Set();</span><br><span class="line">resetEvent.WaitOne();</span><br><span class="line">Console.WriteLine(<span class="string">$&quot;结束&quot;</span>);</span><br><span class="line"></span><br><span class="line">Console.ReadLine();</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></pre></td><td class="code"><pre><span class="line">1 等待</span><br><span class="line">2 等待</span><br><span class="line">3 等待</span><br><span class="line">4 等待</span><br><span class="line">2 执行</span><br><span class="line">2 结束</span><br><span class="line">4 执行</span><br><span class="line">4 结束</span><br><span class="line">3 执行</span><br><span class="line">3 结束</span><br><span class="line">1 执行</span><br><span class="line">1 结束</span><br><span class="line">10 等待</span><br><span class="line">10 执行</span><br><span class="line">10 结束</span><br><span class="line">结束</span><br></pre></td></tr></table></figure><h4 id="ManualResetEvent"><a href="#ManualResetEvent" class="headerlink" title="ManualResetEvent"></a>ManualResetEvent</h4><p>ManualResetEvent 收到 Set 会处理所有线程的 WaitOne，而且新进入的 WaitOne 也不再等待</p><p>除非调用了 <code>Reset</code> 重置信号，才会将 Set 产生的影响消除</p><p>如果将前面示例代码的 AutoResetEvent 类换成 ManualResetEvent</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><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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">1 等待</span><br><span class="line">2 等待</span><br><span class="line">3 等待</span><br><span class="line">4 等待</span><br><span class="line">4 执行</span><br><span class="line">3 执行</span><br><span class="line">2 执行</span><br><span class="line">1 执行</span><br><span class="line">3 结束</span><br><span class="line">2 结束</span><br><span class="line">1 结束</span><br><span class="line">4 结束</span><br><span class="line">10 等待</span><br><span class="line">10 执行</span><br><span class="line">10 结束</span><br><span class="line">结束</span><br></pre></td></tr></table></figure><h3 id="信号量"><a href="#信号量" class="headerlink" title="信号量"></a>信号量</h3><ul><li>Semaphore 可以是系统范围信号量，也可以是本地信号量，限制可同时访问某一资源或资源池的线程数</li><li>SemaphoreSlim 更轻量、速度更快，用于单个进程内的等待，是 Semaphore 的轻量替代</li></ul><p>构造函数的参数 <code>initialCount</code> 表示同时允许多少个 Wait() 不等待</p><ul><li>Release() 退出信号量</li><li>WaitOne() 阻塞线程，直到收到信号</li></ul><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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">var</span> semaphore = <span class="keyword">new</span> SemaphoreSlim(<span class="number">3</span>);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Execute</span>(<span class="params"><span class="built_in">int</span> num</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    Console.WriteLine(<span class="string">$&quot;<span class="subst">&#123;num&#125;</span>等待&quot;</span>);</span><br><span class="line">    semaphore.Wait();</span><br><span class="line">    Console.WriteLine(<span class="string">$&quot;<span class="subst">&#123;num&#125;</span>执行&quot;</span>);</span><br><span class="line">    Thread.Sleep(<span class="number">100</span>);</span><br><span class="line">    Console.WriteLine(<span class="string">$&quot;<span class="subst">&#123;num&#125;</span>结束&quot;</span>);</span><br><span class="line">    semaphore.Release();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">1</span>; i &lt; <span class="number">6</span>; i++)</span><br><span class="line">&#123;</span><br><span class="line">    _ = Task.Factory.StartNew(obj =&gt; &#123; Execute((<span class="built_in">int</span>)obj!); &#125;, i);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Console.ReadLine();</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></pre></td><td class="code"><pre><span class="line">4等待</span><br><span class="line">1等待</span><br><span class="line">3等待</span><br><span class="line">2等待</span><br><span class="line">5等待</span><br><span class="line">4执行</span><br><span class="line">2执行</span><br><span class="line">5执行</span><br><span class="line">5结束</span><br><span class="line">4结束</span><br><span class="line">2结束</span><br><span class="line">1执行</span><br><span class="line">3执行</span><br><span class="line">1结束</span><br><span class="line">3结束</span><br></pre></td></tr></table></figure><h2 id="定时器"><a href="#定时器" class="headerlink" title="定时器"></a>定时器</h2><p>在 C# 中有多种定时器，常见通用的两种为</p><ul><li>System.Threading.Timer 线程计时器</li><li>System.Timers.Timer 服务器计时器</li></ul><p>还有和框架相关的，如</p><table><thead><tr><th>框架</th><th>类和命名空间</th></tr></thead><tbody><tr><td>WPF</td><td>System.Windows.Threading.DispatcherTimer</td></tr><tr><td>WinForm</td><td>System.Windows.Forms.Timer</td></tr><tr><td>ASP.NET</td><td>System.Web.UI.Timer</td></tr></tbody></table><h3 id="框架相关的定时器"><a href="#框架相关的定时器" class="headerlink" title="框架相关的定时器"></a>框架相关的定时器</h3><p>都是单线程的，并且在 UI 线程中执行，因此可以操作 UI 控件</p><p>一般仅做一些简单的和 UI 交互的定时操作，耗时操作会导致 UI 卡顿</p><h3 id="System-Threading-Timer"><a href="#System-Threading-Timer" class="headerlink" title="System.Threading.Timer"></a>System.Threading.Timer</h3><p>线程计时器是轻量计时器，基于线程池实现，工作在后台线程</p><p>如果前一个执行还未结束，会新开个线程执行新任务</p><p>不是线程安全的</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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">new</span> System.Threading.Timer((obj) =&gt;</span><br><span class="line">&#123;</span><br><span class="line">    Console.WriteLine(<span class="string">$&quot;当前线程：<span class="subst">&#123;Environment.CurrentManagedThreadId&#125;</span>&quot;</span>);</span><br><span class="line">    Thread.Sleep(<span class="number">20</span>);</span><br><span class="line">&#125;, <span class="literal">null</span>, <span class="number">10</span>, <span class="number">10</span>);</span><br><span class="line">Console.ReadLine();</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></pre></td><td class="code"><pre><span class="line">当前线程：11</span><br><span class="line">当前线程：5</span><br><span class="line">当前线程：10</span><br><span class="line">当前线程：11</span><br><span class="line">当前线程：10</span><br><span class="line">当前线程：5</span><br><span class="line">当前线程：10</span><br><span class="line">当前线程：5</span><br><span class="line">当前线程：10</span><br><span class="line">...</span><br></pre></td></tr></table></figure><h3 id="System-Timers-Timer"><a href="#System-Timers-Timer" class="headerlink" title="System.Timers.Timer"></a>System.Timers.Timer</h3><p>服务器计时器是针对服务器的服务程序，不过一般程序都可以使用</p><p>对多线程环境下有更多优化</p><p>如果前一个执行还未结束，与 <code>System.Threading.Timer</code> 相同，会新开个线程执行新任务</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><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> timer = <span class="keyword">new</span> System.Timers.Timer(<span class="number">10</span>);</span><br><span class="line">timer.Elapsed += Timer_Elapsed;</span><br><span class="line">timer.Start();</span><br><span class="line">Console.ReadLine();</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">Timer_Elapsed</span>(<span class="params"><span class="built_in">object</span>? sender, System.Timers.ElapsedEventArgs e</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    Console.WriteLine(<span class="string">$&quot;当前线程：<span class="subst">&#123;Environment.CurrentManagedThreadId&#125;</span>&quot;</span>);</span><br><span class="line">    Thread.Sleep(<span class="number">20</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><span class="line">6</span><br><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">当前线程：5</span><br><span class="line">当前线程：11</span><br><span class="line">当前线程：10</span><br><span class="line">当前线程：11</span><br><span class="line">当前线程：5</span><br><span class="line">当前线程：11</span><br><span class="line">当前线程：5</span><br><span class="line">当前线程：11</span><br><span class="line">当前线程：5</span><br><span class="line">...</span><br></pre></td></tr></table></figure><h4 id="AutoReset"><a href="#AutoReset" class="headerlink" title="AutoReset"></a>AutoReset</h4><p><code>AutoReset</code> 属性默认为 false，如果设为 true，那么执行一次 <code>Elapsed</code> 后就不再执行了</p><h4 id="SynchronizingObject"><a href="#SynchronizingObject" class="headerlink" title="SynchronizingObject"></a>SynchronizingObject</h4><p><code>SynchronizingObject</code> 属性可以指定同步上下文</p><p>比如指定为 UI 控件，则回调会在 UI 线程中执行</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;内容概要&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;前台线程&amp;#x2F;后台线程&lt;/li&gt;
&lt;li&gt;线程优先级&lt;/li&gt;
&lt;li&gt;实现多线程的方法 Thread &amp;#x2F; ThreadPool &amp;#x2F; Parallel &amp;#x2F; Task &amp;#x2F; BackgroundWorker&lt;/li&gt;
&lt;li&gt;线程同步，线程锁&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="记录" scheme="https://blog.hal.wang/categories/%E8%AE%B0%E5%BD%95/"/>
    
    
    <category term="C#" scheme="https://blog.hal.wang/tags/C/"/>
    
  </entry>
  
  <entry>
    <title>Blazor 集成 UnoCSS</title>
    <link href="https://blog.hal.wang/c4cce22d/"/>
    <id>https://blog.hal.wang/c4cce22d/</id>
    <published>2024-03-17T13:41:00.000Z</published>
    <updated>2026-03-18T15:50:10.582Z</updated>
    
    <content type="html"><![CDATA[<p>在原子化 css 中，最热门的当属 <code>tailwindcss</code>，而且在 <code>Blazor</code> 中集成 <code>tailwindcss</code> 的教程也很多</p><p>本文使用一个更强大的 <code>UnoCSS</code>，目前 <code>Blazor</code> 集成 <code>UnoCSS</code> 的教程却一个也找不到</p><p>这里记录一下如何在 <code>Blazor</code> 中集成 <code>UnoCSS</code>，相信能给你带来更愉快的开发体验</p><p>本文对应源码：<span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2hhbC13YW5nL0JsYXpvclVub0NTUw==">https://github.com/hal-wang/BlazorUnoCSS<i class="fa fa-external-link-alt"></i></span></p><span id="more"></span><p><em>本文均使用 pnpm，你也可以对应替换为 npm 或 yarn 等</em></p><h2 id="UnoCSS-更强大好用"><a href="#UnoCSS-更强大好用" class="headerlink" title="UnoCSS 更强大好用"></a><code>UnoCSS</code> 更强大好用</h2><p>在 <code>Blazor</code> 中，网上的教程都是使用 <code>postcss</code> 集成 <code>tailwindcss</code></p><p>但 <code>postcss</code> 对 <code>tailwindcss</code> 支持度在 <code>Blazor</code> 中其实不太好</p><p>举个例子，比如 <code>pt-30</code>、<code>pt-30.2</code>，在 <code>tailwindcss</code> 中不可以使用，因为没有预设这个</p><p>但是根据本教程在集成 <code>UnoCSS</code> 后的 <code>Blazor</code> 却可以</p><p>因此按本教程集成 <code>UnoCSS</code> 后，不需要看着文档开发，自由度也更高</p><hr><p><strong>下面我们开始在 <code>Blazor</code> 集成 <code>UnoCSS</code></strong></p><h2 id="安装-js-依赖"><a href="#安装-js-依赖" class="headerlink" title="安装 js 依赖"></a>安装 js 依赖</h2><p>在项目下执行语句，以创建 <code>package.json</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">npm init -y</span><br></pre></td></tr></table></figure><p>在 <code>package.json</code> 文件中，增加 <code>postcss</code> 生成语句，用于生成 css</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="string">&quot;scripts&quot;</span>: &#123;</span><br><span class="line">  <span class="string">&quot;buildcss&quot;</span>: <span class="string">&quot;postcss wwwroot/app.css -o wwwroot/app.min.css&quot;</span></span><br><span class="line">&#125;,</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><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">pnpm add @unocss/postcss</span><br><span class="line">pnpm add postcss-cli</span><br><span class="line">pnpm add unocss</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><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;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;blazor-unocss&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;1.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;&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;index.js&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;buildcss&quot;</span><span class="punctuation">:</span> <span class="string">&quot;postcss wwwroot/app.css -o wwwroot/app.min.css&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 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="string">&quot;&quot;</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;ISC&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;dependencies&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;@unocss/postcss&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^0.59.4&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;postcss-cli&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^11.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;unocss&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^0.59.4&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="配置-unocss-和-postcss"><a href="#配置-unocss-和-postcss" class="headerlink" title="配置 unocss 和 postcss"></a>配置 unocss 和 postcss</h2><p>增加文件 <code>postcss.config.mjs</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="comment">// postcss.config.mjs</span></span><br><span class="line"><span class="keyword">import</span> unocss <span class="keyword">from</span> <span class="string">&quot;@unocss/postcss&quot;</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">plugins</span>: [<span class="title function_">unocss</span>()],</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>增加文件 <code>uno.config.mjs</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="comment">// uno.config.mjs</span></span><br><span class="line"><span class="keyword">import</span> &#123; defineConfig, presetUno &#125; <span class="keyword">from</span> <span class="string">&quot;unocss&quot;</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">content</span>: &#123;</span><br><span class="line">    <span class="attr">filesystem</span>: [<span class="string">&quot;**/*.&#123;html,js,ts,jsx,tsx,razor,cshtml&#125;&quot;</span>],</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">presets</span>: [<span class="title function_">presetUno</span>()],</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>增加或修改文件 <code>wwwroot/app.css</code></p><figure class="highlight css"><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">/* wwwroot/app.css */</span></span><br><span class="line"><span class="keyword">@unocss</span>;</span><br></pre></td></tr></table></figure><p>现在如果执行语句 <code>npm run buildcss</code>，会在 <code>wwwroot/app.min.css</code>生成计算后的 css 文件</p><p>因此我们需要引用这个生成的文件</p><h2 id="引用-css-文件"><a href="#引用-css-文件" class="headerlink" title="引用 css 文件"></a>引用 css 文件</h2><p>修改 App.razor 文件（有可能是其他文件，包含 html 头部基元信息）</p><p>增加</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">link</span> <span class="attr">rel</span>=<span class="string">&quot;stylesheet&quot;</span> <span class="attr">href</span>=<span class="string">&quot;app.min.css&quot;</span> /&gt;</span></span><br></pre></td></tr></table></figure><h2 id="配置编译脚本"><a href="#配置编译脚本" class="headerlink" title="配置编译脚本"></a>配置编译脚本</h2><p>修改 Blazor 项目 .csproj 文件，增加 <code>PreBuild</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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">Target</span> <span class="attr">Name</span>=<span class="string">&quot;PreBuild&quot;</span> <span class="attr">BeforeTargets</span>=<span class="string">&quot;PreBuildEvent&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">Exec</span> <span class="attr">Command</span>=<span class="string">&quot;npm run buildcss&quot;</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">Target</span>&gt;</span></span><br></pre></td></tr></table></figure><p>之后每次编译项目，都会自动执行语句 <code>npm run buildcss</code></p><p>不需要再手动执行</p><h2 id="源码"><a href="#源码" class="headerlink" title="源码"></a>源码</h2><p>GitHub: <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2hhbC13YW5nL0JsYXpvclVub0NTUw==">https://github.com/hal-wang/BlazorUnoCSS<i class="fa fa-external-link-alt"></i></span></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;在原子化 css 中，最热门的当属 &lt;code&gt;tailwindcss&lt;/code&gt;，而且在 &lt;code&gt;Blazor&lt;/code&gt; 中集成 &lt;code&gt;tailwindcss&lt;/code&gt; 的教程也很多&lt;/p&gt;
&lt;p&gt;本文使用一个更强大的 &lt;code&gt;UnoCSS&lt;/code&gt;，目前 &lt;code&gt;Blazor&lt;/code&gt; 集成 &lt;code&gt;UnoCSS&lt;/code&gt; 的教程却一个也找不到&lt;/p&gt;
&lt;p&gt;这里记录一下如何在 &lt;code&gt;Blazor&lt;/code&gt; 中集成 &lt;code&gt;UnoCSS&lt;/code&gt;，相信能给你带来更愉快的开发体验&lt;/p&gt;
&lt;p&gt;本文对应源码：&lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cHM6Ly9naXRodWIuY29tL2hhbC13YW5nL0JsYXpvclVub0NTUw==&quot;&gt;https://github.com/hal-wang/BlazorUnoCSS&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;</summary>
    
    
    
    <category term="记录" scheme="https://blog.hal.wang/categories/%E8%AE%B0%E5%BD%95/"/>
    
    
    <category term="Blazor" scheme="https://blog.hal.wang/tags/Blazor/"/>
    
  </entry>
  
  <entry>
    <title>C# 中的性能提升 - Span&amp;lt;T&amp;gt; 和 Memory&amp;lt;T&amp;gt;</title>
    <link href="https://blog.hal.wang/4e254812/"/>
    <id>https://blog.hal.wang/4e254812/</id>
    <published>2023-11-19T11:34:23.000Z</published>
    <updated>2026-03-18T15:50:10.582Z</updated>
    
    <content type="html"><![CDATA[<p>简单来说，<code>Span&lt;T&gt;</code> 和 <code>Memory&lt;T&gt;</code> 能够以安全的方式使用指针访问内存，它们提供了一种类型安全的方法来访问任意内存的连续区域。</p><p>他们表示连续的内存块，没有任何复制语义，类似于指针。</p><p>另外还有只读版本 <code>ReadOnlySpan&lt;T&gt;</code> 和 <code>ReadOnlyMemory&lt;T&gt;</code></p><span id="more"></span><h2 id="类型"><a href="#类型" class="headerlink" title="类型"></a>类型</h2><p><code>C#</code> 允许以不安全的方式使用指针，类似 <code>C/C++</code>。虽然效率高，但指针不被 <code>GC</code> 跟踪，容易造成内存泄漏</p><p>为此在 <code>C# 7</code> 中引入了新的类型</p><ul><li><code>Span&lt;T&gt;</code> 以类型安全的方式表示内存的连续部分</li><li><code>Memory&lt;T&gt;</code> 连续的内存区域</li><li><code>ReadOnlySpan&lt;T&gt;</code> 与 <code>Span&lt;T&gt;</code> 类似，但内存区域是只读的</li><li><code>ReadOnlyMemory&lt;T&gt;</code> 与 <code>ReadOnlyMemory&lt;T&gt;</code> 类似，但内存连续区域是只读的</li></ul><p><code>C# 7</code> 对应的 <code>.net</code> 版本是 <code>.NET Core 2.1</code>。在后续更新中逐渐增强完善</p><p>截至目前 <code>C#</code> 版本是 <code>C# 12</code>，因此本文内容也是以 <code>C# 12</code> 为基础</p><h2 id="Span-的原理"><a href="#Span-的原理" class="headerlink" title="Span&lt;T&gt; 的原理"></a>Span&lt;T&gt; 的原理</h2><p><code>Span&lt;T&gt;</code> 是值类型的 <code>ref struct</code>，定义类似于</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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">public</span> <span class="keyword">readonly</span> <span class="keyword">ref</span> <span class="keyword">struct</span> Span&lt;T&gt;</span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">readonly</span> <span class="keyword">ref</span> T _pointer;</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">readonly</span> <span class="built_in">int</span> _length;</span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="ref-struct"><a href="#ref-struct" class="headerlink" title="ref struct"></a>ref struct</h3><p><code>ref struct</code> 和 <code>struct</code> 相比有一些使用限制</p><ul><li>不能是数组的元素类型</li><li>不能是类或非 <code>ref struct</code> 的字段的声明类型</li><li>不能实现接口</li><li>不能被装箱为 <code>System.ValueType</code> 或 <code>System.Object</code></li><li>不能是类型参数</li><li>变量不能由 <code>Lambda</code> 表达式或本地函数捕获</li><li>变量不能在 <code>async</code> 方法中使用。 但是，可以在同步方法中使用 <code>ref struct</code> 变量，例如，在返回 <code>Task</code> 或 <code>Task&lt;TResult&gt;</code> 的方法中。</li><li>变量不能在迭代器中使用</li></ul><h3 id="索引器"><a href="#索引器" class="headerlink" title="索引器"></a>索引器</h3><p><code>Span&lt;T&gt;</code> 的索引器是使用 <code>ref T</code> 声明的，因此索引器返回的是实际存储位置的引用</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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">public</span> <span class="keyword">ref</span> T <span class="keyword">this</span>[<span class="built_in">int</span> index]</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">get</span></span><br><span class="line">    &#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><h2 id="Memory"><a href="#Memory" class="headerlink" title="Memory&lt;T&gt;"></a>Memory&lt;T&gt;</h2><p><code>Memory&lt;T&gt;</code> 表示内存中一段连续的区域，对应的只读版本为 <code>ReadOnlyMemory&lt;T&gt;</code>。</p><p><code>Memory&lt;T&gt;</code>很多用法与 <code>Span&lt;T&gt;</code> 相似</p><p><code>Memory&lt;T&gt;</code> 实现原理类似如下</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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">public</span> <span class="keyword">readonly</span> <span class="keyword">struct</span> Memory&lt;T&gt;</span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">readonly</span> <span class="built_in">object</span> _object;</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">readonly</span> <span class="built_in">int</span> _index;</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">readonly</span> <span class="built_in">int</span> _length;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="与-Span-的区别"><a href="#与-Span-的区别" class="headerlink" title="与 Span&lt;T&gt; 的区别"></a>与 Span&lt;T&gt; 的区别</h3><p><code>Memory&lt;T&gt;</code> 的很多操作与 <code>Span&lt;T&gt;</code> 类似，可以通过数组创建 <code>Memory&lt;T&gt;</code> 并进行切片。</p><p><code>Memory&lt;T&gt;</code> 不是 <code>ref struct</code> 类型，因此 <code>Memory&lt;T&gt;</code> 可以在存储在堆，所以有不同于 <code>Span&lt;T&gt;</code> 的特性</p><ul><li>可以作为类的字段或属性</li><li>可以在异步函数中使用</li></ul><p><code>Memory&lt;T&gt;</code> 有个 <code>Span</code> 属性，可以获取 <code>Span&lt;T&gt;</code> 并处理</p><h3 id="为什么需要-Memory"><a href="#为什么需要-Memory" class="headerlink" title="为什么需要 Memory&lt;T&gt;"></a>为什么需要 Memory&lt;T&gt;</h3><p>有了 <code>Span&lt;T&gt;</code> 为什么还要 <code>Memory&lt;T&gt;</code>？</p><p>由于 <code>Span&lt;T&gt;</code> 无论何种情况，只能存在于栈中，因此 <code>Span&lt;T&gt;</code> 的使用限制比较多。</p><p>尤其是 <code>Span&lt;T&gt;</code> 无法在异步函数中使用，因此限制较少的 <code>Memory&lt;T&gt;</code> 更适用于这种场景。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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="function"><span class="keyword">static</span> <span class="keyword">async</span> Task&lt;<span class="built_in">int</span>&gt; <span class="title">ChecksumReadAsync</span>(<span class="params">Memory&lt;<span class="built_in">byte</span>&gt; buffer, Stream stream</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="built_in">int</span> bytesRead = <span class="keyword">await</span> stream.ReadAsync(buffer);</span><br><span class="line">  <span class="keyword">return</span> Checksum(buffer.Span.Slice(<span class="number">0</span>, bytesRead));</span><br><span class="line">  <span class="comment">// Or buffer.Slice(0, bytesRead).Span</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="built_in">int</span> <span class="title">Checksum</span>(<span class="params">Span&lt;<span class="built_in">byte</span>&gt; buffer</span>)</span> &#123; ... &#125;</span><br></pre></td></tr></table></figure><h2 id="stackalloc-表达式"><a href="#stackalloc-表达式" class="headerlink" title="stackalloc 表达式"></a>stackalloc 表达式</h2><p><code>stackalloc</code> 能在堆栈上分配内存块，分配的内存块不会被 <code>GC</code> 自动回收，生命周期仅限当前方法内</p><p>默认变量类型为指针，是不安全代码。但是可以将变量赋值给 <code>Span&lt;T&gt;</code> 或 <code>ReadOnlySpan&lt;T&gt;</code> 即为安全代码</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><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">unsafe</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">var</span> nums = <span class="keyword">stackalloc</span> <span class="built_in">int</span>[<span class="number">10</span>]; <span class="comment">// int* 类型, unsafe</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Span&lt;<span class="built_in">int</span>&gt; nums = <span class="keyword">stackalloc</span> <span class="built_in">int</span>[<span class="number">10</span>]; <span class="comment">// safe</span></span><br></pre></td></tr></table></figure><p>可以和定义数组一样赋初值</p><figure class="highlight cs"><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&lt;<span class="built_in">int</span>&gt; first = <span class="keyword">stackalloc</span> <span class="built_in">int</span>[<span class="number">3</span>] &#123; <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span> &#125;;</span><br><span class="line">Span&lt;<span class="built_in">int</span>&gt; second = <span class="keyword">stackalloc</span> <span class="built_in">int</span>[] &#123; <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span> &#125;;</span><br><span class="line">ReadOnlySpan&lt;<span class="built_in">int</span>&gt; third = <span class="keyword">stackalloc</span>[] &#123; <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span> &#125;;</span><br></pre></td></tr></table></figure><h2 id="Span-在字符串中的实践"><a href="#Span-在字符串中的实践" class="headerlink" title="Span&lt;T&gt; 在字符串中的实践"></a>Span&lt;T&gt; 在字符串中的实践</h2><p><code>Span&lt;T&gt;</code> 的应用场景很广，这里只举例字符串，感受一下 <code>Span&lt;T&gt;</code> 的强大</p><p><code>Span&lt;T&gt;</code> 对字符串的切片效率很高，在内存中不需要创建临时的字符串</p><p>比如对用户输入的表达式进行计算，这里仅简单的处理加法如 <code>11+22</code></p><h3 id="常规简单做法"><a href="#常规简单做法" class="headerlink" title="常规简单做法"></a>常规简单做法</h3><p>使用 <code>string.SubString</code>，会在内存中生成临时字符串</p><p>如果在循环中执行次数很多，就会在堆中生成很多临时字符串</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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> text = <span class="string">&quot;11+22&quot;</span>;</span><br><span class="line"><span class="keyword">var</span> index = text.IndexOf(<span class="string">&#x27;+&#x27;</span>);</span><br><span class="line"><span class="keyword">var</span> num1 = <span class="built_in">int</span>.Parse(text[..index]);</span><br><span class="line"><span class="keyword">var</span> num2 = <span class="built_in">int</span>.Parse(text[(index + <span class="number">1</span>)..]);</span><br><span class="line">Console.WriteLine(num1 + num2); <span class="comment">// 33</span></span><br></pre></td></tr></table></figure><h3 id="使用-Span-ReadOnlySpan"><a href="#使用-Span-ReadOnlySpan" class="headerlink" title="使用 Span&lt;T&gt; &#x2F; ReadOnlySpan&lt;T&gt;"></a>使用 Span&lt;T&gt; &#x2F; ReadOnlySpan&lt;T&gt;</h3><p>不会产生临时字符串，而且切片效率会高很多，没有给 GC 增加压力</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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> text = <span class="string">&quot;11+22&quot;</span>;</span><br><span class="line"><span class="keyword">var</span> index = text.IndexOf(<span class="string">&#x27;+&#x27;</span>);</span><br><span class="line"><span class="keyword">var</span> span = text.AsSpan();</span><br><span class="line"><span class="keyword">var</span> num1 = <span class="built_in">int</span>.Parse(span[..index]);</span><br><span class="line"><span class="keyword">var</span> num2 = <span class="built_in">int</span>.Parse(span[(index + <span class="number">1</span>)..]);</span><br><span class="line">Console.WriteLine(num1 + num2); <span class="comment">// 33</span></span><br></pre></td></tr></table></figure><h2 id="List-自增的影响"><a href="#List-自增的影响" class="headerlink" title="List&lt;T&gt; 自增的影响"></a>List&lt;T&gt; 自增的影响</h2><p><code>List&lt;T&gt;</code> 自增会造成 <code>Span&lt;T&gt;</code> 引用的位置错误，因此是这里是一个坑</p><p>先创建容量为 10 的 <code>List&lt;int&gt;</code>，并赋初值，使用 <code>CollectionsMarshal</code> 创建一个 <code>Span&lt;int&gt;</code>，指向 <code>List&lt;int&gt;</code> 内存块</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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> list = <span class="keyword">new</span> List&lt;<span class="built_in">int</span>&gt;(<span class="number">10</span>);</span><br><span class="line">Console.WriteLine(<span class="string">$&quot;Capacity: <span class="subst">&#123;list.Capacity&#125;</span>&quot;</span>); <span class="comment">// Capacity: 10</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++)</span><br><span class="line">&#123;</span><br><span class="line">    list.Add(i);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> span = CollectionsMarshal.AsSpan(list);</span><br></pre></td></tr></table></figure><p>代码到这里，span 就是 list 中元素的所在的内存段，对 <code>span[i]</code> 的读写都和 <code>list[i]</code> 的读写都是操作同一段内存数据，一切正常</p><p>现在我们给 list 增加一个元素，list 长度超过容量 10，因此容量会自增到 20</p><figure class="highlight cs"><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">list.Add(<span class="number">10</span>);</span><br><span class="line">Console.WriteLine(<span class="string">$&quot;Capacity: <span class="subst">&#123;list.Capacity&#125;</span>&quot;</span>); <span class="comment">// Capacity: 20</span></span><br></pre></td></tr></table></figure><p>此时，span 指向的仍然是原来的 list 内存段，但现在 list 已经通过自增变为了另一个新的内存段</p><p>那么现在 <code>span</code> 和 <code>list</code> 两个变量就不相干了，给 <code>span[i]</code> 和 <code>list[i]</code> 赋值都互不影响，<code>span</code> 就失去了作用和意义</p><p>从逻辑上说，这也算是一种内存泄漏，只是这段内存没有用，即使内存泄漏也没有影响，这段内存会随着 span 变量的回收而回收</p><figure class="highlight cs"><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[<span class="number">0</span>] = <span class="number">100</span>;</span><br><span class="line">Console.WriteLine(<span class="string">$&quot;span[0]: <span class="subst">&#123;span[<span class="number">0</span>]&#125;</span>&quot;</span>); <span class="comment">// span[0]: 100</span></span><br><span class="line">Console.WriteLine(<span class="string">$&quot;list[0]: <span class="subst">&#123;list[<span class="number">0</span>]&#125;</span>&quot;</span>); <span class="comment">// list[0]: 0</span></span><br></pre></td></tr></table></figure><p>或</p><figure class="highlight cs"><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">list[<span class="number">1</span>] = <span class="number">100</span>;</span><br><span class="line">Console.WriteLine(<span class="string">$&quot;span[1]: <span class="subst">&#123;span[<span class="number">1</span>]&#125;</span>&quot;</span>); <span class="comment">// span[1]: 0</span></span><br><span class="line">Console.WriteLine(<span class="string">$&quot;list[1]: <span class="subst">&#123;list[<span class="number">1</span>]&#125;</span>&quot;</span>); <span class="comment">// list[1]: 100</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;简单来说，&lt;code&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt; 和 &lt;code&gt;Memory&amp;lt;T&amp;gt;&lt;/code&gt; 能够以安全的方式使用指针访问内存，它们提供了一种类型安全的方法来访问任意内存的连续区域。&lt;/p&gt;
&lt;p&gt;他们表示连续的内存块，没有任何复制语义，类似于指针。&lt;/p&gt;
&lt;p&gt;另外还有只读版本 &lt;code&gt;ReadOnlySpan&amp;lt;T&amp;gt;&lt;/code&gt; 和 &lt;code&gt;ReadOnlyMemory&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/p&gt;</summary>
    
    
    
    <category term="记录" scheme="https://blog.hal.wang/categories/%E8%AE%B0%E5%BD%95/"/>
    
    
    <category term="C#" scheme="https://blog.hal.wang/tags/C/"/>
    
  </entry>
  
  <entry>
    <title>使用 Let’s Encrypt 创建免费证书</title>
    <link href="https://blog.hal.wang/f965c2da/"/>
    <id>https://blog.hal.wang/f965c2da/</id>
    <published>2023-09-18T20:08:03.000Z</published>
    <updated>2026-03-18T15:50:10.586Z</updated>
    
    <content type="html"><![CDATA[<p>仅需简单几步，即可使用 Let’s Encrypt 创建免费的多域名和泛域名证书</p><span id="more"></span><h2 id="环境要求"><a href="#环境要求" class="headerlink" title="环境要求"></a>环境要求</h2><ul><li>Linux</li></ul><h2 id="安装-cerbot"><a href="#安装-cerbot" class="headerlink" title="安装 cerbot"></a>安装 cerbot</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"><span class="built_in">sudo</span> add-apt-repository ppa:certbot/certbot</span><br><span class="line"><span class="built_in">sudo</span> apt-get update</span><br><span class="line"><span class="built_in">sudo</span> apt-get install certbot</span><br></pre></td></tr></table></figure><h2 id="创建证书"><a href="#创建证书" class="headerlink" title="创建证书"></a>创建证书</h2><p>这里是通过手动 dns 的方式验证域名</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"><span class="built_in">sudo</span> certbot certonly --manual --preferred-challenges dns</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">Enter email address (used <span class="keyword">for</span> urgent renewal and security notices)</span><br><span class="line"> (Enter <span class="string">&#x27;c&#x27;</span> to cancel):</span><br></pre></td></tr></table></figure><p>然后同意几个协议，都输入 <code>Y</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></pre></td><td class="code"><pre><span class="line">Please enter the domain name(s) you would like on your certificate (comma and/or</span><br><span class="line">space separated) (Enter <span class="string">&#x27;c&#x27;</span> to cancel):</span><br></pre></td></tr></table></figure><p>输入域名后，等待验证 dns</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">Please deploy a DNS TXT record under the name:</span><br><span class="line"></span><br><span class="line">_acme-challenge.domain.com.</span><br><span class="line"></span><br><span class="line">with the following value:</span><br><span class="line"></span><br><span class="line">68vo-beB0fF8csOEolL6ADkK-9FaqwaweaweUJfejSe</span><br></pre></td></tr></table></figure><p>这时先按提示在云服务商添加 dns 的 TXT 记录，稍等片刻再回车</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">Successfully received certificate.</span><br><span class="line">Certificate is saved at: /etc/letsencrypt/live/domain.com/fullchain.pem</span><br><span class="line">Key is saved at:         /etc/letsencrypt/live/domain.com/privkey.pem</span><br><span class="line">This certificate expires on 2023-12-17.</span><br><span class="line">These files will be updated when the certificate renews.</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">/etc/letsencrypt/live/domain.com</span><br></pre></td></tr></table></figure><h2 id="证书链不完整"><a href="#证书链不完整" class="headerlink" title="证书链不完整"></a>证书链不完整</h2><p>Let’s Encrypt 创建的证书可能会出现证书链不完整的错误</p><p>需要修复一下证书链</p><p>浏览器打开 <code>https://myssl.com/chain_download.html</code></p><p>选择 <code>上传证书</code></p><p>编辑证书文件，将文件内容拷出来，粘贴到网页，再点击 <code>获取证书链</code>，即得到修复后的证书内容</p><h2 id="证书转换"><a href="#证书转换" class="headerlink" title="证书转换"></a>证书转换</h2><p>PEM -&gt; PFX&#x2F;PKCS12</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">openssl pkcs12 -<span class="built_in">export</span> -out cert.pfx -inkey privkey1.pem -<span class="keyword">in</span> cert1.pem -certfile chain1.pem</span><br></pre></td></tr></table></figure><p>PFX&#x2F;PKCS12 -&gt; PEM</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">openssl pkcs12 -<span class="keyword">in</span> cert.pfx -out cert.pem -nodes</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;仅需简单几步，即可使用 Let’s Encrypt 创建免费的多域名和泛域名证书&lt;/p&gt;</summary>
    
    
    
    <category term="记录" scheme="https://blog.hal.wang/categories/%E8%AE%B0%E5%BD%95/"/>
    
    
    <category term="ssl" scheme="https://blog.hal.wang/tags/ssl/"/>
    
  </entry>
  
  <entry>
    <title>《重构:改善既有代码的设计》读书笔记</title>
    <link href="https://blog.hal.wang/fc63781e/"/>
    <id>https://blog.hal.wang/fc63781e/</id>
    <published>2023-07-23T20:44:27.000Z</published>
    <updated>2026-03-18T15:50:10.586Z</updated>
    
    <content type="html"><![CDATA[<p>阅读《重构:改善既有代码的设计》第二版，学到很多思想和技能，特此记录</p><p>书中演示代码主要以 JavaScript 为主</p><span id="more"></span><h2 id="何时重构"><a href="#何时重构" class="headerlink" title="何时重构"></a>何时重构</h2><p>书中提到一些代码的坏味道</p><h3 id="神秘命名（Mysterious-Name）"><a href="#神秘命名（Mysterious-Name）" class="headerlink" title="神秘命名（Mysterious Name）"></a>神秘命名（Mysterious Name）</h3><p>命名是编程中最难的两件事之一。正因为如此，修改命名可能是最常用的重构方法。如果你发现改名很难，那就说明代码设计有问题。当我们不能给一个模块，一个对象，一个函数，甚至一个变量找到合适名称的时候，往往说明我们对问题的理解还不够透彻，需要重新去挖掘问题的本质，对问题域进行重新分析和抽象。</p><h3 id="重复代码（Duplicated-Code）"><a href="#重复代码（Duplicated-Code）" class="headerlink" title="重复代码（Duplicated Code）"></a>重复代码（Duplicated Code）</h3><p>同一类的两个函数含有相同的表达式，就应该提炼。</p><h3 id="过长函数（Long-Function）"><a href="#过长函数（Long-Function）" class="headerlink" title="过长函数（Long Function）"></a>过长函数（Long Function）</h3><p>活得最长，最好的程序，其中函数一般都很短。如果你觉得需要写注释，大部分情况就代表这个东西需要写进一个独立的函数里面，然后根据用途来命名比较好。条件表达式和循环往往也是提炼函数的信号。</p><h3 id="过长参数列表（Long-Parameter-List）"><a href="#过长参数列表（Long-Parameter-List）" class="headerlink" title="过长参数列表（Long Parameter List）"></a>过长参数列表（Long Parameter List）</h3><p>使用类可以有效的缩短参数列表。如果多个函数有同样的几个参数，引入一个类就尤为有意义。</p><h3 id="全局数据（Global-Data）"><a href="#全局数据（Global-Data）" class="headerlink" title="全局数据（Global Data）"></a>全局数据（Global Data）</h3><p>全局数据仍然是最刺鼻的坏味道之一。它的问题是，全局数据在任何地方都可以被修改。所以正确的做法是将全局数据封装起来，用函数将其包起来，这样就知道那些地方修改了它。有少量的全局数据或者无妨，但数量越多，处理难度就会指数上升。（良药与毒药的区别在于剂量）</p><h3 id="可变数据（Mutable-Data）"><a href="#可变数据（Mutable-Data）" class="headerlink" title="可变数据（Mutable Data）"></a>可变数据（Mutable Data）</h3><p>核心是缩小作用域。可以通过封装变量来确保所有数据更新操作都通过很少几个函数来进行，使其更容易被监控。</p><h3 id="发散式变化（Divergent-Change）"><a href="#发散式变化（Divergent-Change）" class="headerlink" title="发散式变化（Divergent Change）"></a>发散式变化（Divergent Change）</h3><p>发散式变化指某个模块因为不同的原因在不同的方向上发生变化。每次只关心一个上下文。找到引起发散式变化的原因，将它拆分出来。</p><h3 id="霰弹式修改（Shotgun-Surgery）"><a href="#霰弹式修改（Shotgun-Surgery）" class="headerlink" title="霰弹式修改（Shotgun Surgery）"></a>霰弹式修改（Shotgun Surgery）</h3><p>在每次修改的时候，应该只修改一处，而不是到处的修改。因为一个需求，需要修改 3 处代码，那么这就需要思考，这 3 处代码是否应该抽离出来。一个常用的策略就是使用内联（inline）重构代码把本不该分散的逻辑拽回一处。</p><h3 id="依恋情结（Feature-Envy）"><a href="#依恋情结（Feature-Envy）" class="headerlink" title="依恋情结（Feature Envy）"></a>依恋情结（Feature Envy）</h3><p>模块化，力求代码分出区域，最大化区域内部交互，最小化区域间交互。如果两个模块交互频繁，它们应该合并在一起。</p><h3 id="数据泥团（Data-Clumps）"><a href="#数据泥团（Data-Clumps）" class="headerlink" title="数据泥团（Data Clumps）"></a>数据泥团（Data Clumps）</h3><p>如果在多个类中，出现了很多相同项的数据，你需要想想是否要通过将数据提炼成类，来抽离出一个独立对象。建议新建类而非简单的结构体。</p><h3 id="基本类型偏执（Primitive-Obsession）"><a href="#基本类型偏执（Primitive-Obsession）" class="headerlink" title="基本类型偏执（Primitive Obsession）"></a>基本类型偏执（Primitive Obsession）</h3><p>很多程序员不愿意创建对自己的问题域有用的基本类型，如钱，坐标，范围等。比如有程序员用字符串来表示电话号码，实际上你应该抽象出来一个电话号码对象。</p><h3 id="重复的-switch（Repeated-Switches）"><a href="#重复的-switch（Repeated-Switches）" class="headerlink" title="重复的 switch（Repeated Switches）"></a>重复的 switch（Repeated Switches）</h3><p>尽量使用多态而非 switch。</p><h3 id="循环语句（Loops）"><a href="#循环语句（Loops）" class="headerlink" title="循环语句（Loops）"></a>循环语句（Loops）</h3><p>我们应该用管道操作(如 filter 和 map)来替代循环，这样能更快的看清被处理的元素和处理他们的动作。</p><h3 id="冗赘的元素（Lasy-Element）"><a href="#冗赘的元素（Lasy-Element）" class="headerlink" title="冗赘的元素（Lasy Element）"></a>冗赘的元素（Lasy Element）</h3><p>能简单的代码，尽量简单。未来变复杂的时候，再去考虑它。</p><h3 id="夸夸其谈的通用性（Speculative-Generality）"><a href="#夸夸其谈的通用性（Speculative-Generality）" class="headerlink" title="夸夸其谈的通用性（Speculative Generality）"></a>夸夸其谈的通用性（Speculative Generality）</h3><p>同上，能简单的代码，尽量简单。通用性？过早的优化是万恶之源</p><h3 id="临时字段（Temporary-Field）"><a href="#临时字段（Temporary-Field）" class="headerlink" title="临时字段（Temporary Field）"></a>临时字段（Temporary Field）</h3><p>临时字段指内部某个字段仅为某种特定情况而设。临时的字段不应该存在。你需要给他们搬个新家，把所有和临时变量相关的代码搬至那里。</p><h3 id="过长的消息链（Message-Chains）"><a href="#过长的消息链（Message-Chains）" class="headerlink" title="过长的消息链（Message Chains）"></a>过长的消息链（Message Chains）</h3><p>如果你看到用户向一个对象请求另一个对象，然后再向后者请求另一个对象，然后再请求另一个对象，这就是消息链。消息链意味着客户端会耦合消息链的查找过程。应该将查找过程独立出一个函数。</p><h3 id="中间人（Middle-Man）"><a href="#中间人（Middle-Man）" class="headerlink" title="中间人（Middle Man）"></a>中间人（Middle Man）</h3><p>委托函数过多时，减少委托，移除中间人，让调用者直接访问目标类进行操作。</p><h3 id="内幕交易（Insider-Trading）"><a href="#内幕交易（Insider-Trading）" class="headerlink" title="内幕交易（Insider Trading）"></a>内幕交易（Insider Trading）</h3><p>减少模块之间频繁的数据交换，并把这种交换放到明面上。</p><h3 id="过大的类（Large-Class）"><a href="#过大的类（Large-Class）" class="headerlink" title="过大的类（Large Class）"></a>过大的类（Large Class）</h3><p>当一个类代码行数太多或者功能职责太多的时候，拆掉它。两种拆分方法：提取新类，当大类的部分行为可以分解为一个单独的组件，则可以使用提取类的方式拆分。提取子类，当大类的部分行为可以以不同的方式实现或在极少数情况下使用，则可以使用提取子类方式拆分。</p><h3 id="异曲同工的类（Alternative-Classes-with-Different-Interfaces）"><a href="#异曲同工的类（Alternative-Classes-with-Different-Interfaces）" class="headerlink" title="异曲同工的类（Alternative Classes with Different Interfaces）"></a>异曲同工的类（Alternative Classes with Different Interfaces）</h3><p>两个类有着相同的功能，但方法名称不同。重命名方法，并去除掉不必要的重复代码。</p><h3 id="纯数据类（Data-Class）"><a href="#纯数据类（Data-Class）" class="headerlink" title="纯数据类（Data Class）"></a>纯数据类（Data Class）</h3><p>纯数据类常常意味着行为被放在了错误的地方。处理数据的行为应该从客户端移至纯数据类中。</p><h3 id="被拒绝的遗赠（Refused-Bequest）"><a href="#被拒绝的遗赠（Refused-Bequest）" class="headerlink" title="被拒绝的遗赠（Refused Bequest）"></a>被拒绝的遗赠（Refused Bequest）</h3><p>如果子类复用了父类的实现，就应该支持父类的接口。</p><h3 id="注释（Comments）"><a href="#注释（Comments）" class="headerlink" title="注释（Comments）"></a>注释（Comments）</h3><p>注释是提示你，这个地方该重构啦。如果你觉得需要写注释的时候，请先重构，试着让所有注释都变得多余。</p><h2 id="重构手法"><a href="#重构手法" class="headerlink" title="重构手法"></a>重构手法</h2><h3 id="提炼函数（Extract-Function）"><a href="#提炼函数（Extract-Function）" class="headerlink" title="提炼函数（Extract Function）"></a>提炼函数（Extract Function）</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">printOwing</span>(<span class="params">invoice</span>) &#123;</span><br><span class="line">  <span class="title function_">printBanner</span>();</span><br><span class="line">  <span class="keyword">let</span> outstanding = <span class="title function_">calculateOutstanding</span>();</span><br><span class="line"></span><br><span class="line">  <span class="comment">//print details</span></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`name: <span class="subst">$&#123;invoice.customer&#125;</span>`</span>);</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`amount: <span class="subst">$&#123;outstanding&#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_">printOwing</span>(<span class="params">invoice</span>) &#123;</span><br><span class="line">  <span class="title function_">printBanner</span>();</span><br><span class="line">  <span class="keyword">let</span> outstanding = <span class="title function_">calculateOutstanding</span>();</span><br><span class="line">  <span class="title function_">printDetails</span>(outstanding);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">printDetails</span>(<span class="params">outstanding</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`name: <span class="subst">$&#123;invoice.customer&#125;</span>`</span>);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`amount: <span class="subst">$&#123;outstanding&#125;</span>`</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>提炼函数的动机：<strong>将意图与实现分开</strong></p><h3 id="内联函数（Inline-Function）"><a href="#内联函数（Inline-Function）" class="headerlink" title="内联函数（Inline Function）"></a>内联函数（Inline Function）</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">getRating</span>(<span class="params">driver</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">moreThanFiveLateDeliveries</span>(driver) ? <span class="number">2</span> : <span class="number">1</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_">moreThanFiveLateDeliveries</span>(<span class="params">driver</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> driver.<span class="property">numberOfLateDeliveries</span> &gt; <span class="number">5</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_">getRating</span>(<span class="params">driver</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> driver.<span class="property">numberOfLateDeliveries</span> &gt; <span class="number">5</span> ? <span class="number">2</span> : <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="提炼变量（Extract-Variable）"><a href="#提炼变量（Extract-Variable）" class="headerlink" title="提炼变量（Extract Variable）"></a>提炼变量（Extract Variable）</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">return</span> (</span><br><span class="line">  order.<span class="property">quantity</span> * order.<span class="property">itemPrice</span> -</span><br><span class="line">  <span class="title class_">Math</span>.<span class="title function_">max</span>(<span class="number">0</span>, order.<span class="property">quantity</span> - <span class="number">500</span>) * order.<span class="property">itemPrice</span> * <span class="number">0.05</span> +</span><br><span class="line">  <span class="title class_">Math</span>.<span class="title function_">min</span>(order.<span class="property">quantity</span> * order.<span class="property">itemPrice</span> * <span class="number">0.1</span>, <span class="number">100</span>)</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> basePrice = order.<span class="property">quantity</span> * order.<span class="property">itemPrice</span>;</span><br><span class="line"><span class="keyword">const</span> quantityDiscount =</span><br><span class="line">  <span class="title class_">Math</span>.<span class="title function_">max</span>(<span class="number">0</span>, order.<span class="property">quantity</span> - <span class="number">500</span>) * order.<span class="property">itemPrice</span> * <span class="number">0.05</span>;</span><br><span class="line"><span class="keyword">const</span> shipping = <span class="title class_">Math</span>.<span class="title function_">min</span>(basePrice * <span class="number">0.1</span>, <span class="number">100</span>);</span><br><span class="line"><span class="keyword">return</span> basePrice - quantityDiscount + shipping;</span><br></pre></td></tr></table></figure><h3 id="内联变量（Inline-Variable）"><a href="#内联变量（Inline-Variable）" class="headerlink" title="内联变量（Inline Variable）"></a>内联变量（Inline Variable）</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">let</span> basePrice = anOrder.<span class="property">basePrice</span>;</span><br><span class="line"><span class="keyword">return</span> basePrice &gt; <span class="number">1000</span>;</span><br></pre></td></tr></table></figure><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">return</span> anOrder.<span class="property">basePrice</span> &gt; <span class="number">1000</span>;</span><br></pre></td></tr></table></figure><h3 id="改变函数声明（Change-Function-Declaration）"><a href="#改变函数声明（Change-Function-Declaration）" class="headerlink" title="改变函数声明（Change Function Declaration）"></a>改变函数声明（Change Function Declaration）</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="keyword">function</span> <span class="title function_">circum</span>(<span class="params">radius</span>) &#123;...&#125;</span><br></pre></td></tr></table></figure><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">function</span> <span class="title function_">circumference</span>(<span class="params">radius</span>) &#123;...&#125;</span><br></pre></td></tr></table></figure><h3 id="封装变量（Encapsulate-Variable）"><a href="#封装变量（Encapsulate-Variable）" class="headerlink" title="封装变量（Encapsulate Variable）"></a>封装变量（Encapsulate Variable）</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="keyword">let</span> defaultOwner = &#123; <span class="attr">firstName</span>: <span class="string">&quot;Martin&quot;</span>, <span class="attr">lastName</span>: <span class="string">&quot;Fowler&quot;</span> &#125;;</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> defaultOwnerData = &#123; <span class="attr">firstName</span>: <span class="string">&quot;Martin&quot;</span>, <span class="attr">lastName</span>: <span class="string">&quot;Fowler&quot;</span> &#125;;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">defaultOwner</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> defaultOwnerData;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">setDefaultOwner</span>(<span class="params">arg</span>) &#123;</span><br><span class="line">  defaultOwnerData = arg;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="变量改名（Rename-Variable）"><a href="#变量改名（Rename-Variable）" class="headerlink" title="变量改名（Rename Variable）"></a>变量改名（Rename Variable）</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="keyword">let</span> a = height * width;</span><br></pre></td></tr></table></figure><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> area = height * width;</span><br></pre></td></tr></table></figure><h3 id="引入参数对象（Introduce-Parameter-Object）"><a href="#引入参数对象（Introduce-Parameter-Object）" class="headerlink" title="引入参数对象（Introduce Parameter Object）"></a>引入参数对象（Introduce Parameter Object）</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="keyword">function</span> <span class="title function_">amountInvoiced</span>(<span class="params">startDate, endDate</span>) &#123;...&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">amountReceived</span>(<span class="params">startDate, endDate</span>) &#123;...&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">amountOverdue</span>(<span class="params">startDate, endDate</span>) &#123;...&#125;</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="keyword">function</span> <span class="title function_">amountInvoiced</span>(<span class="params">aDateRange</span>) &#123;...&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">amountReceived</span>(<span class="params">aDateRange</span>) &#123;...&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">amountOverdue</span>(<span class="params">aDateRange</span>) &#123;...&#125;</span><br></pre></td></tr></table></figure><h3 id="函数组合成类（Combine-Functions-into-Class）"><a href="#函数组合成类（Combine-Functions-into-Class）" class="headerlink" title="函数组合成类（Combine Functions into Class）"></a>函数组合成类（Combine Functions into Class）</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="keyword">function</span> <span class="title function_">base</span>(<span class="params">aReading</span>) &#123;...&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">taxableCharge</span>(<span class="params">aReading</span>) &#123;...&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">calculateBaseCharge</span>(<span class="params">aReading</span>) &#123;...&#125;</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="keyword">class</span> <span class="title class_">Reading</span> &#123;</span><br><span class="line">  <span class="title function_">base</span>(<span class="params"></span>) &#123;...&#125;</span><br><span class="line">  <span class="title function_">taxableCharge</span>(<span class="params"></span>) &#123;...&#125;</span><br><span class="line">  <span class="title function_">calculateBaseCharge</span>(<span class="params"></span>) &#123;...&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="函数组合成变换（Combine-Functions-into-Transform）"><a href="#函数组合成变换（Combine-Functions-into-Transform）" class="headerlink" title="函数组合成变换（Combine Functions into Transform）"></a>函数组合成变换（Combine Functions into Transform）</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">function</span> <span class="title function_">base</span>(<span class="params">aReading</span>) &#123;...&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">taxableCharge</span>(<span class="params">aReading</span>) &#123;...&#125;</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">function</span> <span class="title function_">enrichReading</span>(<span class="params">argReading</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> aReading = _.<span class="title function_">cloneDeep</span>(argReading);</span><br><span class="line">  aReading.<span class="property">baseCharge</span> = <span class="title function_">base</span>(aReading);</span><br><span class="line">  aReading.<span class="property">taxableCharge</span> = <span class="title function_">taxableCharge</span>(aReading);</span><br><span class="line">  <span class="keyword">return</span> aReading;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="拆分阶段（Split-Phase）"><a href="#拆分阶段（Split-Phase）" class="headerlink" title="拆分阶段（Split Phase）"></a>拆分阶段（Split Phase）</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="keyword">const</span> orderData = orderString.<span class="title function_">split</span>(<span class="regexp">/\s+/</span>);</span><br><span class="line"><span class="keyword">const</span> productPrice = priceList[orderData[<span class="number">0</span>].<span class="title function_">split</span>(<span class="string">&quot;-&quot;</span>)[<span class="number">1</span>]];</span><br><span class="line"><span class="keyword">const</span> orderPrice = <span class="built_in">parseInt</span>(orderData[<span class="number">1</span>]) * productPrice;</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> orderRecord = <span class="title function_">parseOrder</span>(order);</span><br><span class="line"><span class="keyword">const</span> orderPrice = <span class="title function_">price</span>(orderRecord, priceList);</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">parseOrder</span>(<span class="params">aString</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> values = aString.<span class="title function_">split</span>(<span class="regexp">/\s+/</span>);</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="attr">productID</span>: values[<span class="number">0</span>].<span class="title function_">split</span>(<span class="string">&quot;-&quot;</span>)[<span class="number">1</span>],</span><br><span class="line">    <span class="attr">quantity</span>: <span class="built_in">parseInt</span>(values[<span class="number">1</span>]),</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">price</span>(<span class="params">order, priceList</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> order.<span class="property">quantity</span> * priceList[order.<span class="property">productID</span>];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;阅读《重构:改善既有代码的设计》第二版，学到很多思想和技能，特此记录&lt;/p&gt;
&lt;p&gt;书中演示代码主要以 JavaScript 为主&lt;/p&gt;</summary>
    
    
    
    <category term="速查" scheme="https://blog.hal.wang/categories/%E9%80%9F%E6%9F%A5/"/>
    
    
    <category term="规范" scheme="https://blog.hal.wang/tags/%E8%A7%84%E8%8C%83/"/>
    
  </entry>
  
  <entry>
    <title>C# 各版本新特性</title>
    <link href="https://blog.hal.wang/db715eae/"/>
    <id>https://blog.hal.wang/db715eae/</id>
    <published>2023-07-08T15:57:13.000Z</published>
    <updated>2026-03-18T15:50:10.582Z</updated>
    
    <content type="html"><![CDATA[<p>仅记录 C# 5 及以后版本</p><span id="more"></span><h2 id="C-5"><a href="#C-5" class="headerlink" title="C# 5"></a>C# 5</h2><p>随 Visual Studio 2012 发布</p><h3 id="异步-async-await"><a href="#异步-async-await" class="headerlink" title="异步 async&#x2F;await"></a>异步 async&#x2F;await</h3><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">async</span> Task <span class="title">Func</span>()</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">await</span> Task.Run(() =&gt; &#123;</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><p>可以通过特性，在函数参数，取得调用方信息</p><ul><li>CallerMemberName 调用者函数名&#x2F;属性名等</li><li>CallerFilePath 调用者文件地址</li><li>CallerLineNumber 调用者代码行数</li></ul><h2 id="C-6"><a href="#C-6" class="headerlink" title="C# 6"></a>C# 6</h2><p>随 Visual Studio 2015 发布</p><h3 id="using-静态引入"><a href="#using-静态引入" class="headerlink" title="using 静态引入"></a>using 静态引入</h3><p>可以 using 引入类的静态成员</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> <span class="keyword">static</span> <span class="keyword">namespace</span>.Class;</span><br></pre></td></tr></table></figure><p>引入后可直接访问该类中的静态成员</p><h3 id="using-引入别名"><a href="#using-引入别名" class="headerlink" title="using 引入别名"></a>using 引入别名</h3><p>可以给引入的命名空间取别名</p><figure class="highlight cs"><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">using</span> name = <span class="keyword">namespace</span>.subnamespace;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> obj = name.Class();</span><br></pre></td></tr></table></figure><h3 id="异常筛选器"><a href="#异常筛选器" class="headerlink" title="异常筛选器"></a>异常筛选器</h3><p>支持异常的条件过滤</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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">try</span></span><br><span class="line">&#123;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">catch</span> (Exception e) <span class="keyword">when</span> (e.Message.StartsWith(<span class="string">&quot;demo&quot;</span>))</span><br><span class="line">&#123;</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>为属性赋初始值</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">int</span> Id &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125; = <span class="number">123</span>;</span><br></pre></td></tr></table></figure><h3 id="Expression-bodied-成员"><a href="#Expression-bodied-成员" class="headerlink" title="Expression-bodied 成员"></a>Expression-bodied 成员</h3><p>用 Lambda 表达式定义类成员</p><p>只读属性</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> DateTime Time =&gt; DateTime.Now;</span><br></pre></td></tr></table></figure><p>get&#x2F;set 属性</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">public</span> <span class="built_in">string</span> Name</span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">get</span> =&gt; name;</span><br><span class="line">  <span class="keyword">set</span> =&gt; name = <span class="keyword">value</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>无返回值方法</p><figure class="highlight cs"><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">public</span> <span class="keyword">void</span> <span class="title">Func</span>(<span class="params"><span class="built_in">int</span> id</span>)</span> =&gt; <span class="keyword">this</span>.Id = id;</span><br></pre></td></tr></table></figure><p>有返回值方法</p><figure class="highlight cs"><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">public</span> <span class="built_in">int</span> <span class="title">Func</span>(<span class="params"><span class="built_in">int</span> id</span>)</span> =&gt; id + <span class="number">1</span>;</span><br></pre></td></tr></table></figure><p>构造函数</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><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">public</span> <span class="keyword">class</span> <span class="title">Location</span></span><br><span class="line">&#123;</span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="title">Location</span>(<span class="params"><span class="built_in">string</span> name</span>)</span> =&gt; <span class="keyword">this</span>.Name = name;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="null-传播器"><a href="#null-传播器" class="headerlink" title="null 传播器"></a>null 传播器</h3><p>运算符 <code>?.</code> 和 <code>?[]</code></p><figure class="highlight cs"><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> title = user?.todos?[<span class="number">0</span>]?.title;</span><br></pre></td></tr></table></figure><h3 id="字符串插槽"><a href="#字符串插槽" class="headerlink" title="字符串插槽"></a>字符串插槽</h3><p>用 <code>$</code> 标识的字符串，其中 <code>{}</code> 内可写变量或表达式</p><figure class="highlight cs"><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> name = <span class="string">&quot;hal.wang&quot;</span>;</span><br><span class="line"><span class="keyword">var</span> text = <span class="string">$&quot;你好，<span class="subst">&#123;name&#125;</span>&quot;</span>;</span><br></pre></td></tr></table></figure><h3 id="nameof-运算符"><a href="#nameof-运算符" class="headerlink" title="nameof 运算符"></a>nameof 运算符</h3><p>获取变量的名称</p><figure class="highlight cs"><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> user1 = <span class="keyword">new</span> User();</span><br><span class="line">Console.WriteLine(<span class="keyword">nameof</span>(user1)); <span class="comment">// user1</span></span><br></pre></td></tr></table></figure><h2 id="C-7"><a href="#C-7" class="headerlink" title="C# 7"></a>C# 7</h2><p>随 Visual Studio 2017 发布</p><h3 id="out-变量"><a href="#out-变量" class="headerlink" title="out 变量"></a>out 变量</h3><p>简化之前的 out 变量写法，可以不用单独定义变量</p><p>以前的写法为</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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> intput = <span class="string">&quot;123&quot;</span>;</span><br><span class="line"><span class="built_in">int</span> num;</span><br><span class="line"><span class="keyword">if</span>(<span class="built_in">int</span>.TryParse(intput, <span class="keyword">out</span> num))&#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 cs"><table><tr><td class="gutter"><pre><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> intput = <span class="string">&quot;123&quot;</span>;</span><br><span class="line"><span class="keyword">if</span>(<span class="built_in">int</span>.TryParse(intput, <span class="keyword">out</span> <span class="keyword">var</span> num))&#123;</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><p>升级 Tuple 写法</p><figure class="highlight cs"><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">double</span>, <span class="built_in">int</span>) t1 = (<span class="number">4.5</span>, <span class="number">3</span>);</span><br><span class="line"><span class="keyword">var</span> val1 = t1.Item1;</span><br><span class="line"><span class="keyword">var</span> val2 = t1.Item2;</span><br></pre></td></tr></table></figure><figure class="highlight cs"><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">double</span> Sum, <span class="built_in">int</span> Count) t2 = (<span class="number">4.5</span>, <span class="number">3</span>);</span><br><span class="line"><span class="keyword">var</span> val1 = t2.Sum;</span><br><span class="line"><span class="keyword">var</span> val2 = t2.Count;</span><br></pre></td></tr></table></figure><figure class="highlight cs"><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> t3 = (Sum: <span class="number">4.5</span>, Count:<span class="number">3</span>);</span><br><span class="line"><span class="keyword">var</span> val1 = t3.Sum;</span><br><span class="line"><span class="keyword">var</span> val2 = t3.Count;</span><br></pre></td></tr></table></figure><p>类型相同可赋值</p><figure class="highlight cs"><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">double</span>, <span class="built_in">int</span>) t1 = (<span class="number">4.5</span>, <span class="number">3</span>);</span><br><span class="line">(sum, distance) = t1;</span><br></pre></td></tr></table></figure><p>判断相等</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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="built_in">int</span> a, <span class="built_in">byte</span> b) left = (<span class="number">5</span>, <span class="number">10</span>);</span><br><span class="line">(<span class="built_in">long</span> a, <span class="built_in">int</span> b) right = (<span class="number">5</span>, <span class="number">10</span>);</span><br><span class="line">Console.WriteLine(left == right); <span class="comment">// true</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> left2 = (A: <span class="number">5</span>, B: <span class="number">10</span>);</span><br><span class="line"><span class="keyword">var</span> right2 = (B: <span class="number">10</span>, A: <span class="number">5</span>);</span><br><span class="line">Console.WriteLine(left2 == right2); <span class="comment">// true</span></span><br></pre></td></tr></table></figure><h3 id="模式匹配"><a href="#模式匹配" class="headerlink" title="模式匹配"></a>模式匹配</h3><p>用 <code>is</code> 和 <code>is not</code> 判断变量类型，也可以判断 null</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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> i = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">if</span> (i <span class="keyword">is</span> <span class="built_in">int</span> num)</span><br><span class="line">&#123;</span><br><span class="line">  <span class="comment">//</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>比较离散值（switch）</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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="function"><span class="keyword">public</span> <span class="built_in">string</span> <span class="title">Func</span>(<span class="params"><span class="built_in">string</span> command</span>)</span> =&gt; command <span class="keyword">switch</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;val1&quot;</span> =&gt; <span class="string">&quot;result1&quot;</span>,</span><br><span class="line">  <span class="string">&quot;val2&quot;</span> =&gt; <span class="string">&quot;result2&quot;</span>,</span><br><span class="line">  _ =&gt; <span class="keyword">throw</span> <span class="keyword">new</span> Exception()</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>关系模式（switch）</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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="function"><span class="keyword">public</span> <span class="built_in">string</span> <span class="title">Func</span>(<span class="params"><span class="built_in">int</span> num</span>)</span> =&gt; num <span class="keyword">switch</span></span><br><span class="line">&#123;</span><br><span class="line">  &lt;=<span class="number">5</span> =&gt; <span class="string">&quot;result1&quot;</span>,</span><br><span class="line">  (&gt;<span class="number">5</span> <span class="keyword">and</span> &lt;<span class="number">10</span>) =&gt; <span class="string">&quot;result2&quot;</span>,</span><br><span class="line">  <span class="number">11</span> =&gt; <span class="string">&quot;result3&quot;</span>,</span><br><span class="line">  &gt; <span class="number">11</span> =&gt; <span class="string">&quot;result4&quot;</span>,</span><br><span class="line">  _ =&gt; <span class="keyword">throw</span> <span class="keyword">new</span> Exception()</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="本地函数"><a href="#本地函数" class="headerlink" title="本地函数"></a>本地函数</h3><p>函数内部定义函数</p><h3 id="弃元"><a href="#弃元" class="headerlink" title="弃元"></a>弃元</h3><p>用 <code>_</code> 命名的变量，可重复</p><h3 id="命名参数"><a href="#命名参数" class="headerlink" title="命名参数"></a>命名参数</h3><p>默认参数可指定参数名传参</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Func</span>(<span class="params"><span class="built_in">int</span> arg1 = <span class="literal">null</span>, <span class="built_in">int</span> arg2 = <span class="literal">null</span></span>)</span></span><br><span class="line">&#123;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Func(arg2: <span class="number">2</span>);</span><br><span class="line">Func(arg2: <span class="number">2</span>, arg1: <span class="number">1</span>);</span><br></pre></td></tr></table></figure><h3 id="ref"><a href="#ref" class="headerlink" title="ref"></a>ref</h3><p>将值类型声明为引用类型</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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="built_in">int</span> number = <span class="number">6</span>;</span><br><span class="line"><span class="keyword">ref</span> <span class="built_in">int</span> n = <span class="keyword">ref</span> number;</span><br><span class="line">n = <span class="number">8</span>;</span><br><span class="line"></span><br><span class="line">Console.WriteLine(<span class="string">$&quot;number = <span class="subst">&#123;number&#125;</span>&quot;</span>); <span class="comment">// 8</span></span><br><span class="line">Console.WriteLine(<span class="string">$&quot;n = <span class="subst">&#123;n&#125;</span>&quot;</span>); <span class="comment">// 8</span></span><br></pre></td></tr></table></figure><h2 id="C-8"><a href="#C-8" class="headerlink" title="C# 8"></a>C# 8</h2><p>是专门面向 .net core 的第一个主要 C# 版本</p><h3 id="默认接口方法"><a href="#默认接口方法" class="headerlink" title="默认接口方法"></a>默认接口方法</h3><p>可以不止是约束，也可以实现完整的方法</p><h3 id="模式匹配-1"><a href="#模式匹配-1" class="headerlink" title="模式匹配"></a>模式匹配</h3><p>增强 switch 的模式匹配</p><h4 id="属性模式"><a href="#属性模式" class="headerlink" title="属性模式"></a>属性模式</h4><p>可以匹配对象中的属性</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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">public</span> <span class="keyword">class</span> <span class="title">Cls</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">public</span> <span class="built_in">string</span> Prop &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125; = <span class="literal">null</span>!;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">string</span> <span class="title">Func</span>(<span class="params">Cls obj</span>)</span> =&gt; obj <span class="keyword">switch</span></span><br><span class="line">&#123;</span><br><span class="line">  &#123; Prop: <span class="string">&quot;val1&quot;</span> &#125; =&gt; <span class="string">&quot;result1&quot;</span>,</span><br><span class="line">  &#123; Prop: <span class="string">&quot;val2&quot;</span> &#125; =&gt; <span class="string">&quot;result2&quot;</span>,</span><br><span class="line">  &#123; Prop: <span class="string">&quot;val3&quot;</span> &#125; =&gt; <span class="string">&quot;result3&quot;</span>,</span><br><span class="line">  _ =&gt; <span class="string">&quot;result4&quot;</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> v = Func(<span class="keyword">new</span> Cls()</span><br><span class="line">&#123;</span><br><span class="line">  Prop = <span class="string">&quot;val2&quot;</span></span><br><span class="line">&#125;);</span><br><span class="line">Console.WriteLine(v); <span class="comment">// result2</span></span><br></pre></td></tr></table></figure><h4 id="元组模式"><a href="#元组模式" class="headerlink" title="元组模式"></a>元组模式</h4><p>可以匹配元组</p><h4 id="位置模式"><a href="#位置模式" class="headerlink" title="位置模式"></a>位置模式</h4><p>按属性位置取，组成元组，然后匹配</p><h3 id="using-新版声明"><a href="#using-新版声明" class="headerlink" title="using 新版声明"></a>using 新版声明</h3><p>using 块如果范围为整个函数，可以省略大括号</p><h3 id="静态本地函数"><a href="#静态本地函数" class="headerlink" title="静态本地函数"></a>静态本地函数</h3><p>本地函数可以声明为静态的</p><h3 id="可处置的-ref-结构"><a href="#可处置的-ref-结构" class="headerlink" title="可处置的 ref 结构"></a>可处置的 ref 结构</h3><p>可以用 ref 声明 struct，表示此 struct 的实例对象都是引用类型</p><h3 id="可空引用类型"><a href="#可空引用类型" class="headerlink" title="可空引用类型"></a>可空引用类型</h3><p>更灵活的可空特性</p><p>通过 <code>?</code> 为字段、属性、方法参数、返回值等添加是否可为 null 的特性</p><h3 id="异步流"><a href="#异步流" class="headerlink" title="异步流"></a>异步流</h3><p>可以返回异步版本的迭代器</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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="function"><span class="keyword">async</span> IAsyncEnumerable&lt;<span class="built_in">int</span>&gt; <span class="title">GetList</span>()</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; <span class="number">100</span>; i++)</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="keyword">await</span> Task.Delay(<span class="number">100</span>);</span><br><span class="line">    <span class="keyword">yield</span> <span class="keyword">return</span> 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="keyword">await</span> <span class="keyword">foreach</span> (<span class="function"><span class="keyword">var</span> num <span class="keyword">in</span> <span class="title">GetList</span>())</span></span><br><span class="line">&#123;</span><br><span class="line">  Console.WriteLine(num); <span class="comment">// 0-99</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="异步释放"><a href="#异步释放" class="headerlink" title="异步释放"></a>异步释放</h3><p>增加 IDisposable 的异步版本 IDisposableAsync</p><p>接口函数为 <code>DisposeAsync()</code></p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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">public</span> <span class="keyword">class</span> <span class="title">Cls</span> : <span class="title">IAsyncDisposable</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="function"><span class="keyword">public</span> <span class="keyword">async</span> ValueTask <span class="title">DisposeAsync</span>()</span></span><br><span class="line">  &#123;</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><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">var</span> nums = <span class="keyword">new</span> <span class="built_in">int</span>[]&#123;</span><br><span class="line">  <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><span class="line"><span class="keyword">var</span> nums2 = nums[^<span class="number">2</span>]; <span class="comment">// 倒数</span></span><br><span class="line"><span class="keyword">var</span> nums3 = nums[<span class="number">1.</span><span class="number">.3</span>];</span><br><span class="line"><span class="keyword">var</span> nums4 = nums[^<span class="number">2.</span>.^<span class="number">0</span>]; <span class="comment">// 倒数范围</span></span><br><span class="line"><span class="keyword">var</span> nums5 = nums[..];</span><br><span class="line"><span class="keyword">var</span> nums6 = nums[.<span class="number">.3</span>]; <span class="comment">// 前三个</span></span><br><span class="line"><span class="keyword">var</span> nums7 = nums[<span class="number">2.</span>.]; <span class="comment">// 第三个开始直到结尾</span></span><br></pre></td></tr></table></figure><p>声明范围</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><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="built_in">int</span>[] nums = [<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">6</span>, <span class="number">7</span>, <span class="number">8</span>, <span class="number">9</span>];</span><br><span class="line">Range rg = <span class="number">3.</span><span class="number">.6</span>;</span><br><span class="line"><span class="built_in">int</span>[] vals = nums[rg];</span><br><span class="line">Console.WriteLine(<span class="built_in">string</span>.Join(<span class="string">&#x27;,&#x27;</span>, vals)); <span class="comment">// 4,5,6</span></span><br></pre></td></tr></table></figure><h3 id="null-合并赋值"><a href="#null-合并赋值" class="headerlink" title="null 合并赋值"></a>null 合并赋值</h3><p>语法 <code>??=</code></p><p>如果左侧不为空，直接返回左侧</p><p>如果左侧为空，则将右侧赋值给左侧，然后返回左侧</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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">// WPF 常用</span></span><br><span class="line"><span class="keyword">private</span> ICommand _okCommand = <span class="literal">null</span>;</span><br><span class="line"><span class="keyword">public</span> ICommand OkCommand =&gt; _okCommand ??= <span class="keyword">new</span> DelegateCommand(() =&gt;</span><br><span class="line">&#123;</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>可以结合 <code>$</code> 和 <code>@</code> 声明字符串，结合二者功能，声明顺序不分先后</p><figure class="highlight cs"><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> text = <span class="string">$@&quot;abc\de<span class="subst">&#123;<span class="number">12</span>&#125;</span>&quot;</span>;</span><br><span class="line">Console.WriteLine(text); <span class="comment">// abc\de12</span></span><br></pre></td></tr></table></figure><h2 id="C-9"><a href="#C-9" class="headerlink" title="C# 9"></a>C# 9</h2><p>随 .NET5 一起发布，是面向 .NET5 版本的任何程序集的默认语言版本</p><h3 id="记录类型"><a href="#记录类型" class="headerlink" title="记录类型"></a>记录类型</h3><p>用 <code>record</code> 声明的类型</p><p>是新的引用类型，相当于引用类型的 <code>struct</code></p><p>与类的区别是，<code>record</code> 可以使用基于值的相等性</p><h4 id="简单声明"><a href="#简单声明" class="headerlink" title="简单声明"></a>简单声明</h4><p>可以简单声明只读记录</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">record</span> <span class="title">Personal</span>(<span class="title">string</span> <span class="title">FirstName</span>, <span class="title">string</span> <span class="title">LastName</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> person = <span class="keyword">new</span> Personal(<span class="string">&quot;n1&quot;</span>, <span class="string">&quot;n2&quot;</span>);</span><br><span class="line">Console.WriteLine(<span class="string">$&quot;FirstName = <span class="subst">&#123;person.FirstName&#125;</span>&quot;</span>);</span><br><span class="line">Console.WriteLine(<span class="string">$&quot;LastName = <span class="subst">&#123;person.LastName&#125;</span>&quot;</span>);</span><br></pre></td></tr></table></figure><h4 id="可初始化记录"><a href="#可初始化记录" class="headerlink" title="可初始化记录"></a>可初始化记录</h4><p>也可以用 <code>init</code> 声明可初始化的记录，只能在构造函数中或初始化类时赋值</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">public</span> <span class="keyword">record</span> <span class="title">Personal</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">public</span> <span class="built_in">string</span> FirstName &#123; <span class="keyword">get</span>; <span class="keyword">init</span>; &#125; = <span class="literal">null</span>!;</span><br><span class="line">  <span class="keyword">public</span> <span class="built_in">string</span> LastName &#123; <span class="keyword">get</span>; <span class="keyword">init</span>; &#125; = <span class="literal">null</span>!;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="读写记录"><a href="#读写记录" class="headerlink" title="读写记录"></a>读写记录</h4><p>也可以声明可读写记录</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">public</span> <span class="keyword">record</span> <span class="title">Personal</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">public</span> <span class="built_in">string</span> FirstName &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125; = <span class="literal">null</span>!;</span><br><span class="line">  <span class="keyword">public</span> <span class="built_in">string</span> LastName &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125; = <span class="literal">null</span>!;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="值相等性"><a href="#值相等性" class="headerlink" title="值相等性"></a>值相等性</h4><p>同类 record 的两个实例对象，只要属性值都相同，<code>==</code> 运算符就为 <code>true</code></p><p>运行时类型必须相等，派生类型也不行</p><h4 id="非破坏性修改"><a href="#非破坏性修改" class="headerlink" title="非破坏性修改"></a>非破坏性修改</h4><p>用 <code>with</code> 基于已有记录，新建一条记录</p><figure class="highlight cs"><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">Personal person1 = <span class="keyword">new</span>(<span class="string">&quot;n1&quot;</span>, <span class="string">&quot;n2&quot;</span>);</span><br><span class="line">Personal person2 = person1 <span class="keyword">with</span> &#123; FirstName = <span class="string">&quot;John&quot;</span> &#125;;</span><br></pre></td></tr></table></figure><h4 id="ToString"><a href="#ToString" class="headerlink" title="ToString"></a>ToString</h4><p>.ToString 会格式化属性名和属性值</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">DailyTemperature &#123; HighTemp = 57, LowTemp = 30, Mean = 43.5 &#125;</span><br></pre></td></tr></table></figure><h3 id="仅限-init-的资源库"><a href="#仅限-init-的资源库" class="headerlink" title="仅限 init 的资源库"></a>仅限 init 的资源库</h3><p>用 init 替换 set 声明属性，只能在构造函数或初始化时设置属性值</p><h3 id="顶级语句"><a href="#顶级语句" class="headerlink" title="顶级语句"></a>顶级语句</h3><p>不用 Main 方法和 namespace，直接写函数体代码</p><h3 id="模式匹配增强功能"><a href="#模式匹配增强功能" class="headerlink" title="模式匹配增强功能"></a>模式匹配增强功能</h3><p>改进模式匹配</p><ul><li>类型模式匹配一个与特定类型匹配的对象</li><li>带圆括号的模式强制或强调模式组合的优先级</li><li>联合 <code>and</code> 模式要求两个模式都匹配</li><li>析取 <code>or</code> 模式要求任一模式匹配</li><li>否定 <code>not</code> 模式要求模式不匹配</li><li>关系模式要求输入小于、大于、小于等于或大于等于给定常数。</li></ul><figure class="highlight cs"><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">public</span> <span class="keyword">static</span> <span class="built_in">bool</span> <span class="title">IsLetter</span>(<span class="params"><span class="keyword">this</span> <span class="built_in">char</span> c</span>)</span> =&gt; c <span class="keyword">is</span> &gt;= <span class="string">&#x27;a&#x27;</span> <span class="keyword">and</span> &lt;= <span class="string">&#x27;z&#x27;</span> <span class="keyword">or</span> &gt;= <span class="string">&#x27;A&#x27;</span> <span class="keyword">and</span> &lt;= <span class="string">&#x27;Z&#x27;</span>;</span><br></pre></td></tr></table></figure><figure class="highlight cs"><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">public</span> <span class="keyword">static</span> <span class="built_in">bool</span> <span class="title">IsLetterOrSeparator</span>(<span class="params"><span class="keyword">this</span> <span class="built_in">char</span> c</span>)</span> =&gt; <span class="function">c <span class="title">is</span> (<span class="params">&gt;= <span class="string">&#x27;a&#x27;</span> <span class="keyword">and</span> &lt;= <span class="string">&#x27;z&#x27;</span></span>) <span class="title">or</span> (<span class="params">&gt;= <span class="string">&#x27;A&#x27;</span> <span class="keyword">and</span> &lt;= <span class="string">&#x27;Z&#x27;</span></span>) <span class="keyword">or</span> &#x27;.&#x27; <span class="keyword">or</span> &#x27;,&#x27;</span>;</span><br></pre></td></tr></table></figure><h3 id="可省略-new"><a href="#可省略-new" class="headerlink" title="可省略 new"></a>可省略 new</h3><p>已知类型 <code>new</code> 表达式中可以省略类型</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> List&lt;WeatherObservation&gt; _observations = <span class="keyword">new</span>();</span><br></pre></td></tr></table></figure><h3 id="静态匿名函数"><a href="#静态匿名函数" class="headerlink" title="静态匿名函数"></a>静态匿名函数</h3><p>允许对 lambda 和匿名方法，使用 <code>static</code> 修饰符</p><figure class="highlight cs"><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">Action act = <span class="keyword">static</span> () =&gt; &#123;</span><br><span class="line"></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="扩展-GetEnumerator"><a href="#扩展-GetEnumerator" class="headerlink" title="扩展 GetEnumerator"></a>扩展 GetEnumerator</h3><p>允许 foreach 循环识别扩展方法 <code>GetEnumerator</code></p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">public</span> <span class="keyword">class</span> <span class="title">PeopleExtensions</span>&#123;</span><br><span class="line">  <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> People <span class="title">GetEnumerator</span>(<span class="params"><span class="keyword">this</span> People people</span>)</span> =&gt; people;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> people = <span class="keyword">new</span> People();</span><br><span class="line"></span><br><span class="line"><span class="keyword">foreach</span>(<span class="keyword">var</span> person <span class="keyword">in</span> people)&#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>Current</code> 和 <code>MoveNext</code></p><h3 id="lambda-弃元参数"><a href="#lambda-弃元参数" class="headerlink" title="lambda 弃元参数"></a>lambda 弃元参数</h3><p>可以用 <code>_</code> 作为 lambda 和匿名方法的参数</p><ul><li>lambda： <code>(_, _) =&gt; 0</code> ， <code>(int _, int _) =&gt; 0</code></li><li>匿名方法： <code>delegate(int _, int _) { return 0; }</code></li></ul><h3 id="分部方法"><a href="#分部方法" class="headerlink" title="分部方法"></a>分部方法</h3><p>对于 <code>partial</code> 类，可以在一处声明，在另一处实现</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><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">partial</span> <span class="keyword">class</span> <span class="title">D</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">// Okay</span></span><br><span class="line">    <span class="function"><span class="keyword">internal</span> <span class="keyword">partial</span> <span class="built_in">bool</span> <span class="title">TryParse</span>(<span class="params"><span class="built_in">string</span> s, <span class="keyword">out</span> <span class="built_in">int</span> i</span>)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">partial</span> <span class="keyword">class</span> <span class="title">D</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">internal</span> <span class="keyword">partial</span> <span class="built_in">bool</span> <span class="title">TryParse</span>(<span class="params"><span class="built_in">string</span> s, <span class="keyword">out</span> <span class="built_in">int</span> i</span>)</span> &#123; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="C-10"><a href="#C-10" class="headerlink" title="C# 10"></a>C# 10</h2><p>.NET 6 的默认语言版本为 C# 10</p><h3 id="记录结构"><a href="#记录结构" class="headerlink" title="记录结构"></a>记录结构</h3><p>扩展之前的 record 记录</p><ul><li>用 <code>record struct</code> 声明值类型</li><li>用 <code>record class</code> 声明引用类型</li><li>用 <code>readonly record struct</code> 声明只读值类型</li></ul><h4 id="无参构造函数"><a href="#无参构造函数" class="headerlink" title="无参构造函数"></a>无参构造函数</h4><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">record</span> <span class="title">struct</span> <span class="title">Person</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">public</span> <span class="built_in">int</span> Id &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125; = <span class="number">123</span>;</span><br><span class="line">  <span class="keyword">public</span> <span class="built_in">string</span> Name &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125; = <span class="string">&quot;h&quot;</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 cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">record</span> <span class="title">struct</span> <span class="title">Person</span>(<span class="title">string</span> <span class="title">name</span>)</span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">public</span> <span class="built_in">int</span> Id &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125; = <span class="number">123</span>;</span><br><span class="line">  <span class="keyword">public</span> <span class="built_in">string</span> Name &#123; <span class="keyword">get</span>; <span class="keyword">set</span>; &#125; = name;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="全局-using"><a href="#全局-using" class="headerlink" title="全局 using"></a>全局 using</h3><p>一次引入，整个项目可用</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">global</span> <span class="keyword">using</span> System.Math;</span><br></pre></td></tr></table></figure><h3 id="文件范围命名空间"><a href="#文件范围命名空间" class="headerlink" title="文件范围命名空间"></a>文件范围命名空间</h3><p>用新方式声明 namespace 后，整个文件中的类都是属于这个命名空间</p><p>可以减少一组大括号，增加易读性</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">namespace</span> <span class="title">CustomNamespace</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Class1</span>&#123;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Class2</span>&#123;</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>模式匹配可以用嵌套的属性或字段</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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="built_in">object</span> people = <span class="keyword">new</span> People</span><br><span class="line">&#123;</span><br><span class="line">  Name = <span class="string">&quot;H&quot;</span>,</span><br><span class="line">  Contact = <span class="keyword">new</span></span><br><span class="line">  &#123;</span><br><span class="line">    Email = <span class="string">&quot;hi@hal.wang&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">if</span> (people <span class="keyword">is</span> People &#123; Contact.Email: <span class="string">&quot;hi@hal.wang&quot;</span> &#125;)</span><br><span class="line">&#123;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (people <span class="keyword">is</span> People &#123; Contact: &#123; Email: <span class="string">&quot;hi@hal.wang&quot;</span> &#125; &#125;)</span><br><span class="line">&#123;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="lambda-改进"><a href="#lambda-改进" class="headerlink" title="lambda 改进"></a>lambda 改进</h3><ul><li>Lambda 表达式可以具有自然类型，这使编译器可从 Lambda 表达式或方法组推断委托类型。</li><li>如果编译器无法推断返回类型，Lambda 表达式可以声明该类型。</li><li>特性可应用于 Lambda 表达式。</li><li>Lambda 支持关键字修饰，如 ref&#x2F;out 等</li></ul><h4 id="特性应用到函数"><a href="#特性应用到函数" class="headerlink" title="特性应用到函数"></a>特性应用到函数</h4><figure class="highlight cs"><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> fn1 = [CustomAttribute] () =&gt; &#123; &#125;;</span><br><span class="line"><span class="keyword">var</span> fn2 = [Custom] () =&gt; &#123; &#125;;</span><br></pre></td></tr></table></figure><h4 id="特性应用到参数"><a href="#特性应用到参数" class="headerlink" title="特性应用到参数"></a>特性应用到参数</h4><figure class="highlight cs"><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> concat = ([DisallowNull] <span class="built_in">string</span> a, [DisallowNull] <span class="built_in">string</span> b) =&gt; a + b;</span><br></pre></td></tr></table></figure><h4 id="特性应用到返回值"><a href="#特性应用到返回值" class="headerlink" title="特性应用到返回值"></a>特性应用到返回值</h4><figure class="highlight cs"><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> inc = [<span class="keyword">return</span>: NotNullifNotNull(<span class="keyword">nameof</span>(s))] (<span class="built_in">int</span>? s) =&gt; s.HasValue ? s++ : <span class="literal">null</span>;</span><br></pre></td></tr></table></figure><h3 id="常量内插字符串"><a href="#常量内插字符串" class="headerlink" title="常量内插字符串"></a>常量内插字符串</h3><p>如果内插变量都是 const 修饰的，那么内插字符串也可以用 const 修饰</p><figure class="highlight cs"><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> <span class="built_in">string</span> str1 = <span class="string">&quot;s1&quot;</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="built_in">string</span> str2 = <span class="string">&quot;s2&quot;</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="built_in">string</span> str = <span class="string">$&quot;abc<span class="subst">&#123;str1&#125;</span>def<span class="subst">&#123;str2&#125;</span>g&quot;</span>;</span><br></pre></td></tr></table></figure><h3 id="记录类型可密封-ToString"><a href="#记录类型可密封-ToString" class="headerlink" title="记录类型可密封 ToString"></a>记录类型可密封 ToString</h3><p>用 <code>sealed</code> 修饰 <code>ToString</code>，子类不能再覆写 <code>ToString</code></p><h2 id="C-11"><a href="#C-11" class="headerlink" title="C# 11"></a>C# 11</h2><p>.NET 7 的默认语言版本为 C# 11</p><h3 id="泛型特性"><a href="#泛型特性" class="headerlink" title="泛型特性"></a>泛型特性</h3><p>可创建泛型类并继承 <code>Attribute</code>，实现泛型特性</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">GenericAttribute</span>&lt;<span class="title">T</span>&gt; : <span class="title">Attribute</span> &#123; &#125;</span><br></pre></td></tr></table></figure><figure class="highlight cs"><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">GenericAttribute&lt;string&gt;()</span>]</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="built_in">string</span> <span class="title">Method</span>()</span> =&gt; <span class="literal">default</span>;</span><br></pre></td></tr></table></figure><p>泛型类型必须完全构造，不能有任何参数</p><ul><li>不能是 dynamic，可以是 object</li><li>不能是 <code>string?</code>&#x2F;<code>int?</code> 等可空引用类型，应使用 string&#x2F;int 等</li><li>不能用元组如 <code>(int X, int Y)</code>，可使用 <code>ValueTuple&lt;int, int&gt;</code></li></ul><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">public</span> <span class="keyword">class</span> <span class="title">GenericType</span>&lt;<span class="title">T</span>&gt;</span></span><br><span class="line">&#123;</span><br><span class="line">   [<span class="meta">GenericAttribute&lt;T&gt;()</span>] <span class="comment">// Not allowed! generic attributes must be fully constructed types.</span></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="built_in">string</span> <span class="title">Method</span>()</span> =&gt; <span class="literal">default</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>{ }</code>, 允许换行</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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> str = <span class="string">$&quot;abc<span class="subst">&#123;</span></span></span><br><span class="line"><span class="subst"><span class="string">    <span class="number">1</span>+<span class="number">2</span></span></span></span><br><span class="line"><span class="subst"><span class="string">    +<span class="number">3</span></span></span></span><br><span class="line"><span class="subst"><span class="string">    +<span class="number">4</span></span></span></span><br><span class="line"><span class="subst"><span class="string">&#125;</span>def&quot;</span>;</span><br><span class="line">Console.WriteLine(str); <span class="comment">// abc10def</span></span><br></pre></td></tr></table></figure><h3 id="列表的模式匹配"><a href="#列表的模式匹配" class="headerlink" title="列表的模式匹配"></a>列表的模式匹配</h3><p>可以将数组或列表与模式的序列进行匹配</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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="built_in">int</span>[] numbers = &#123; <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span> &#125;;</span><br><span class="line"></span><br><span class="line">Console.WriteLine(numbers <span class="keyword">is</span> [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]);  <span class="comment">// True</span></span><br><span class="line">Console.WriteLine(numbers <span class="keyword">is</span> [<span class="number">1</span>, <span class="number">2</span>, <span class="number">4</span>]);  <span class="comment">// False</span></span><br><span class="line">Console.WriteLine(numbers <span class="keyword">is</span> [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>]);  <span class="comment">// False</span></span><br><span class="line">Console.WriteLine(numbers <span class="keyword">is</span> [<span class="number">0</span> <span class="keyword">or</span> <span class="number">1</span>, &lt;= <span class="number">2</span>, &gt;= <span class="number">3</span>]);  <span class="comment">// True</span></span><br></pre></td></tr></table></figure><h4 id="弃元-1"><a href="#弃元-1" class="headerlink" title="弃元"></a>弃元</h4><p>弃元可以匹配任何值</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><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="built_in">int</span>[] numbers = &#123; <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span> &#125;;</span><br><span class="line"></span><br><span class="line">Console.WriteLine(numbers <span class="keyword">is</span> [<span class="number">1</span>, <span class="number">2</span>, _]);  <span class="comment">// True</span></span><br><span class="line">Console.WriteLine(numbers <span class="keyword">is</span> [<span class="number">1</span>, _, _]);  <span class="comment">// True</span></span><br></pre></td></tr></table></figure><h4 id="取值"><a href="#取值" class="headerlink" title="取值"></a>取值</h4><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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">List&lt;<span class="built_in">int</span>&gt; numbers = <span class="keyword">new</span>() &#123; <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span> &#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (numbers <span class="keyword">is</span> [<span class="keyword">var</span> first, _, _])</span><br><span class="line">&#123;</span><br><span class="line">    Console.WriteLine(<span class="string">$&quot;The first element of a three-item list is <span class="subst">&#123;first&#125;</span>.&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="切片"><a href="#切片" class="headerlink" title="切片"></a>切片</h4><p>在模式匹配中可使用切片模式</p><p>切片可匹配 0 个或多个元素，最多出现一个切片</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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">Console.WriteLine(<span class="keyword">new</span>[] &#123; <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> &#125; <span class="keyword">is</span> [&gt; <span class="number">0</span>, &gt; <span class="number">0</span>, ..]);  <span class="comment">// True</span></span><br><span class="line">Console.WriteLine(<span class="keyword">new</span>[] &#123; <span class="number">1</span>, <span class="number">1</span> &#125; <span class="keyword">is</span> [_, _, ..]);  <span class="comment">// True</span></span><br><span class="line">Console.WriteLine(<span class="keyword">new</span>[] &#123; <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> &#125; <span class="keyword">is</span> [&gt; <span class="number">0</span>, &gt; <span class="number">0</span>, ..]);  <span class="comment">// False</span></span><br><span class="line">Console.WriteLine(<span class="keyword">new</span>[] &#123; <span class="number">1</span> &#125; <span class="keyword">is</span> [<span class="number">1</span>, <span class="number">2</span>, ..]);  <span class="comment">// False</span></span><br><span class="line"></span><br><span class="line">Console.WriteLine(<span class="keyword">new</span>[] &#123; <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span> &#125; <span class="keyword">is</span> [.., &gt; <span class="number">0</span>, &gt; <span class="number">0</span>]);  <span class="comment">// True</span></span><br><span class="line">Console.WriteLine(<span class="keyword">new</span>[] &#123; <span class="number">2</span>, <span class="number">4</span> &#125; <span class="keyword">is</span> [.., &gt; <span class="number">0</span>, <span class="number">2</span>, <span class="number">4</span>]);  <span class="comment">// False</span></span><br><span class="line">Console.WriteLine(<span class="keyword">new</span>[] &#123; <span class="number">2</span>, <span class="number">4</span> &#125; <span class="keyword">is</span> [.., <span class="number">2</span>, <span class="number">4</span>]);  <span class="comment">// True</span></span><br><span class="line"></span><br><span class="line">Console.WriteLine(<span class="keyword">new</span>[] &#123; <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span> &#125; <span class="keyword">is</span> [&gt;= <span class="number">0</span>, .., <span class="number">2</span> <span class="keyword">or</span> <span class="number">4</span>]);  <span class="comment">// True</span></span><br><span class="line">Console.WriteLine(<span class="keyword">new</span>[] &#123; <span class="number">1</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">1</span> &#125; <span class="keyword">is</span> [<span class="number">1</span>, <span class="number">0</span>, .., <span class="number">0</span>, <span class="number">1</span>]);  <span class="comment">// True</span></span><br><span class="line">Console.WriteLine(<span class="keyword">new</span>[] &#123; <span class="number">1</span>, <span class="number">0</span>, <span class="number">1</span> &#125; <span class="keyword">is</span> [<span class="number">1</span>, <span class="number">0</span>, .., <span class="number">0</span>, <span class="number">1</span>]);  <span class="comment">// False</span></span><br></pre></td></tr></table></figure><h3 id="原始字符串文本"><a href="#原始字符串文本" class="headerlink" title="原始字符串文本"></a>原始字符串文本</h3><p>用 3 个双引号 <code>&quot;&quot;&quot;</code> 开头和结尾</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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="built_in">string</span> longMessage = <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    This is a long message.</span></span><br><span class="line"><span class="string">    It has several lines.</span></span><br><span class="line"><span class="string">        Some are indented</span></span><br><span class="line"><span class="string">                more than others.</span></span><br><span class="line"><span class="string">    Some should start at the first column.</span></span><br><span class="line"><span class="string">    Some have &quot;quoted text&quot; in them.</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> location = $<span class="string">$&quot;&quot;</span><span class="string">&quot;</span></span><br><span class="line"><span class="string">   You are at &#123;&#123;&#123;Longitude&#125;&#125;, &#123;&#123;Latitude&#125;&#125;&#125;</span></span><br><span class="line"><span class="string">   &quot;</span><span class="string">&quot;&quot;</span>;</span><br></pre></td></tr></table></figure><h3 id="UTF-8-字符串字面量"><a href="#UTF-8-字符串字面量" class="headerlink" title="UTF-8 字符串字面量"></a>UTF-8 字符串字面量</h3><p>用 <code>u8</code> 后缀修饰字符串字面量，可指定 UTF-8 编码</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><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">ReadOnlySpan&lt;<span class="built_in">byte</span>&gt; AuthWithTrailingSpace = <span class="keyword">new</span> <span class="built_in">byte</span>[] &#123; <span class="number">0x41</span>, <span class="number">0x55</span>, <span class="number">0x54</span>, <span class="number">0x48</span>, <span class="number">0x20</span> &#125;;</span><br><span class="line">ReadOnlySpan&lt;<span class="built_in">byte</span>&gt; AuthStringLiteral = <span class="string">&quot;AUTH &quot;</span>u8;</span><br><span class="line"></span><br><span class="line"><span class="built_in">byte</span>[] AuthStringLiteral = <span class="string">&quot;AUTH &quot;</span>u8.ToArray();</span><br></pre></td></tr></table></figure><h3 id="必须的成员"><a href="#必须的成员" class="headerlink" title="必须的成员"></a>必须的成员</h3><p>用 <code>required</code> 修饰属性，要求必须初始化该属性</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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">public</span> <span class="keyword">class</span> <span class="title">Person</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">required</span> <span class="built_in">string</span> FirstName &#123; <span class="keyword">get</span>; <span class="keyword">init</span>; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> person = <span class="keyword">new</span> Person &#123; FirstName = <span class="string">&quot;John&quot;</span>&#125;;</span><br></pre></td></tr></table></figure><p>如果用构造函数初始化，需要用 <code>SetsRequiredMembers</code> 特性修饰构造函数</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><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">public</span> <span class="keyword">class</span> <span class="title">Person</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">Person</span>()</span> &#123; &#125;</span><br><span class="line"></span><br><span class="line">    [<span class="meta">SetsRequiredMembers</span>]</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">Person</span>(<span class="params"><span class="built_in">string</span> firstName</span>)</span> =&gt; FirstName = firstName;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">required</span> <span class="built_in">string</span> FirstName &#123; <span class="keyword">get</span>; <span class="keyword">init</span>; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> person = <span class="keyword">new</span> Person(<span class="string">&quot;John&quot;</span>);</span><br></pre></td></tr></table></figure><h3 id="文件本地类型"><a href="#文件本地类型" class="headerlink" title="文件本地类型"></a>文件本地类型</h3><p>用 <code>file</code> 修饰类型，限制该类型的可见性为文件内</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><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">file</span> <span class="keyword">class</span> <span class="title">HiddenWidget</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">// implementation</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在其他代码文件的任何命名空间中，即使在部分类中，都无法访问该类型</p><h2 id="C-12"><a href="#C-12" class="headerlink" title="C# 12"></a>C# 12</h2><p>.NET 8 的默认语言版本为 C# 12</p><h3 id="主构造函数"><a href="#主构造函数" class="headerlink" title="主构造函数"></a>主构造函数</h3><p>不再局限于 <code>record</code> 类型，也可以在 <code>class</code> 和 <code>struct</code> 中使用主构造函数</p><figure class="highlight cs"><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">public</span> <span class="keyword">class</span> <span class="title">ExampleController</span>(<span class="params">IService service</span>) : ControllerBase</span></span><br><span class="line">&#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="集合的展开运算符"><a href="#集合的展开运算符" class="headerlink" title="集合的展开运算符"></a>集合的展开运算符</h3><p>可以使用展开运算符 <code>..</code> 将集合中的元素与其他集合内敛</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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="built_in">int</span>[] arr1 = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="built_in">int</span>[] arr2 = [.. arr1, <span class="number">4</span>, <span class="number">5</span>];</span><br><span class="line"></span><br><span class="line">Console.WriteLine(<span class="built_in">string</span>.Join(<span class="string">&#x27;,&#x27;</span>, arr2));</span><br><span class="line"><span class="comment">// 1,2,3,4,5</span></span><br></pre></td></tr></table></figure><p>类似 <code>js</code> 中对数组操作的展开运算符 <code>...</code></p><h3 id="默认-Lambda-参数"><a href="#默认-Lambda-参数" class="headerlink" title="默认 Lambda 参数"></a>默认 Lambda 参数</h3><p>可以为 Lambda 表达式的参数定义默认值，与普通方法相同</p><h3 id="类型别名"><a href="#类型别名" class="headerlink" title="类型别名"></a>类型别名</h3><p>可以用 <code>using</code> 指令为任意类型创建别名，类似 <code>ts</code> 中的 <code>type</code></p><figure class="highlight cs"><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">using</span> CustomType = (<span class="built_in">int</span> X, <span class="built_in">int</span> Y);</span><br><span class="line"><span class="keyword">var</span> val = <span class="keyword">new</span> CustomType(<span class="number">1</span>, <span class="number">2</span>);</span><br><span class="line">Console.WriteLine(val.X + val.Y); <span class="comment">// 3</span></span><br></pre></td></tr></table></figure><figure class="highlight cs"><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">using</span> CustomList = System.Collections.Generic.List&lt;<span class="built_in">int</span>&gt;;</span><br><span class="line"><span class="keyword">var</span> val = <span class="keyword">new</span> CustomList() &#123; <span class="number">1</span>, <span class="number">2</span> &#125;;</span><br><span class="line">Console.WriteLine(val[<span class="number">0</span>] + val[<span class="number">1</span>]); <span class="comment">// 3</span></span><br></pre></td></tr></table></figure><h3 id="内联数组"><a href="#内联数组" class="headerlink" title="内联数组"></a>内联数组</h3><p>内联数组是包含多个元素的连续块的结构，适用于高性能场景</p><ul><li>内联数组是 struct</li><li>struct 仅有 1 个字段</li><li>struct 未指定显式布局</li><li>使用 <code>System.Runtime.CompilerServices.InlineArrayAttribute</code> 特性修饰 struct</li><li><code>System.Runtime.CompilerServices.InlineArrayAttribute</code> 参数必须大于 0</li></ul><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">System.Runtime.CompilerServices.InlineArray(10)</span>]</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">struct</span> Buffer</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="built_in">int</span> _element0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><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> buffer = <span class="keyword">new</span> Buffer();</span><br><span class="line"><span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++)</span><br><span class="line">&#123;</span><br><span class="line">    buffer[i] = i;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">foreach</span> (<span class="keyword">var</span> i <span class="keyword">in</span> buffer)</span><br><span class="line">&#123;</span><br><span class="line">    Console.WriteLine(i);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;仅记录 C# 5 及以后版本&lt;/p&gt;</summary>
    
    
    
    <category term="速查" scheme="https://blog.hal.wang/categories/%E9%80%9F%E6%9F%A5/"/>
    
    
    <category term="C#" scheme="https://blog.hal.wang/tags/C/"/>
    
  </entry>
  
  <entry>
    <title>Three.js 创建地图飞线</title>
    <link href="https://blog.hal.wang/383ffbdc/"/>
    <id>https://blog.hal.wang/383ffbdc/</id>
    <published>2023-03-21T23:06:53.000Z</published>
    <updated>2026-03-18T15:50:10.582Z</updated>
    
    <content type="html"><![CDATA[<p>使用 Three.js 在地图中创建有动画的飞线</p><p>能够根据输入坐标，创建飞线</p><p>示例代码均为 ts，效果如图</p><span id="more"></span><p><img src="./line.png" alt="line"></p><h2 id="创建渲染类"><a href="#创建渲染类" class="headerlink" title="创建渲染类"></a>创建渲染类</h2><p>用于封装飞线相关内容，完成后可方便被调用，最后面有完成的完整代码</p><p>构造函数接收 Scene 对象，用于添加模型</p><p>包含两个 public 方法</p><ul><li>render 传入点坐标，生成模型，仅需调用一次</li><li>animation 在 Three.js 的动画中调用，用于渲染动画，会被多次调用</li></ul><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> * <span class="keyword">as</span> <span class="variable constant_">THREE</span> <span class="keyword">from</span> <span class="string">&#x27;three&#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_">LineRender</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"><span class="keyword">private</span> <span class="keyword">readonly</span> <span class="attr">scene</span>: THREE.<span class="title class_">Scene</span></span>) &#123;&#125;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"><span class="attr">poses</span>: [[<span class="built_in">number</span>, <span class="built_in">number</span>], [<span class="built_in">number</span>, <span class="built_in">number</span>]][]</span>) &#123;&#125;</span><br><span class="line">  <span class="title function_">animation</span>(<span class="params"></span>) &#123;&#125;</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="经纬度转-Three-js-坐标"><a href="#经纬度转-Three-js-坐标" class="headerlink" title="经纬度转 Three.js 坐标"></a>经纬度转 Three.js 坐标</h3><p>使用 <code>d3</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> * <span class="keyword">as</span> d3 <span class="keyword">from</span> <span class="string">&#x27;d3&#x27;</span>;</span><br><span class="line"></span><br><span class="line">d3.<span class="title function_">geoMercator</span>().<span class="title function_">center</span>([<span class="number">104.0</span>, <span class="number">37.5</span>]).<span class="title function_">translate</span>([<span class="number">0</span>, <span class="number">0</span>])(lon, lat)</span><br></pre></td></tr></table></figure><p><code>center()</code> 函数传入地图中心经纬度</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">LineRender</span> &#123;</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">readonly</span> projection = d3.<span class="title function_">geoMercator</span>().<span class="title function_">center</span>([<span class="number">104.0</span>, <span class="number">37.5</span>]).<span class="title function_">translate</span>([<span class="number">0</span>, <span class="number">0</span>]);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>此后使用方式如 <code>this.projection(lon, lat)</code></p><h3 id="解决-LineBasicMaterial-线宽无效"><a href="#解决-LineBasicMaterial-线宽无效" class="headerlink" title="解决 LineBasicMaterial 线宽无效"></a>解决 LineBasicMaterial 线宽无效</h3><p>根据官方解释，由于 opengl 的限制，webgl 渲染器在大部分平台上会无视 lineWidth</p><blockquote><p>Due to limitations of the OpenGL Core Profile with the WebGL renderer on most platforms linewidth will always be 1 regardless of the set value.</p></blockquote><p>我们改为使用 Line2</p><p>首先导入相关包并创建模型，使用 Line2 的方式如下</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">import</span> &#123; <span class="title class_">Line2</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;three/examples/jsm/lines/Line2&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">LineGeometry</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;three/examples/jsm/lines/LineGeometry&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">LineMaterial</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;three/examples/jsm/lines/LineMaterial&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> lineGeometry = <span class="keyword">new</span> <span class="title class_">LineGeometry</span>();</span><br><span class="line">lineGeometry.<span class="title function_">setPositions</span>(points); <span class="comment">// points 为各点数组，后面会有生成</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> material = <span class="keyword">new</span> <span class="title class_">LineMaterial</span>(&#123;</span><br><span class="line">  <span class="attr">color</span>: <span class="number">0xf44336</span>,</span><br><span class="line">  <span class="attr">linewidth</span>: <span class="number">2</span>,</span><br><span class="line">  <span class="attr">side</span>: <span class="variable constant_">THREE</span>.<span class="property">DoubleSide</span>,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> line = <span class="keyword">new</span> <span class="title class_">Line2</span>(lineGeometry, material);</span><br><span class="line"><span class="variable language_">this</span>.<span class="property">scene</span>.<span class="title function_">add</span>(line);</span><br></pre></td></tr></table></figure><h3 id="二次贝塞尔曲线"><a href="#二次贝塞尔曲线" class="headerlink" title="二次贝塞尔曲线"></a>二次贝塞尔曲线</h3><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><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">export</span> <span class="keyword">class</span> <span class="title class_">LineRender</span> &#123;</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">readonly</span> <span class="attr">lines</span>: <span class="title class_">Line2</span>[] = [];</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> <span class="title function_">lineConnect</span>(<span class="params"><span class="attr">posStartX</span>: <span class="built_in">number</span>, <span class="attr">posStartY</span>: <span class="built_in">number</span>, <span class="attr">posEndX</span>: <span class="built_in">number</span>, <span class="attr">posEndY</span>: <span class="built_in">number</span></span>) &#123;</span><br><span class="line">    <span class="comment">// 根据目标坐标设置3D坐标  z轴位置在地图表面</span></span><br><span class="line">    <span class="keyword">const</span> [x0, y0, z0] = [posStartX, posStartY, <span class="number">0</span>];</span><br><span class="line">    <span class="keyword">const</span> [x1, y1, z1] = [posEndX, posEndY, <span class="number">0</span>];</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 使用QuadraticBezierCurve3() 创建 三维二次贝塞尔曲线</span></span><br><span class="line">    <span class="keyword">const</span> curve = <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">QuadraticBezierCurve3</span>(</span><br><span class="line">      <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">Vector3</span>(x0, -y0, z0),</span><br><span class="line">      <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">Vector3</span>((x0 + x1) / <span class="number">2</span>, -(y0 + y1) / <span class="number">2</span>, <span class="number">20</span>),</span><br><span class="line">      <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">Vector3</span>(x1, -y1, z1),</span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取曲线 上的50个点</span></span><br><span class="line">    <span class="keyword">const</span> points = curve.<span class="title function_">getPoints</span>(<span class="number">50</span>).<span class="title function_">reduce</span>(<span class="function">(<span class="params">arr, cur</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> arr.<span class="title function_">concat</span>(cur.<span class="property">x</span>, cur.<span class="property">y</span>, cur.<span class="property">z</span>);</span><br><span class="line">    &#125;, [] <span class="keyword">as</span> <span class="built_in">number</span>[]);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> lineGeometry = <span class="keyword">new</span> <span class="title class_">LineGeometry</span>();</span><br><span class="line">    lineGeometry.<span class="title function_">setPositions</span>(points);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> material = <span class="keyword">new</span> <span class="title class_">LineMaterial</span>(&#123;</span><br><span class="line">      <span class="attr">color</span>: <span class="number">0xf44336</span>,</span><br><span class="line">      <span class="attr">linewidth</span>: <span class="number">2</span>,</span><br><span class="line">      <span class="attr">side</span>: <span class="variable constant_">THREE</span>.<span class="property">DoubleSide</span>,</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    material.<span class="property">resolution</span>.<span class="title function_">set</span>(<span class="variable language_">window</span>.<span class="property">innerWidth</span>, <span class="variable language_">window</span>.<span class="property">innerHeight</span>);</span><br><span class="line">    <span class="keyword">const</span> line = <span class="keyword">new</span> <span class="title class_">Line2</span>(lineGeometry, material);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">lines</span>.<span class="title function_">push</span>(line);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">scene</span>.<span class="title function_">add</span>(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><p>有两种容易实现的方式让线动起来</p><ul><li>再创建一个线，通过在动画函数中修改坐标点来实现移动效果</li><li>设置飞线的渐变色，并通过改变渐变色的方式让线看起来在移动</li></ul><p>这里选的实现方式二</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><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">export</span> <span class="keyword">class</span> <span class="title class_">LineRender</span> &#123;</span><br><span class="line">  <span class="comment">// 颜色插值</span></span><br><span class="line">  <span class="keyword">private</span> <span class="title function_">gradientColors</span>(<span class="params"><span class="attr">start</span>: <span class="built_in">string</span>, <span class="attr">end</span>: <span class="built_in">string</span>, <span class="attr">steps</span>: <span class="built_in">number</span>, gamma = <span class="number">1</span></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">parseColor</span> = (<span class="params"><span class="attr">hexStr</span>: <span class="built_in">string</span></span>) =&gt; &#123;</span><br><span class="line">      <span class="keyword">return</span> hexStr.<span class="property">length</span> === <span class="number">4</span></span><br><span class="line">        ? hexStr</span><br><span class="line">            .<span class="title function_">substr</span>(<span class="number">1</span>)</span><br><span class="line">            .<span class="title function_">split</span>(<span class="string">&#x27;&#x27;</span>)</span><br><span class="line">            .<span class="title function_">map</span>(<span class="keyword">function</span> (<span class="params">s</span>) &#123;</span><br><span class="line">              <span class="keyword">return</span> <span class="number">0x11</span> * <span class="built_in">parseInt</span>(s, <span class="number">16</span>);</span><br><span class="line">            &#125;)</span><br><span class="line">        : [hexStr.<span class="title function_">substr</span>(<span class="number">1</span>, <span class="number">2</span>), hexStr.<span class="title function_">substr</span>(<span class="number">3</span>, <span class="number">2</span>), hexStr.<span class="title function_">substr</span>(<span class="number">5</span>, <span class="number">2</span>)].<span class="title function_">map</span>(<span class="keyword">function</span> (<span class="params">s</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">parseInt</span>(s, <span class="number">16</span>);</span><br><span class="line">          &#125;);</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="keyword">const</span> pad = <span class="keyword">function</span> (<span class="params">s</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> s.<span class="property">length</span> === <span class="number">1</span> ? <span class="string">`0<span class="subst">$&#123;s&#125;</span>`</span> : s;</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="keyword">let</span> j;</span><br><span class="line">    <span class="keyword">let</span> ms;</span><br><span class="line">    <span class="keyword">let</span> me;</span><br><span class="line">    <span class="keyword">const</span> <span class="attr">output</span>: <span class="built_in">string</span>[] = [];</span><br><span class="line">    <span class="keyword">const</span> <span class="attr">so</span>: <span class="built_in">string</span>[] = [];</span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">normalize</span> = (<span class="params"><span class="attr">channel</span>: <span class="built_in">number</span></span>) =&gt; &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="title class_">Math</span>.<span class="title function_">pow</span>(channel / <span class="number">255</span>, gamma);</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="keyword">const</span> startNum = <span class="title function_">parseColor</span>(start).<span class="title function_">map</span>(normalize);</span><br><span class="line">    <span class="keyword">const</span> endNum = <span class="title function_">parseColor</span>(end).<span class="title function_">map</span>(normalize);</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; steps; i++) &#123;</span><br><span class="line">      ms = i / (steps - <span class="number">1</span>);</span><br><span class="line">      me = <span class="number">1</span> - ms;</span><br><span class="line">      <span class="keyword">for</span> (j = <span class="number">0</span>; j &lt; <span class="number">3</span>; j++) &#123;</span><br><span class="line">        so[j] = <span class="title function_">pad</span>(</span><br><span class="line">          <span class="title class_">Math</span>.<span class="title function_">round</span>(<span class="title class_">Math</span>.<span class="title function_">pow</span>(startNum[j] * me + endNum[j] * ms, <span class="number">1</span> / gamma) * <span class="number">255</span>).<span class="title function_">toString</span>(<span class="number">16</span>),</span><br><span class="line">        );</span><br><span class="line">      &#125;</span><br><span class="line">      output.<span class="title function_">push</span>(<span class="string">`#<span class="subst">$&#123;so.join(<span class="string">&#x27;&#x27;</span>)&#125;</span>`</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> output;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">LineRender</span> &#123;</span><br><span class="line">  <span class="keyword">private</span> <span class="title function_">lineConnect</span>(<span class="params"><span class="attr">posStartX</span>: <span class="built_in">number</span>, <span class="attr">posStartY</span>: <span class="built_in">number</span>, <span class="attr">posEndX</span>: <span class="built_in">number</span>, <span class="attr">posEndY</span>: <span class="built_in">number</span></span>) &#123;</span><br><span class="line">    <span class="comment">// 根据目标坐标设置3D坐标  z轴位置在地图表面</span></span><br><span class="line">    <span class="keyword">const</span> [x0, y0, z0] = [posStartX, posStartY, <span class="number">0</span>];</span><br><span class="line">    <span class="keyword">const</span> [x1, y1, z1] = [posEndX, posEndY, <span class="number">0</span>];</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 使用QuadraticBezierCurve3() 创建 三维二次贝塞尔曲线</span></span><br><span class="line">    <span class="keyword">const</span> curve = <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">QuadraticBezierCurve3</span>(</span><br><span class="line">      <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">Vector3</span>(x0, -y0, z0),</span><br><span class="line">      <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">Vector3</span>((x0 + x1) / <span class="number">2</span>, -(y0 + y1) / <span class="number">2</span>, <span class="number">20</span>),</span><br><span class="line">      <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">Vector3</span>(x1, -y1, z1),</span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取曲线 上的50个点</span></span><br><span class="line">    <span class="keyword">const</span> points = curve.<span class="title function_">getPoints</span>(<span class="number">50</span>).<span class="title function_">reduce</span>(<span class="function">(<span class="params">arr, cur</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> arr.<span class="title function_">concat</span>(cur.<span class="property">x</span>, cur.<span class="property">y</span>, cur.<span class="property">z</span>);</span><br><span class="line">    &#125;, [] <span class="keyword">as</span> <span class="built_in">number</span>[]);</span><br><span class="line">    <span class="keyword">const</span> lineGeometry = <span class="keyword">new</span> <span class="title class_">LineGeometry</span>();</span><br><span class="line"></span><br><span class="line">    lineGeometry.<span class="title function_">setPositions</span>(points);</span><br><span class="line">    <span class="keyword">const</span> colors = [</span><br><span class="line">      ...<span class="variable language_">this</span>.<span class="title function_">gradientColors</span>(<span class="string">&#x27;#00ffff&#x27;</span>, <span class="string">&#x27;#f44336&#x27;</span>, points.<span class="property">length</span> / <span class="number">3</span> / <span class="number">2</span>),</span><br><span class="line">      ...<span class="variable language_">this</span>.<span class="title function_">gradientColors</span>(<span class="string">&#x27;#f44336&#x27;</span>, <span class="string">&#x27;#f44336&#x27;</span>, points.<span class="property">length</span> / <span class="number">3</span> / <span class="number">2</span>),</span><br><span class="line">    ].<span class="title function_">reverse</span>();</span><br><span class="line">    <span class="keyword">const</span> colorArr = colors.<span class="title function_">reduce</span>(<span class="function">(<span class="params"><span class="attr">arr</span>: <span class="built_in">number</span>[], item</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> <span class="title class_">Tcolor</span> = <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">Color</span>(item);</span><br><span class="line">      <span class="keyword">return</span> arr.<span class="title function_">concat</span>(<span class="title class_">Tcolor</span>.<span class="property">r</span>, <span class="title class_">Tcolor</span>.<span class="property">g</span>, <span class="title class_">Tcolor</span>.<span class="property">b</span>);</span><br><span class="line">    &#125;, []);</span><br><span class="line">    lineGeometry.<span class="title function_">setColors</span>(colorArr);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> material = <span class="keyword">new</span> <span class="title class_">LineMaterial</span>(&#123;</span><br><span class="line">      <span class="comment">// color: 0xf44336,</span></span><br><span class="line">      <span class="attr">vertexColors</span>: <span class="literal">true</span>,</span><br><span class="line">      <span class="attr">linewidth</span>: <span class="number">2</span>,</span><br><span class="line">      <span class="attr">transparent</span>: <span class="literal">true</span>,</span><br><span class="line">      <span class="attr">side</span>: <span class="variable constant_">THREE</span>.<span class="property">DoubleSide</span>,</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    material.<span class="property">resolution</span>.<span class="title function_">set</span>(<span class="variable language_">window</span>.<span class="property">innerWidth</span>, <span class="variable language_">window</span>.<span class="property">innerHeight</span>);</span><br><span class="line">    <span class="keyword">const</span> line = <span class="keyword">new</span> <span class="title class_">Line2</span>(lineGeometry, material);</span><br><span class="line">    line[<span class="string">&#x27;_colors&#x27;</span>] = colorArr;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">lines</span>.<span class="title function_">push</span>(line);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">scene</span>.<span class="title function_">add</span>(line);</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">private</span> <span class="title function_">gradientColors</span>(<span class="params"><span class="attr">start</span>: <span class="built_in">string</span>, <span class="attr">end</span>: <span class="built_in">string</span>, <span class="attr">steps</span>: <span class="built_in">number</span>, gamma = <span class="number">1</span></span>)&#123;&#125;</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><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> <span class="variable constant_">THREE</span> <span class="keyword">from</span> <span class="string">&#x27;three&#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_">LineRender</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"><span class="keyword">private</span> <span class="keyword">readonly</span> <span class="attr">scene</span>: THREE.<span class="title class_">Scene</span></span>) &#123;&#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">readonly</span> <span class="attr">lines</span>: <span class="title class_">Line2</span>[] = [];</span><br><span class="line"></span><br><span class="line">  <span class="title function_">render</span>(<span class="params"><span class="attr">poses</span>: [[<span class="built_in">number</span>, <span class="built_in">number</span>], [<span class="built_in">number</span>, <span class="built_in">number</span>]][]</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">lines</span>.<span class="title function_">splice</span>(<span class="number">0</span>);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">addLines</span>(poses);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">animation</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">arclineAnimate</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_">arclineAnimate</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> line <span class="keyword">of</span> <span class="variable language_">this</span>.<span class="property">lines</span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (!line[<span class="string">&#x27;_tick&#x27;</span>]) line[<span class="string">&#x27;_tick&#x27;</span>] = <span class="number">0</span>;</span><br><span class="line">      line[<span class="string">&#x27;_tick&#x27;</span>] = (line[<span class="string">&#x27;_tick&#x27;</span>] + <span class="number">1</span>) % <span class="number">4</span>;</span><br><span class="line">      <span class="keyword">if</span> (line[<span class="string">&#x27;_tick&#x27;</span>] &gt; <span class="number">1</span>) <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">const</span> <span class="attr">colors</span>: <span class="built_in">any</span>[] = line[<span class="string">&#x27;_colors&#x27;</span>];</span><br><span class="line">      colors.<span class="title function_">splice</span>(<span class="number">0</span>, <span class="number">0</span>, ...colors.<span class="title function_">splice</span>(colors.<span class="property">length</span> - <span class="number">3</span>, <span class="number">3</span>));</span><br><span class="line"></span><br><span class="line">      line.<span class="property">geometry</span>.<span class="title function_">setColors</span>(colors);</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">private</span> <span class="title function_">addLines</span>(<span class="params"><span class="attr">poses</span>: [[<span class="built_in">number</span>, <span class="built_in">number</span>], [<span class="built_in">number</span>, <span class="built_in">number</span>]][]</span>) &#123;</span><br><span class="line">    poses.<span class="title function_">forEach</span>(<span class="function">(<span class="params">item</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> start = <span class="variable language_">this</span>.<span class="title function_">projection</span>(item[<span class="number">0</span>]);</span><br><span class="line">      <span class="keyword">const</span> end = <span class="variable language_">this</span>.<span class="title function_">projection</span>(item[<span class="number">1</span>]);</span><br><span class="line">      <span class="keyword">if</span> (start &amp;&amp; end) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="title function_">lineConnect</span>(...start, ...end);</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">private</span> <span class="title function_">lineConnect</span>(<span class="params"><span class="attr">posStartX</span>: <span class="built_in">number</span>, <span class="attr">posStartY</span>: <span class="built_in">number</span>, <span class="attr">posEndX</span>: <span class="built_in">number</span>, <span class="attr">posEndY</span>: <span class="built_in">number</span></span>) &#123;</span><br><span class="line">    <span class="comment">// 根据目标坐标设置3D坐标  z轴位置在地图表面</span></span><br><span class="line">    <span class="keyword">const</span> [x0, y0, z0] = [posStartX, posStartY, <span class="number">0</span>];</span><br><span class="line">    <span class="keyword">const</span> [x1, y1, z1] = [posEndX, posEndY, <span class="number">0</span>];</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 使用QuadraticBezierCurve3() 创建 三维二次贝塞尔曲线</span></span><br><span class="line">    <span class="keyword">const</span> curve = <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">QuadraticBezierCurve3</span>(</span><br><span class="line">      <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">Vector3</span>(x0, -y0, z0),</span><br><span class="line">      <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">Vector3</span>((x0 + x1) / <span class="number">2</span>, -(y0 + y1) / <span class="number">2</span>, <span class="number">20</span>),</span><br><span class="line">      <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">Vector3</span>(x1, -y1, z1),</span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取曲线 上的50个点</span></span><br><span class="line">    <span class="keyword">const</span> points = curve.<span class="title function_">getPoints</span>(<span class="number">50</span>);</span><br><span class="line">    <span class="keyword">const</span> lineGeometry = <span class="keyword">new</span> <span class="title class_">LineGeometry</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> <span class="attr">arr</span>: <span class="built_in">number</span>[] = [];</span><br><span class="line">    points.<span class="title function_">forEach</span>(<span class="function">(<span class="params">item</span>) =&gt;</span> &#123;</span><br><span class="line">      arr.<span class="title function_">push</span>(item.<span class="property">x</span>);</span><br><span class="line">      arr.<span class="title function_">push</span>(item.<span class="property">y</span>);</span><br><span class="line">      arr.<span class="title function_">push</span>(item.<span class="property">z</span>);</span><br><span class="line">    &#125;);</span><br><span class="line">    lineGeometry.<span class="title function_">setPositions</span>(arr);</span><br><span class="line">    <span class="keyword">const</span> colors = [</span><br><span class="line">      ...<span class="variable language_">this</span>.<span class="title function_">gradientColors</span>(<span class="string">&#x27;#00ffff&#x27;</span>, <span class="string">&#x27;#f44336&#x27;</span>, points.<span class="property">length</span> / <span class="number">2</span>),</span><br><span class="line">      ...<span class="variable language_">this</span>.<span class="title function_">gradientColors</span>(<span class="string">&#x27;#f44336&#x27;</span>, <span class="string">&#x27;#f44336&#x27;</span>, points.<span class="property">length</span> / <span class="number">2</span>),</span><br><span class="line">    ].<span class="title function_">reverse</span>();</span><br><span class="line">    <span class="keyword">const</span> colorArr = colors.<span class="title function_">reduce</span>(<span class="function">(<span class="params"><span class="attr">arr</span>: <span class="built_in">number</span>[], item</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> <span class="title class_">Tcolor</span> = <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">Color</span>(item);</span><br><span class="line">      <span class="keyword">return</span> arr.<span class="title function_">concat</span>(<span class="title class_">Tcolor</span>.<span class="property">r</span>, <span class="title class_">Tcolor</span>.<span class="property">g</span>, <span class="title class_">Tcolor</span>.<span class="property">b</span>);</span><br><span class="line">    &#125;, []);</span><br><span class="line">    lineGeometry.<span class="title function_">setColors</span>(colorArr);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> material = <span class="keyword">new</span> <span class="title class_">LineMaterial</span>(&#123;</span><br><span class="line">      <span class="attr">vertexColors</span>: <span class="literal">true</span>,</span><br><span class="line">      <span class="attr">linewidth</span>: <span class="number">2</span>,</span><br><span class="line">      <span class="attr">transparent</span>: <span class="literal">true</span>,</span><br><span class="line">      <span class="attr">side</span>: <span class="variable constant_">THREE</span>.<span class="property">DoubleSide</span>,</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    material.<span class="property">resolution</span>.<span class="title function_">set</span>(<span class="variable language_">window</span>.<span class="property">innerWidth</span>, <span class="variable language_">window</span>.<span class="property">innerHeight</span>);</span><br><span class="line">    <span class="keyword">const</span> line = <span class="keyword">new</span> <span class="title class_">Line2</span>(lineGeometry, material);</span><br><span class="line">    line[<span class="string">&#x27;_colors&#x27;</span>] = colorArr;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">lines</span>.<span class="title function_">push</span>(line);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">scene</span>.<span class="title function_">add</span>(line);</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">private</span> <span class="title function_">gradientColors</span>(<span class="params"><span class="attr">start</span>: <span class="built_in">string</span>, <span class="attr">end</span>: <span class="built_in">string</span>, <span class="attr">steps</span>: <span class="built_in">number</span>, gamma = <span class="number">1</span></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">parseColor</span> = (<span class="params"><span class="attr">hexStr</span>: <span class="built_in">string</span></span>) =&gt; &#123;</span><br><span class="line">      <span class="keyword">return</span> hexStr.<span class="property">length</span> === <span class="number">4</span></span><br><span class="line">        ? hexStr</span><br><span class="line">            .<span class="title function_">substr</span>(<span class="number">1</span>)</span><br><span class="line">            .<span class="title function_">split</span>(<span class="string">&#x27;&#x27;</span>)</span><br><span class="line">            .<span class="title function_">map</span>(<span class="keyword">function</span> (<span class="params">s</span>) &#123;</span><br><span class="line">              <span class="keyword">return</span> <span class="number">0x11</span> * <span class="built_in">parseInt</span>(s, <span class="number">16</span>);</span><br><span class="line">            &#125;)</span><br><span class="line">        : [hexStr.<span class="title function_">substr</span>(<span class="number">1</span>, <span class="number">2</span>), hexStr.<span class="title function_">substr</span>(<span class="number">3</span>, <span class="number">2</span>), hexStr.<span class="title function_">substr</span>(<span class="number">5</span>, <span class="number">2</span>)].<span class="title function_">map</span>(<span class="keyword">function</span> (<span class="params">s</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">parseInt</span>(s, <span class="number">16</span>);</span><br><span class="line">          &#125;);</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="keyword">const</span> pad = <span class="keyword">function</span> (<span class="params">s</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> s.<span class="property">length</span> === <span class="number">1</span> ? <span class="string">`0<span class="subst">$&#123;s&#125;</span>`</span> : s;</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="keyword">let</span> j;</span><br><span class="line">    <span class="keyword">let</span> ms;</span><br><span class="line">    <span class="keyword">let</span> me;</span><br><span class="line">    <span class="keyword">const</span> <span class="attr">output</span>: <span class="built_in">string</span>[] = [];</span><br><span class="line">    <span class="keyword">const</span> <span class="attr">so</span>: <span class="built_in">string</span>[] = [];</span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">normalize</span> = (<span class="params"><span class="attr">channel</span>: <span class="built_in">number</span></span>) =&gt; &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="title class_">Math</span>.<span class="title function_">pow</span>(channel / <span class="number">255</span>, gamma);</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="keyword">const</span> startNum = <span class="title function_">parseColor</span>(start).<span class="title function_">map</span>(normalize);</span><br><span class="line">    <span class="keyword">const</span> endNum = <span class="title function_">parseColor</span>(end).<span class="title function_">map</span>(normalize);</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; steps; i++) &#123;</span><br><span class="line">      ms = i / (steps - <span class="number">1</span>);</span><br><span class="line">      me = <span class="number">1</span> - ms;</span><br><span class="line">      <span class="keyword">for</span> (j = <span class="number">0</span>; j &lt; <span class="number">3</span>; j++) &#123;</span><br><span class="line">        so[j] = <span class="title function_">pad</span>(</span><br><span class="line">          <span class="title class_">Math</span>.<span class="title function_">round</span>(<span class="title class_">Math</span>.<span class="title function_">pow</span>(startNum[j] * me + endNum[j] * ms, <span class="number">1</span> / gamma) * <span class="number">255</span>).<span class="title function_">toString</span>(<span class="number">16</span>),</span><br><span class="line">        );</span><br><span class="line">      &#125;</span><br><span class="line">      output.<span class="title function_">push</span>(<span class="string">`#<span class="subst">$&#123;so.join(<span class="string">&#x27;&#x27;</span>)&#125;</span>`</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> output;</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>每次将颜色数组的最后一个颜色（r + g + b 三个数字），提到最前面</p><p>这里由于动画较快，通过 <code>_tick</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">export</span> <span class="keyword">class</span> <span class="title class_">LineRender</span> &#123;</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">readonly</span> <span class="attr">lines</span>: <span class="title class_">Line2</span>[] = [];</span><br><span class="line">  <span class="keyword">private</span> <span class="title function_">arclineAnimate</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> line <span class="keyword">of</span> <span class="variable language_">this</span>.<span class="property">lines</span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (!line[<span class="string">&#x27;_tick&#x27;</span>]) line[<span class="string">&#x27;_tick&#x27;</span>] = <span class="number">0</span>;</span><br><span class="line">      line[<span class="string">&#x27;_tick&#x27;</span>] = (line[<span class="string">&#x27;_tick&#x27;</span>] + <span class="number">1</span>) % <span class="number">4</span>;</span><br><span class="line">      <span class="keyword">if</span> (line[<span class="string">&#x27;_tick&#x27;</span>] &gt; <span class="number">1</span>) <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">const</span> <span class="attr">colors</span>: <span class="built_in">any</span>[] = line[<span class="string">&#x27;_colors&#x27;</span>];</span><br><span class="line">      colors.<span class="title function_">splice</span>(<span class="number">0</span>, <span class="number">0</span>, ...colors.<span class="title function_">splice</span>(colors.<span class="property">length</span> - <span class="number">3</span>, <span class="number">3</span>));</span><br><span class="line"></span><br><span class="line">      line.<span class="property">geometry</span>.<span class="title function_">setColors</span>(colors);</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>addLines</code> 并在 <code>render</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">LineRender</span> &#123;</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">readonly</span> <span class="attr">lines</span>: <span class="title class_">Line2</span>[] = [];</span><br><span class="line"></span><br><span class="line">  <span class="title function_">render</span>(<span class="params"><span class="attr">poses</span>: [[<span class="built_in">number</span>, <span class="built_in">number</span>], [<span class="built_in">number</span>, <span class="built_in">number</span>]][]</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">lines</span>.<span class="title function_">splice</span>(<span class="number">0</span>);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">addLines</span>(poses);</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_">addLines</span>(<span class="params"><span class="attr">poses</span>: [[<span class="built_in">number</span>, <span class="built_in">number</span>], [<span class="built_in">number</span>, <span class="built_in">number</span>]][]</span>) &#123;</span><br><span class="line">    poses.<span class="title function_">forEach</span>(<span class="function">(<span class="params">item</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> start = <span class="variable language_">this</span>.<span class="title function_">projection</span>(item[<span class="number">0</span>]);</span><br><span class="line">      <span class="keyword">const</span> end = <span class="variable language_">this</span>.<span class="title function_">projection</span>(item[<span class="number">1</span>]);</span><br><span class="line">      <span class="keyword">if</span> (start &amp;&amp; end) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="title function_">lineConnect</span>(...start, ...end);</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="起始点动画"><a href="#起始点动画" class="headerlink" title="起始点动画"></a>起始点动画</h2><p>在飞线的开始和结尾处，有两个扩散动画的点，我们要增加点模型并让其动起来</p><h3 id="创建模型"><a href="#创建模型" class="headerlink" title="创建模型"></a>创建模型</h3><p>创建函数 <code>spotCircle</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">LineRender</span> &#123;</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">readonly</span> <span class="attr">circleYs</span>: <span class="variable constant_">THREE</span>.<span class="property">Mesh</span>&lt;<span class="variable constant_">THREE</span>.<span class="property">RingGeometry</span>, <span class="variable constant_">THREE</span>.<span class="property">MeshBasicMaterial</span>&gt;[] = [];</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="title function_">spotCircle</span>(<span class="params"><span class="attr">spot</span>: [<span class="built_in">number</span>, <span class="built_in">number</span>]</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> geometry1 = <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">CircleGeometry</span>(<span class="number">2</span>, <span class="number">200</span>);</span><br><span class="line">    <span class="keyword">const</span> material1 = <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">MeshBasicMaterial</span>(&#123; <span class="attr">color</span>: <span class="string">&#x27;#F44336&#x27;</span>, <span class="attr">side</span>: <span class="variable constant_">THREE</span>.<span class="property">DoubleSide</span> &#125;);</span><br><span class="line">    <span class="keyword">const</span> circle = <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">Mesh</span>(geometry1, material1);</span><br><span class="line">    <span class="comment">// 绘制地图时 y轴取反 这里同步</span></span><br><span class="line">    circle.<span class="property">position</span>.<span class="title function_">set</span>(spot[<span class="number">0</span>], -spot[<span class="number">1</span>], <span class="number">0.4</span>);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">scene</span>.<span class="title function_">add</span>(circle);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 圆环</span></span><br><span class="line">    <span class="keyword">const</span> geometry2 = <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">RingGeometry</span>(<span class="number">2</span>, <span class="number">1</span>, <span class="number">50</span>);</span><br><span class="line">    <span class="comment">// transparent 设置 true 开启透明</span></span><br><span class="line">    <span class="keyword">const</span> material2 = <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">MeshBasicMaterial</span>(&#123;</span><br><span class="line">      <span class="attr">color</span>: <span class="number">0xf44336</span>,</span><br><span class="line">      <span class="attr">side</span>: <span class="variable constant_">THREE</span>.<span class="property">DoubleSide</span>,</span><br><span class="line">      <span class="attr">transparent</span>: <span class="literal">true</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">const</span> circleY = <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">Mesh</span>(geometry2, material2);</span><br><span class="line">    <span class="comment">// 绘制地图时 y轴取反 这里同步</span></span><br><span class="line">    circleY.<span class="property">position</span>.<span class="title function_">set</span>(spot[<span class="number">0</span>], -spot[<span class="number">1</span>], <span class="number">0.4</span>);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">scene</span>.<span class="title function_">add</span>(circleY);</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">circleYs</span>.<span class="title function_">push</span>(circleY);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>修改前面的 <code>addLines</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">class</span> <span class="title class_">LineRender</span> &#123;</span><br><span class="line">  <span class="keyword">private</span> <span class="title function_">addLines</span>(<span class="params"><span class="attr">poses</span>: [[<span class="built_in">number</span>, <span class="built_in">number</span>], [<span class="built_in">number</span>, <span class="built_in">number</span>]][]</span>) &#123;</span><br><span class="line">    poses.<span class="title function_">forEach</span>(<span class="function">(<span class="params">item</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> start = <span class="variable language_">this</span>.<span class="title function_">projection</span>(item[<span class="number">0</span>]);</span><br><span class="line">      <span class="keyword">const</span> end = <span class="variable language_">this</span>.<span class="title function_">projection</span>(item[<span class="number">1</span>]);</span><br><span class="line">      <span class="keyword">if</span> (start &amp;&amp; end) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="title function_">spotCircle</span>(start);</span><br><span class="line">        <span class="variable language_">this</span>.<span class="title function_">spotCircle</span>(end);</span><br><span class="line">        <span class="variable language_">this</span>.<span class="title function_">lineConnect</span>(...start, ...end);</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="让点动起来"><a href="#让点动起来" class="headerlink" title="让点动起来"></a>让点动起来</h3><p>通过放缩点的大小，并改变其透明度，可实现动画效果</p><p>增加函数 <code>pointAnimate</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">LineRender</span> &#123;</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">readonly</span> <span class="attr">circleYs</span>: <span class="variable constant_">THREE</span>.<span class="property">Mesh</span>&lt;<span class="variable constant_">THREE</span>.<span class="property">RingGeometry</span>, <span class="variable constant_">THREE</span>.<span class="property">MeshBasicMaterial</span>&gt;[] = [];</span><br><span class="line"></span><br><span class="line">  <span class="title function_">animation</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">pointAnimate</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_">pointAnimate</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">circleYs</span>.<span class="title function_">forEach</span>(<span class="keyword">function</span> (<span class="params">mesh</span>) &#123;</span><br><span class="line">      mesh[<span class="string">&#x27;_s&#x27;</span>] += <span class="number">0.01</span>;</span><br><span class="line">      mesh.<span class="property">scale</span>.<span class="title function_">set</span>(<span class="number">1.1</span> * mesh[<span class="string">&#x27;_s&#x27;</span>], <span class="number">1.1</span> * mesh[<span class="string">&#x27;_s&#x27;</span>], <span class="number">1.1</span> * mesh[<span class="string">&#x27;_s&#x27;</span>]);</span><br><span class="line">      <span class="keyword">if</span> (mesh[<span class="string">&#x27;_s&#x27;</span>] &lt;= <span class="number">2</span>) &#123;</span><br><span class="line">        mesh.<span class="property">material</span>.<span class="property">opacity</span> = <span class="number">2</span> - mesh[<span class="string">&#x27;_s&#x27;</span>];</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        mesh[<span class="string">&#x27;_s&#x27;</span>] = <span class="number">1</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="最终代码"><a href="#最终代码" class="headerlink" title="最终代码"></a>最终代码</h2><p>按以上步骤，最终实现的 <code>LineRender</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> <span class="variable constant_">THREE</span> <span class="keyword">from</span> <span class="string">&#x27;three&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> d3 <span class="keyword">from</span> <span class="string">&#x27;d3&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Line2</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;three/examples/jsm/lines/Line2&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">LineGeometry</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;three/examples/jsm/lines/LineGeometry&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">LineMaterial</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;three/examples/jsm/lines/LineMaterial&#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_">LineRender</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"><span class="keyword">private</span> <span class="keyword">readonly</span> <span class="attr">scene</span>: THREE.<span class="title class_">Scene</span></span>) &#123;&#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">readonly</span> <span class="attr">circleYs</span>: <span class="variable constant_">THREE</span>.<span class="property">Mesh</span>&lt;<span class="variable constant_">THREE</span>.<span class="property">RingGeometry</span>, <span class="variable constant_">THREE</span>.<span class="property">MeshBasicMaterial</span>&gt;[] = [];</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">readonly</span> projection = d3.<span class="title function_">geoMercator</span>().<span class="title function_">center</span>([<span class="number">104.0</span>, <span class="number">37.5</span>]).<span class="title function_">translate</span>([<span class="number">0</span>, <span class="number">0</span>]);</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">readonly</span> <span class="attr">lines</span>: <span class="title class_">Line2</span>[] = [];</span><br><span class="line"></span><br><span class="line">  <span class="title function_">render</span>(<span class="params"><span class="attr">poses</span>: [[<span class="built_in">number</span>, <span class="built_in">number</span>], [<span class="built_in">number</span>, <span class="built_in">number</span>]][]</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">circleYs</span>.<span class="title function_">splice</span>(<span class="number">0</span>);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">lines</span>.<span class="title function_">splice</span>(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">addLines</span>(poses);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">animation</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">pointAnimate</span>();</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">arclineAnimate</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_">pointAnimate</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">circleYs</span>.<span class="title function_">forEach</span>(<span class="keyword">function</span> (<span class="params">mesh</span>) &#123;</span><br><span class="line">      mesh[<span class="string">&#x27;_s&#x27;</span>] += <span class="number">0.01</span>;</span><br><span class="line">      mesh.<span class="property">scale</span>.<span class="title function_">set</span>(<span class="number">1.1</span> * mesh[<span class="string">&#x27;_s&#x27;</span>], <span class="number">1.1</span> * mesh[<span class="string">&#x27;_s&#x27;</span>], <span class="number">1.1</span> * mesh[<span class="string">&#x27;_s&#x27;</span>]);</span><br><span class="line">      <span class="keyword">if</span> (mesh[<span class="string">&#x27;_s&#x27;</span>] &lt;= <span class="number">2</span>) &#123;</span><br><span class="line">        mesh.<span class="property">material</span>.<span class="property">opacity</span> = <span class="number">2</span> - mesh[<span class="string">&#x27;_s&#x27;</span>];</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        mesh[<span class="string">&#x27;_s&#x27;</span>] = <span class="number">1</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">private</span> <span class="title function_">arclineAnimate</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> line <span class="keyword">of</span> <span class="variable language_">this</span>.<span class="property">lines</span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (!line[<span class="string">&#x27;_tick&#x27;</span>]) line[<span class="string">&#x27;_tick&#x27;</span>] = <span class="number">0</span>;</span><br><span class="line">      line[<span class="string">&#x27;_tick&#x27;</span>] = (line[<span class="string">&#x27;_tick&#x27;</span>] + <span class="number">1</span>) % <span class="number">4</span>;</span><br><span class="line">      <span class="keyword">if</span> (line[<span class="string">&#x27;_tick&#x27;</span>] &gt; <span class="number">1</span>) <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">const</span> <span class="attr">colors</span>: <span class="built_in">any</span>[] = line[<span class="string">&#x27;_colors&#x27;</span>];</span><br><span class="line">      colors.<span class="title function_">splice</span>(<span class="number">0</span>, <span class="number">0</span>, ...colors.<span class="title function_">splice</span>(colors.<span class="property">length</span> - <span class="number">3</span>, <span class="number">3</span>));</span><br><span class="line"></span><br><span class="line">      line.<span class="property">geometry</span>.<span class="title function_">setColors</span>(colors);</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">private</span> <span class="title function_">addLines</span>(<span class="params"><span class="attr">poses</span>: [[<span class="built_in">number</span>, <span class="built_in">number</span>], [<span class="built_in">number</span>, <span class="built_in">number</span>]][]</span>) &#123;</span><br><span class="line">    poses.<span class="title function_">forEach</span>(<span class="function">(<span class="params">item</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> start = <span class="variable language_">this</span>.<span class="title function_">projection</span>(item[<span class="number">0</span>]);</span><br><span class="line">      <span class="keyword">const</span> end = <span class="variable language_">this</span>.<span class="title function_">projection</span>(item[<span class="number">1</span>]);</span><br><span class="line">      <span class="keyword">if</span> (start &amp;&amp; end) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="title function_">spotCircle</span>(start);</span><br><span class="line">        <span class="variable language_">this</span>.<span class="title function_">spotCircle</span>(end);</span><br><span class="line">        <span class="variable language_">this</span>.<span class="title function_">lineConnect</span>(...start, ...end);</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">private</span> <span class="title function_">spotCircle</span>(<span class="params"><span class="attr">spot</span>: [<span class="built_in">number</span>, <span class="built_in">number</span>]</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> geometry1 = <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">CircleGeometry</span>(<span class="number">2</span>, <span class="number">200</span>);</span><br><span class="line">    <span class="keyword">const</span> material1 = <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">MeshBasicMaterial</span>(&#123; <span class="attr">color</span>: <span class="string">&#x27;#F44336&#x27;</span>, <span class="attr">side</span>: <span class="variable constant_">THREE</span>.<span class="property">DoubleSide</span> &#125;);</span><br><span class="line">    <span class="keyword">const</span> circle = <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">Mesh</span>(geometry1, material1);</span><br><span class="line">    <span class="comment">// 绘制地图时 y轴取反 这里同步</span></span><br><span class="line">    circle.<span class="property">position</span>.<span class="title function_">set</span>(spot[<span class="number">0</span>], -spot[<span class="number">1</span>], <span class="number">0.4</span>);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">scene</span>.<span class="title function_">add</span>(circle);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 圆环</span></span><br><span class="line">    <span class="keyword">const</span> geometry2 = <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">RingGeometry</span>(<span class="number">2</span>, <span class="number">1</span>, <span class="number">50</span>);</span><br><span class="line">    <span class="comment">// transparent 设置 true 开启透明</span></span><br><span class="line">    <span class="keyword">const</span> material2 = <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">MeshBasicMaterial</span>(&#123;</span><br><span class="line">      <span class="attr">color</span>: <span class="number">0xf44336</span>,</span><br><span class="line">      <span class="attr">side</span>: <span class="variable constant_">THREE</span>.<span class="property">DoubleSide</span>,</span><br><span class="line">      <span class="attr">transparent</span>: <span class="literal">true</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">const</span> circleY = <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">Mesh</span>(geometry2, material2);</span><br><span class="line">    <span class="comment">// 绘制地图时 y轴取反 这里同步</span></span><br><span class="line">    circleY.<span class="property">position</span>.<span class="title function_">set</span>(spot[<span class="number">0</span>], -spot[<span class="number">1</span>], <span class="number">0.4</span>);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">scene</span>.<span class="title function_">add</span>(circleY);</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">circleYs</span>.<span class="title function_">push</span>(circleY);</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_">lineConnect</span>(<span class="params"><span class="attr">posStartX</span>: <span class="built_in">number</span>, <span class="attr">posStartY</span>: <span class="built_in">number</span>, <span class="attr">posEndX</span>: <span class="built_in">number</span>, <span class="attr">posEndY</span>: <span class="built_in">number</span></span>) &#123;</span><br><span class="line">    <span class="comment">// 根据目标坐标设置3D坐标  z轴位置在地图表面</span></span><br><span class="line">    <span class="keyword">const</span> [x0, y0, z0] = [posStartX, posStartY, <span class="number">0</span>];</span><br><span class="line">    <span class="keyword">const</span> [x1, y1, z1] = [posEndX, posEndY, <span class="number">0</span>];</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 使用QuadraticBezierCurve3() 创建 三维二次贝塞尔曲线</span></span><br><span class="line">    <span class="keyword">const</span> curve = <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">QuadraticBezierCurve3</span>(</span><br><span class="line">      <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">Vector3</span>(x0, -y0, z0),</span><br><span class="line">      <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">Vector3</span>((x0 + x1) / <span class="number">2</span>, -(y0 + y1) / <span class="number">2</span>, <span class="number">20</span>),</span><br><span class="line">      <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">Vector3</span>(x1, -y1, z1),</span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取曲线 上的50个点</span></span><br><span class="line">    <span class="keyword">const</span> points = curve.<span class="title function_">getPoints</span>(<span class="number">50</span>).<span class="title function_">reduce</span>(<span class="function">(<span class="params">arr, cur</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> arr.<span class="title function_">concat</span>(cur.<span class="property">x</span>, cur.<span class="property">y</span>, cur.<span class="property">z</span>);</span><br><span class="line">    &#125;, [] <span class="keyword">as</span> <span class="built_in">number</span>[]);</span><br><span class="line">    <span class="keyword">const</span> lineGeometry = <span class="keyword">new</span> <span class="title class_">LineGeometry</span>();</span><br><span class="line"></span><br><span class="line">    lineGeometry.<span class="title function_">setPositions</span>(points);</span><br><span class="line">    <span class="keyword">const</span> colors = [</span><br><span class="line">      ...<span class="variable language_">this</span>.<span class="title function_">gradientColors</span>(<span class="string">&#x27;#00ffff&#x27;</span>, <span class="string">&#x27;#f44336&#x27;</span>, points.<span class="property">length</span> / <span class="number">3</span> / <span class="number">2</span>),</span><br><span class="line">      ...<span class="variable language_">this</span>.<span class="title function_">gradientColors</span>(<span class="string">&#x27;#f44336&#x27;</span>, <span class="string">&#x27;#f44336&#x27;</span>, points.<span class="property">length</span> / <span class="number">3</span> / <span class="number">2</span>),</span><br><span class="line">    ].<span class="title function_">reverse</span>();</span><br><span class="line">    <span class="keyword">const</span> colorArr = colors.<span class="title function_">reduce</span>(<span class="function">(<span class="params"><span class="attr">arr</span>: <span class="built_in">number</span>[], item</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> <span class="title class_">Tcolor</span> = <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">Color</span>(item);</span><br><span class="line">      <span class="keyword">return</span> arr.<span class="title function_">concat</span>(<span class="title class_">Tcolor</span>.<span class="property">r</span>, <span class="title class_">Tcolor</span>.<span class="property">g</span>, <span class="title class_">Tcolor</span>.<span class="property">b</span>);</span><br><span class="line">    &#125;, []);</span><br><span class="line">    lineGeometry.<span class="title function_">setColors</span>(colorArr);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> material = <span class="keyword">new</span> <span class="title class_">LineMaterial</span>(&#123;</span><br><span class="line">      <span class="comment">// color: 0xf44336,</span></span><br><span class="line">      <span class="attr">vertexColors</span>: <span class="literal">true</span>,</span><br><span class="line">      <span class="attr">linewidth</span>: <span class="number">2</span>,</span><br><span class="line">      <span class="attr">transparent</span>: <span class="literal">true</span>,</span><br><span class="line">      <span class="attr">side</span>: <span class="variable constant_">THREE</span>.<span class="property">DoubleSide</span>,</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    material.<span class="property">resolution</span>.<span class="title function_">set</span>(<span class="variable language_">window</span>.<span class="property">innerWidth</span>, <span class="variable language_">window</span>.<span class="property">innerHeight</span>);</span><br><span class="line">    <span class="keyword">const</span> line = <span class="keyword">new</span> <span class="title class_">Line2</span>(lineGeometry, material);</span><br><span class="line">    line[<span class="string">&#x27;_colors&#x27;</span>] = colorArr;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">lines</span>.<span class="title function_">push</span>(line);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">scene</span>.<span class="title function_">add</span>(line);</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">private</span> <span class="title function_">gradientColors</span>(<span class="params"><span class="attr">start</span>: <span class="built_in">string</span>, <span class="attr">end</span>: <span class="built_in">string</span>, <span class="attr">steps</span>: <span class="built_in">number</span>, gamma = <span class="number">1</span></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">parseColor</span> = (<span class="params"><span class="attr">hexStr</span>: <span class="built_in">string</span></span>) =&gt; &#123;</span><br><span class="line">      <span class="keyword">return</span> hexStr.<span class="property">length</span> === <span class="number">4</span></span><br><span class="line">        ? hexStr</span><br><span class="line">            .<span class="title function_">substr</span>(<span class="number">1</span>)</span><br><span class="line">            .<span class="title function_">split</span>(<span class="string">&#x27;&#x27;</span>)</span><br><span class="line">            .<span class="title function_">map</span>(<span class="keyword">function</span> (<span class="params">s</span>) &#123;</span><br><span class="line">              <span class="keyword">return</span> <span class="number">0x11</span> * <span class="built_in">parseInt</span>(s, <span class="number">16</span>);</span><br><span class="line">            &#125;)</span><br><span class="line">        : [hexStr.<span class="title function_">substr</span>(<span class="number">1</span>, <span class="number">2</span>), hexStr.<span class="title function_">substr</span>(<span class="number">3</span>, <span class="number">2</span>), hexStr.<span class="title function_">substr</span>(<span class="number">5</span>, <span class="number">2</span>)].<span class="title function_">map</span>(<span class="keyword">function</span> (<span class="params">s</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">parseInt</span>(s, <span class="number">16</span>);</span><br><span class="line">          &#125;);</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="keyword">const</span> pad = <span class="keyword">function</span> (<span class="params">s</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> s.<span class="property">length</span> === <span class="number">1</span> ? <span class="string">`0<span class="subst">$&#123;s&#125;</span>`</span> : s;</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="keyword">let</span> j;</span><br><span class="line">    <span class="keyword">let</span> ms;</span><br><span class="line">    <span class="keyword">let</span> me;</span><br><span class="line">    <span class="keyword">const</span> <span class="attr">output</span>: <span class="built_in">string</span>[] = [];</span><br><span class="line">    <span class="keyword">const</span> <span class="attr">so</span>: <span class="built_in">string</span>[] = [];</span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">normalize</span> = (<span class="params"><span class="attr">channel</span>: <span class="built_in">number</span></span>) =&gt; &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="title class_">Math</span>.<span class="title function_">pow</span>(channel / <span class="number">255</span>, gamma);</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="keyword">const</span> startNum = <span class="title function_">parseColor</span>(start).<span class="title function_">map</span>(normalize);</span><br><span class="line">    <span class="keyword">const</span> endNum = <span class="title function_">parseColor</span>(end).<span class="title function_">map</span>(normalize);</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; steps; i++) &#123;</span><br><span class="line">      ms = i / (steps - <span class="number">1</span>);</span><br><span class="line">      me = <span class="number">1</span> - ms;</span><br><span class="line">      <span class="keyword">for</span> (j = <span class="number">0</span>; j &lt; <span class="number">3</span>; j++) &#123;</span><br><span class="line">        so[j] = <span class="title function_">pad</span>(</span><br><span class="line">          <span class="title class_">Math</span>.<span class="title function_">round</span>(<span class="title class_">Math</span>.<span class="title function_">pow</span>(startNum[j] * me + endNum[j] * ms, <span class="number">1</span> / gamma) * <span class="number">255</span>).<span class="title function_">toString</span>(<span class="number">16</span>),</span><br><span class="line">        );</span><br><span class="line">      &#125;</span><br><span class="line">      output.<span class="title function_">push</span>(<span class="string">`#<span class="subst">$&#123;so.join(<span class="string">&#x27;&#x27;</span>)&#125;</span>`</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> output;</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>在调用处可以很方便的根据经纬度生成飞线</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> lineRender = <span class="keyword">new</span> <span class="title class_">LineRender</span>(scene);</span><br><span class="line">lineRender.<span class="title function_">render</span>(data);</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></pre></td><td class="code"><pre><span class="line">lineRender.<span class="title function_">animation</span>();</span><br></pre></td></tr></table></figure><h3 id="以-Vue-举例"><a href="#以-Vue-举例" class="headerlink" title="以 Vue 举例"></a>以 Vue 举例</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><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">lang</span>=<span class="string">&quot;ts&quot;</span> <span class="attr">setup</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">const</span> scene = <span class="keyword">new</span> <span class="variable constant_">THREE</span>.<span class="title class_">Scene</span>();</span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">const</span> lineRender = <span class="keyword">new</span> <span class="title class_">LineRender</span>(scene);</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="title function_">onMounted</span>(<span class="function">() =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-javascript">    lineRender.<span class="title function_">render</span>(data); <span class="comment">// 经纬度数据</span></span></span><br><span class="line"><span class="language-javascript">    <span class="title function_">requestAnimationFrame</span>(animation);</span></span><br><span class="line"><span class="language-javascript">  &#125;);</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">function</span> <span class="title function_">animation</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">    <span class="title function_">requestAnimationFrame</span>(animation);</span></span><br><span class="line"><span class="language-javascript">    lineRender.<span class="title function_">animation</span>();</span></span><br><span class="line"><span class="language-javascript">  &#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><h3 id="飞线经纬度的数据格式"><a href="#飞线经纬度的数据格式" class="headerlink" title="飞线经纬度的数据格式"></a>飞线经纬度的数据格式</h3><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">export</span> <span class="keyword">const</span> <span class="attr">data</span>: [[<span class="built_in">number</span>, <span class="built_in">number</span>], [<span class="built_in">number</span>, <span class="built_in">number</span>]][] = [</span><br><span class="line">  [</span><br><span class="line">    [<span class="number">106.557691</span>, <span class="number">25.559296</span>],</span><br><span class="line">    [<span class="number">86.495721</span>, <span class="number">39.236797</span>],</span><br><span class="line">  ],</span><br><span class="line">  [</span><br><span class="line">    [<span class="number">116.557691</span>, <span class="number">39.559296</span>],</span><br><span class="line">    [<span class="number">139.495721</span>, <span class="number">36.236797</span>],</span><br><span class="line">  ],</span><br><span class="line">  [</span><br><span class="line">    [<span class="number">116.557691</span>, <span class="number">39.559296</span>],</span><br><span class="line">    [<span class="number">104.495721</span>, <span class="number">47.236797</span>],</span><br><span class="line">  ]</span><br><span class="line">];</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;使用 Three.js 在地图中创建有动画的飞线&lt;/p&gt;
&lt;p&gt;能够根据输入坐标，创建飞线&lt;/p&gt;
&lt;p&gt;示例代码均为 ts，效果如图&lt;/p&gt;</summary>
    
    
    
    <category term="记录" scheme="https://blog.hal.wang/categories/%E8%AE%B0%E5%BD%95/"/>
    
    
    <category term="JS" scheme="https://blog.hal.wang/tags/JS/"/>
    
    <category term="Three.js" scheme="https://blog.hal.wang/tags/Three-js/"/>
    
  </entry>
  
  <entry>
    <title>Git 大文件 LFS</title>
    <link href="https://blog.hal.wang/4e5ea5aa/"/>
    <id>https://blog.hal.wang/4e5ea5aa/</id>
    <published>2022-08-16T10:39:41.000Z</published>
    <updated>2026-03-18T15:50:10.582Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>Git LFS 是一个易于安装、易于配置，使用高效的 Git 拓展工具，它能有效的管理仓库中的大文件，避免仓库体积过大，影响项目管理效率</p></blockquote><span id="more"></span><h2 id="为什么使用-LFS"><a href="#为什么使用-LFS" class="headerlink" title="为什么使用 LFS"></a>为什么使用 LFS</h2><blockquote><p>这段是废话，既然看到了这里，说明你应该已经需要用 LFS 了，跳过吧！</p></blockquote><ol><li>主流 Git 托管服务都支持 LFS，如 <code>GitHub</code>, <code>Gitee</code>, <code>GitLab</code>, 云效等</li><li>有些托管服务的 Git 仓库限制了大文件，或者限制仓库总大小，如 <code>GitHub</code> 目前限制文件不能超过 <code>100MB</code></li><li>仓库太大每次 pull&#x2F;push 都很慢</li><li>大文件往往很少修改</li></ol><p>使用 LFS 不仅能解决大文件带来的仓库问题，同时托管服务也对大文件做了传输速度优化</p><h2 id="开启-LFS"><a href="#开启-LFS" class="headerlink" title="开启 LFS"></a>开启 LFS</h2><p>按以下操作给已有仓库开启 LFS</p><ul><li>初始化 lfs，在项目目录下运行：</li></ul><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">git lfs install</span><br></pre></td></tr></table></figure><ul><li>跟踪大文件，并生成 <code>.gitattributes</code>：</li></ul><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">git lfs track &quot;xxx/file.mp4&quot;</span><br></pre></td></tr></table></figure><ul><li>提交修改：</li></ul><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">git add -A</span><br><span class="line">git commit -m &quot;message&quot;</span><br><span class="line">git push origin main</span><br></pre></td></tr></table></figure><p>之后就和普通仓库一样操作即可</p><h2 id="新增大文件"><a href="#新增大文件" class="headerlink" title="新增大文件"></a>新增大文件</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">git lfs track &quot;xxx/file.mp4&quot;</span><br></pre></td></tr></table></figure><p>也可以手动修改文件 <code>.gitattributes</code> 添加文件，可以和 <code>.gitignore</code> 一样使用通配符</p><h2 id="删除历史大文件"><a href="#删除历史大文件" class="headerlink" title="删除历史大文件"></a>删除历史大文件</h2><p>如果之前的提交有大文件，在不回退提交的前提下，删除文件是不会在 git 中删除大文件的</p><p>可以用以下方式修改历史提交，彻底删除大文件</p><h3 id="查找大文件"><a href="#查找大文件" class="headerlink" title="查找大文件"></a>查找大文件</h3><ul><li>查看文件大小</li></ul><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">du</span> -ah .git/object</span><br></pre></td></tr></table></figure><p>如果在 Windows 系统无法执行上述语句，可以用 <code>sh</code> 命令切换到 git bash 中执行，后续命令同样如此</p><ul><li>查看占用空间最多的五个文件</li></ul><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">git rev-list --objects --all | grep <span class="string">&quot;<span class="subst">$(git verify-pack -v .git/objects/pack/*.idx | sort -k 3 -n | tail -5 | awk &#x27;&#123;print$1&#125;&#x27;)</span>&quot;</span></span><br></pre></td></tr></table></figure><h3 id="删除大文件"><a href="#删除大文件" class="headerlink" title="删除大文件"></a>删除大文件</h3><ul><li>修改历史提交以删除大文件</li></ul><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">git filter-branch --force --index-filter <span class="string">&#x27;git rm -rf --cached --ignore-unmatch 你的大文件名&#x27;</span> --prune-empty --tag-name-filter <span class="built_in">cat</span> -- --all</span><br></pre></td></tr></table></figure><ul><li>清理本地仓库</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="built_in">rm</span> -rf .git/refs/original/</span><br><span class="line">git reflog expire --expire=now --all</span><br><span class="line">git gc --prune=now</span><br><span class="line">git gc --aggressive --prune=now</span><br></pre></td></tr></table></figure><ul><li>提交并覆盖远程仓库</li></ul><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">git push origin main --force</span><br></pre></td></tr></table></figure><p>如果该分支被保护，需要在托管网站解除保护才能 <code>force</code> 提交</p>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;Git LFS 是一个易于安装、易于配置，使用高效的 Git 拓展工具，它能有效的管理仓库中的大文件，避免仓库体积过大，影响项目管理效率&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="记录" scheme="https://blog.hal.wang/categories/%E8%AE%B0%E5%BD%95/"/>
    
    
    <category term="Git" scheme="https://blog.hal.wang/tags/Git/"/>
    
  </entry>
  
  <entry>
    <title>深入理解 JS 原型</title>
    <link href="https://blog.hal.wang/9c60be83/"/>
    <id>https://blog.hal.wang/9c60be83/</id>
    <published>2022-03-30T09:42:53.000Z</published>
    <updated>2026-03-18T15:50:10.596Z</updated>
    
    <content type="html"><![CDATA[<p>JS 中的原型链在面试中可以说是“必考题”</p><p>日常开发不常遇到，而且在 ES6 之后，原型链就更少见了</p><p>但是，如果设计框架或封装组件，可能就需要了解原型链</p><p>ES6 的类，可以认为是 ES5 的语法糖，因此本文主要以探究 ES5 为主</p><span id="more"></span><h2 id="JS-创建对象与其他语言区别"><a href="#JS-创建对象与其他语言区别" class="headerlink" title="JS 创建对象与其他语言区别"></a>JS 创建对象与其他语言区别</h2><p>以 C# 为例，C++&#x2F;JAVA 等类似</p><h3 id="C"><a href="#C" class="headerlink" title="C#"></a>C#</h3><p>在 C# 中，类相当于一个模板，对象是模板创造的实例。代码编译后，类本身除了静态属性和静态函数，没有其他用处</p><h3 id="JS"><a href="#JS" class="headerlink" title="JS"></a>JS</h3><p>在 JS 中，ES5 及之前是没有类的概念，对象是通过构造函数创建的，即构造函数承载了类的功能，在构造函数中可以使用 this 为实例对象增加字段和函数，以及赋值的操作。这点对于有其他语言基础的人来说，思想较难转变</p><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="title function_">Func</span>(<span class="params"></span>)&#123;&#125; <span class="comment">// 构造函数</span></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Func</span>(); <span class="comment">// 用构造函数创建对象</span></span><br><span class="line"><span class="title class_">Func</span>(); <span class="comment">// 调用普通函数</span></span><br></pre></td></tr></table></figure><p>JS 通过构造函数创建对象，会有以下对象参与其中</p><ul><li>构造函数：作为对象的构造函数</li><li>构造函数的原型对象：对象的对象原型将指向构造函数的原型对象</li></ul><h2 id="原型对象与对象原型"><a href="#原型对象与对象原型" class="headerlink" title="原型对象与对象原型"></a>原型对象与对象原型</h2><p>一般每个构造函数都有一个原型对象 <code>Func.prototype</code>，简单函数的原型对象是 <code>Object</code> 对象</p><p>一般每个对象都有一个对象原型 <code>obj.__proto__</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_">Func</span>(<span class="params"></span>)&#123;&#125; <span class="comment">// 构造函数</span></span><br><span class="line"><span class="keyword">const</span> obj=<span class="keyword">new</span> <span class="title class_">Func</span>(); <span class="comment">// 用构造函数创建对象</span></span><br><span class="line"><span class="title class_">Func</span>.<span class="property"><span class="keyword">prototype</span></span> == obj.<span class="property">__proto__</span> <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>由于 JS 中调用对象方法时，先查找对象自身方法，再查找原型对象 <code>__proto__</code> 中的方法，因此对象可以使用对象原型中的方法</p><p>原型对象也可以置空，这样构造函数创建的对象将没有额外方法，如 <code>toString</code>, <code>hasOwnProperty</code> 等</p><p>原型对象都有 <code>constructor</code> 字段，指向对应的构造函数</p><h2 id="原型链"><a href="#原型链" class="headerlink" title="原型链"></a>原型链</h2><p>由前面得出结论，对象和对象原型 <code>__proto__</code> 形成了一个原型链，原型链的最顶端是 <code>null</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">obj.<span class="property">__proto__</span>.<span class="property">__proto__</span>.<span class="property">__proto__</span> == <span class="literal">null</span> <span class="comment">// true</span></span><br></pre></td></tr></table></figure><p>上面可能有很多节点，也可能没有节点，取决于创建方法</p><p>类每多一次继承就会多一个节点，ES5 中的写法是给构造函数的 <code>prototype</code> 赋值并修改 <code>prototype.constructor</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_">Father</span>(<span class="params"></span>) &#123;&#125; <span class="comment">// 父类构造函数</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Son</span>(<span class="params"></span>) &#123; <span class="comment">// 子类构造函数</span></span><br><span class="line">  <span class="title class_">Father</span>.<span class="title function_">call</span>(<span class="variable language_">this</span>); <span class="comment">// 等同于ES6: this.super();</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="title class_">Son</span>.<span class="property"><span class="keyword">prototype</span></span> = <span class="keyword">new</span> <span class="title class_">Father</span>(); <span class="comment">// 不能直接 = Father，这样会导致 Father 和 Son 的对象原型指向同一个原型对象</span></span><br><span class="line"><span class="title class_">Son</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">constructor</span> = <span class="title class_">Son</span>; <span class="comment">// 原型对象需要指向构造函数，如果不赋值，指向的是 Father 构造函数</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">const</span> obj = <span class="title class_">Object</span>.<span class="title function_">create</span>(<span class="literal">null</span>, &#123;&#125;)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(obj.<span class="property">__proto__</span>) <span class="comment">// undefined</span></span><br></pre></td></tr></table></figure><p>每个对象，都可以使用原型链中的任意方法，因为调用方法时会按原型链逐级向上查找</p><h2 id="ES6-类和对象"><a href="#ES6-类和对象" class="headerlink" title="ES6 类和对象"></a>ES6 类和对象</h2><p>ES6 之前通过 <code>构造函数 + 原型</code> 实现面向对象编程<br>ES6 通过 <code>类</code> 实现面向对象编程</p><p>类的本质也是函数，也可以简单的认为，类就是 ES5 构造函数的简单写法</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;JS 中的原型链在面试中可以说是“必考题”&lt;/p&gt;
&lt;p&gt;日常开发不常遇到，而且在 ES6 之后，原型链就更少见了&lt;/p&gt;
&lt;p&gt;但是，如果设计框架或封装组件，可能就需要了解原型链&lt;/p&gt;
&lt;p&gt;ES6 的类，可以认为是 ES5 的语法糖，因此本文主要以探究 ES5 为主&lt;/p&gt;</summary>
    
    
    
    <category term="记录" scheme="https://blog.hal.wang/categories/%E8%AE%B0%E5%BD%95/"/>
    
    
    <category term="JS" scheme="https://blog.hal.wang/tags/JS/"/>
    
  </entry>
  
  <entry>
    <title>正则表达式进阶</title>
    <link href="https://blog.hal.wang/92f1340d/"/>
    <id>https://blog.hal.wang/92f1340d/</id>
    <published>2022-03-23T16:14:19.000Z</published>
    <updated>2026-03-18T15:50:10.596Z</updated>
    
    <content type="html"><![CDATA[<p>正则表达式，是每个程序员的必备的技能</p><h2 id="贪婪匹配-和-惰性匹配"><a href="#贪婪匹配-和-惰性匹配" class="headerlink" title="贪婪匹配 和 惰性匹配"></a>贪婪匹配 和 惰性匹配</h2><ul><li>贪婪匹配是尽可能匹配更多的字符</li><li>惰性匹配是尽可能匹配更少的字符</li></ul><p>惰性匹配是在 <code>*</code> , <code>+</code> , <code>{m,}</code> 后加上 <code>?</code></p><span id="more"></span><ul><li><code>*</code>: 匹配前面 0 次或以上，尽可能多</li><li><code>+</code>: 匹配前面 1 次或以上，尽可能多</li><li><code>{m,}</code>: 匹配前面 m 次或以上，尽可能多</li><li><code>*?</code>: 匹配前面 0 次或以上，尽可能少</li><li><code>+?</code>: 匹配前面 1 次或以上，尽可能少</li><li><code>{m,}?</code>: 匹配前面 m 次或以上，尽可能少</li></ul><h3 id="举例"><a href="#举例" class="headerlink" title="举例"></a>举例</h3><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">1a-2b-3c-4d-e5-f6</span><br></pre></td></tr></table></figure><p>除了 <code>-</code> 外，其他字符串都是不定长的，而且字符串也可以是其他除 <code>-</code> 外的字符甚至特殊符号</p><ul><li>贪婪匹配<ul><li>表达式：<code>^.+\-</code></li><li>匹配值：<code>1a-2b-3c-4d-e5-</code></li></ul></li><li>惰性匹配<ul><li>表达式：<code>^.+?\-</code></li><li>匹配值：<code>1a-</code></li></ul></li></ul><h3 id="惰性匹配顺序问题"><a href="#惰性匹配顺序问题" class="headerlink" title="惰性匹配顺序问题"></a>惰性匹配顺序问题</h3><p>前面的例子，如果想匹配后面的 <code>-f6</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">\-.+?$</span><br></pre></td></tr></table></figure><p>但匹配结果是 <code>-2b-3c-4d-e5-f6</code> 而不是 <code>-f6</code>，和贪婪匹配结果一样</p><p>这是因为正则匹配是从前往后，当匹配到 <code>-2</code> 时发现匹配了一部分，就会继续向前查询 <code>-2d</code> &gt; <code>-2d-</code> &gt; <code>-2d-3</code> &gt; <code>-2d-3c</code> 等，直到查询 <code>-2b-3c-4d-e5-f6</code> 才找到满足条件的值</p><p>为了解决这个问题，可以用排除法，即排除前面的 <code>-</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">\-[^\-]+$</span><br></pre></td></tr></table></figure><h2 id="字符串掐头去尾"><a href="#字符串掐头去尾" class="headerlink" title="字符串掐头去尾"></a>字符串掐头去尾</h2><p>在代码中，如果想去除字符串前面一部分，或者字符串后面一部分，可以用 <code>正则 + 替换</code> 的方式</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">image.png</span><br></pre></td></tr></table></figure><ul><li>若只想要不带扩展名的名称，在 JS 中可以这样</li></ul><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">\..+$</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="keyword">const</span> file = <span class="string">&quot;image.png&quot;</span></span><br><span class="line"><span class="keyword">const</span> name = file.<span class="title function_">replace</span>(<span class="regexp">/\..+$/</span>, <span class="string">&quot;&quot;</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(name); <span class="comment">// image</span></span><br></pre></td></tr></table></figure><ul><li>如果文件名中可能包含多个 <code>.</code></li></ul><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">image.1.png</span><br></pre></td></tr></table></figure><p>按上面的写法只能取到 <code>image</code> 而不是 <code>image.1</code></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">\.+[^\.]*$</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="keyword">const</span> file = <span class="string">&quot;image.1.png&quot;</span></span><br><span class="line"><span class="keyword">const</span> name = file.<span class="title function_">replace</span>(<span class="regexp">/\.+[^\.]*$/</span>, <span class="string">&quot;&quot;</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(name); <span class="comment">// image</span></span><br></pre></td></tr></table></figure><ul><li>如果只想保留扩展名，可以这样</li></ul><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">^.*\.</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="keyword">const</span> file = <span class="string">&quot;image.png&quot;</span></span><br><span class="line"><span class="keyword">const</span> extended = file.<span class="title function_">replace</span>(<span class="regexp">/^.*\./</span>, <span class="string">&quot;&quot;</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(extended); <span class="comment">// png</span></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><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">?=分组</span><br></pre></td></tr></table></figure><p>如 <code>[a-zA-Z](?=\d)</code> ，若字母<code>后</code>是数字则匹配该字母 ，否则<code>不</code>匹配，即 <code>[a-z]</code> <code>后</code>必须匹配 <code>\d</code></p><h3 id="反前瞻"><a href="#反前瞻" class="headerlink" title="反前瞻"></a>反前瞻</h3><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">?!分组</span><br></pre></td></tr></table></figure><p>如 <code>[a-zA-Z](?!\d)</code> ，若字母<code>后</code>是数字则<code>不</code>匹配该字母 ，否则匹配，即 <code>[a-z]</code> <code>后</code>必须<code>不</code>匹配 <code>\d</code></p><h3 id="正前顾"><a href="#正前顾" class="headerlink" title="正前顾"></a>正前顾</h3><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">?&lt;=分组</span><br></pre></td></tr></table></figure><p>如 <code>(?&lt;=\d)[a-zA-Z]</code> ，若字母<code>前</code>是数字则匹配该字母 ，否则<code>不</code>匹配，即 <code>[a-z]</code> <code>前</code>必须匹配 <code>\d</code></p><h3 id="反后顾"><a href="#反后顾" class="headerlink" title="反后顾"></a>反后顾</h3><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">?&lt;!分组</span><br></pre></td></tr></table></figure><p>如 <code>(?&lt;!\d)[a-zA-Z]</code> ，若字母<code>前</code>是数字则<code>不</code>匹配该字母 ，否则匹配，即 <code>[a-z]</code> <code>前</code>必须<code>不</code>匹配 <code>\d</code></p><h2 id="正向引用"><a href="#正向引用" class="headerlink" title="正向引用"></a>正向引用</h2><p>子匹配可以被引用，使用 <code>\n</code> 访问</p><p>如 <code>abcd&lt;custom-button&gt;link&lt;/custom-button&gt;efg</code> 匹配 <code>custom-button</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">&lt;(custom-button)&gt;.*&lt;/\1&gt;</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;正则表达式，是每个程序员的必备的技能&lt;/p&gt;
&lt;h2 id=&quot;贪婪匹配-和-惰性匹配&quot;&gt;&lt;a href=&quot;#贪婪匹配-和-惰性匹配&quot; class=&quot;headerlink&quot; title=&quot;贪婪匹配 和 惰性匹配&quot;&gt;&lt;/a&gt;贪婪匹配 和 惰性匹配&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;贪婪匹配是尽可能匹配更多的字符&lt;/li&gt;
&lt;li&gt;惰性匹配是尽可能匹配更少的字符&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;惰性匹配是在 &lt;code&gt;*&lt;/code&gt; , &lt;code&gt;+&lt;/code&gt; , &lt;code&gt;{m,}&lt;/code&gt; 后加上 &lt;code&gt;?&lt;/code&gt;&lt;/p&gt;</summary>
    
    
    
    <category term="记录" scheme="https://blog.hal.wang/categories/%E8%AE%B0%E5%BD%95/"/>
    
    
    <category term="正则" scheme="https://blog.hal.wang/tags/%E6%AD%A3%E5%88%99/"/>
    
  </entry>
  
  <entry>
    <title>Vue3 + Vite + Pinia + TS 入门项目搭建</title>
    <link href="https://blog.hal.wang/bc60d1b8/"/>
    <id>https://blog.hal.wang/bc60d1b8/</id>
    <published>2022-03-17T11:43:34.000Z</published>
    <updated>2026-03-18T15:50:10.586Z</updated>
    
    <content type="html"><![CDATA[<p>本文将从零开始搭建一个 <code>Vue3</code> + <code>Vite</code> + <code>Pinia</code> + <code>TS</code> 入门项目</p><p>源码：<span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2hhbC13YW5nL3Z1ZTMtdml0ZS10cy10ZW1wbGF0ZQ==">https://github.com/hal-wang/vue3-vite-ts-template<i class="fa fa-external-link-alt"></i></span></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">git clone https://github.com/hal-wang/vue3-vite-ts-template.git</span><br></pre></td></tr></table></figure><ul><li>Vue3 + Vite</li><li>Pinia: 新的状态管理工具，替代 Vuex</li><li>Vue Router 4: 路由管理</li><li>TS + setup: TS 语法糖写法</li><li>Prettier: 格式化工具</li><li>ESlint: 格式标准工具</li><li>Windi CSS: 功能类优先的 CSS 框架，与 Tailwind CSS 用法相同，但速度更快</li><li>iconify + svg: iconify 是功能丰富的图标框架，加上 svg 文件解析，让你选图标随心所欲</li><li>huskey + lint-staged 每次提交代码校验格式规范</li><li>huskey + commitlint 每次提交代码校验提交消息规范</li></ul><span id="more"></span><p>文中都是用 yarn，如果你使用 npm，可以相应替换</p><h2 id="创建项目"><a href="#创建项目" class="headerlink" title="创建项目"></a>创建项目</h2><ul><li>在特定目录下运行命令</li></ul><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 create @vitejs/app</span><br></pre></td></tr></table></figure><ul><li><p>按提示，输入项目名</p></li><li><p>选择 vue -&gt; vue-ts 模板</p></li><li><p>进入项目，在项目目录下执行</p></li></ul><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 install</span><br></pre></td></tr></table></figure><ul><li>运行项目</li></ul><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 dev</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><p>如果你不需要区分多个环境，可以跳过这部分</p><p>Vite 使用 <code>ESM</code> 的方式访问环境变量，即不再使用 <code>process.env</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="keyword">import</span>.<span class="property">meta</span>.<span class="property">env</span>.<span class="property">VITE_NAME</span></span><br></pre></td></tr></table></figure><p>你可以使用多个环境来用于 开发&#x2F;生产 环境</p><h4 id="环境变量文件"><a href="#环境变量文件" class="headerlink" title="环境变量文件"></a>环境变量文件</h4><p>在项目根目录下创建环境变量文件</p><p>命名格式为 <code>.env.&lt;name&gt;</code>，如 <code>.env.production</code> 和 <code>.env.development</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><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">NODE_ENV=development</span><br><span class="line"># NODE_ENV=production</span><br><span class="line"></span><br><span class="line">VITE_BASE_URL= &#x27;Base api url&#x27;</span><br><span class="line"># more...</span><br></pre></td></tr></table></figure><p>其中 NODE_ENV 值为 <code>development</code> 或 <code>production</code>，对应 开发&#x2F;生产 环境</p><h4 id="使用环境变量"><a href="#使用环境变量" class="headerlink" title="使用环境变量"></a>使用环境变量</h4><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">const</span> url = <span class="keyword">import</span>.<span class="property">meta</span>.<span class="property">env</span>.<span class="property">VITE_BASE_URL</span></span><br></pre></td></tr></table></figure><h4 id="指定环境"><a href="#指定环境" class="headerlink" title="指定环境"></a>指定环境</h4><p>在 <code>package.json</code> 文件的 scripts 命令中，增加参数 <code>--mode &lt;name&gt;</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></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;dev&quot;</span><span class="punctuation">:</span> <span class="string">&quot;vite&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;vue-tsc --noEmit &amp;&amp; vite build&quot;</span><span class="punctuation">,</span></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></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;dev&quot;</span><span class="punctuation">:</span> <span class="string">&quot;vite --mode development&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;dev:prod&quot;</span><span class="punctuation">:</span> <span class="string">&quot;vite --mode production&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;build:dev&quot;</span><span class="punctuation">:</span> <span class="string">&quot;vue-tsc --noEmit &amp;&amp; vite build --mode development&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;build:prod&quot;</span><span class="punctuation">:</span> <span class="string">&quot;vue-tsc --noEmit &amp;&amp; vite build --mode production&quot;</span><span class="punctuation">,</span></span><br></pre></td></tr></table></figure><h4 id="环境变量智能提示"><a href="#环境变量智能提示" class="headerlink" title="环境变量智能提示"></a>环境变量智能提示</h4><p>添加文件 <code>src/types/global</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 添加项目实际需要的内容</span></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">ViteEnv</span> &#123;</span><br><span class="line">  <span class="keyword">readonly</span> <span class="attr">VITE_GLOB_API_PROXY_PREFIX</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="keyword">readonly</span> <span class="attr">VITE_GLOB_API_URL</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="keyword">readonly</span> <span class="attr">VITE_GLOB_PROXY_API_URL</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="keyword">readonly</span> <span class="attr">VITE_PORT</span>: <span class="built_in">number</span>;</span><br><span class="line">  <span class="keyword">readonly</span> <span class="attr">VITE_GLOB_APP_TITLE</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="keyword">readonly</span> <span class="attr">VITE_PUBLIC_PATH</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="keyword">readonly</span> <span class="attr">VITE_DROP_CONSOLE</span>: <span class="built_in">boolean</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_">ImportMetaEnv</span> <span class="keyword">extends</span> <span class="title class_">ViteEnv</span> &#123;</span><br><span class="line">  <span class="attr">__</span>: <span class="built_in">unknown</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_">ImportMeta</span> &#123;</span><br><span class="line">  <span class="keyword">readonly</span> <span class="attr">env</span>: <span class="title class_">ImportMetaEnv</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>添加文件 <code>src/build/env.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></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">wrapperEnv</span>(<span class="params"><span class="attr">envConf</span>: <span class="title class_">Record</span>&lt;<span class="built_in">string</span>, <span class="built_in">any</span>&gt;</span>): <span class="title class_">ViteEnv</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> <span class="attr">ret</span>: <span class="built_in">any</span> = &#123;&#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> envName <span class="keyword">of</span> <span class="title class_">Object</span>.<span class="title function_">keys</span>(envConf)) &#123;</span><br><span class="line">    <span class="keyword">let</span> realName = envConf[envName].<span class="title function_">replace</span>(<span class="regexp">/\\n/g</span>, <span class="string">&#x27;\n&#x27;</span>);</span><br><span class="line">    realName = realName === <span class="string">&#x27;true&#x27;</span> ? <span class="literal">true</span> : realName === <span class="string">&#x27;false&#x27;</span> ? <span class="literal">false</span> : realName;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (envName === <span class="string">&#x27;VITE_PORT&#x27;</span>) &#123;</span><br><span class="line">      realName = <span class="title class_">Number</span>(realName);</span><br><span class="line">    &#125;</span><br><span class="line">    ret[envName] = realName;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> realName === <span class="string">&#x27;string&#x27;</span>) &#123;</span><br><span class="line">      process.<span class="property">env</span>[envName] = realName;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">typeof</span> realName === <span class="string">&#x27;object&#x27;</span>) &#123;</span><br><span class="line">      process.<span class="property">env</span>[envName] = <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(realName);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> ret;</span><br><span class="line">&#125;</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; wrapperEnv &#125; <span class="keyword">from</span> <span class="string">&#x27;/@/build/env&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> viteEnv = <span class="title function_">wrapperEnv</span>(env);</span><br></pre></td></tr></table></figure><h3 id="网络代理"><a href="#网络代理" class="headerlink" title="网络代理"></a>网络代理</h3><p>配置网络代理可以解决开发时的跨域问题，此配置仅开发环境有效，生产环境应配合 nginx 等实现转发</p><p>如果你的项目不需要与后端交互，或无需考虑跨域问题，可忽略此部分</p><h4 id="创建帮助函数"><a href="#创建帮助函数" class="headerlink" title="创建帮助函数"></a>创建帮助函数</h4><p>添加文件 <code>src/build/proxy.ts</code></p><p>创建 <code>createProxy</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> <span class="keyword">type</span> &#123; <span class="title class_">ProxyOptions</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;vite&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">ProxyItem</span> = [<span class="built_in">string</span>, <span class="built_in">string</span>];</span><br><span class="line"><span class="keyword">type</span> <span class="title class_">ProxyList</span> = <span class="title class_">ProxyItem</span>[];</span><br><span class="line"><span class="keyword">type</span> <span class="title class_">ProxyTargetList</span> = <span class="title class_">Record</span>&lt;<span class="built_in">string</span>, <span class="title class_">ProxyOptions</span>&gt;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> httpsRE = <span class="regexp">/^https:\/\//</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">createProxy</span>(<span class="params"><span class="attr">list</span>: <span class="title class_">ProxyList</span> = []</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> <span class="attr">ret</span>: <span class="title class_">ProxyTargetList</span> = &#123;&#125;;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> [prefix, target] <span class="keyword">of</span> list) &#123;</span><br><span class="line">    <span class="keyword">const</span> isHttps = httpsRE.<span class="title function_">test</span>(target);</span><br><span class="line">    ret[prefix] = &#123;</span><br><span class="line">      <span class="attr">target</span>: target,</span><br><span class="line">      <span class="attr">changeOrigin</span>: <span class="literal">true</span>,</span><br><span class="line">      <span class="attr">ws</span>: <span class="literal">true</span>,</span><br><span class="line">      <span class="attr">rewrite</span>: <span class="function">(<span class="params">path</span>) =&gt;</span> path.<span class="title function_">replace</span>(<span class="keyword">new</span> <span class="title class_">RegExp</span>(<span class="string">`^<span class="subst">$&#123;prefix&#125;</span>`</span>), <span class="string">&#x27;&#x27;</span>),</span><br><span class="line">      ...(isHttps ? &#123; <span class="attr">secure</span>: <span class="literal">false</span> &#125; : &#123;&#125;),</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> ret;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h4><p>修改 <code>vite.config.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="keyword">const</span> viteEnv = <span class="title function_">wrapperEnv</span>(env);</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="attr">server</span>: &#123;</span><br><span class="line">  <span class="attr">host</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="attr">port</span>: <span class="variable constant_">VITE_PORT</span>,</span><br><span class="line">  <span class="attr">proxy</span>: <span class="title function_">createProxy</span>([[<span class="variable constant_">VITE_GLOB_API_PROXY_PREFIX</span>, <span class="variable constant_">VITE_GLOB_PROXY_API_URL</span>]]),</span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure><p><code>VITE_GLOB_API_PROXY_PREFIX</code> 为代理的路由段</p><h4 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h4><p>开发环境调用接口时，需要增加 <code>/api</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_">get</span>(<span class="string">&#x27;/api/user&#x27;</span>)</span><br></pre></td></tr></table></figure><p>发布环境不能加 <code>/api</code> 开头，因此你需要封装网络访问，以防止每次请求都判断运行环境</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span>(dev)&#123;</span><br><span class="line">  <span class="title function_">get</span>(<span class="string">&#x27;/api&#x27;</span> + url)</span><br><span class="line">&#125;<span class="keyword">else</span>&#123;</span><br><span class="line">  <span class="title function_">get</span>(baseurl + url)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="路径别名"><a href="#路径别名" class="headerlink" title="路径别名"></a>路径别名</h3><p>配置路径别名后可以使用路径如 <code>/@/views/index.ts</code>, <code>/@/components/comp.ts</code> 等</p><ul><li>在 <code>vite.config.ts</code> 文件中，增加内容</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; resolve &#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="keyword">function</span> <span class="title function_">pathResolve</span>(<span class="params"><span class="attr">dir</span>: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">resolve</span>(process.<span class="title function_">cwd</span>(), <span class="string">&#x27;.&#x27;</span>, dir);</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 function_">defineConfig</span>(&#123;</span><br><span class="line">  <span class="attr">resolve</span>: &#123;</span><br><span class="line">    <span class="attr">alias</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">find</span>: <span class="regexp">/\/@\//</span>,</span><br><span class="line">        <span class="attr">replacement</span>: <span class="title function_">pathResolve</span>(<span class="string">&quot;src&quot;</span>) + <span class="string">&quot;/&quot;</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">find</span>: <span class="regexp">/\/#\//</span>,</span><br><span class="line">        <span class="attr">replacement</span>: <span class="title function_">pathResolve</span>(<span class="string">&quot;types&quot;</span>) + <span class="string">&quot;/&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><p><code>/@/</code> 用于模块，<code>/#/</code> 用于类型</p><ul><li><code>tsconfig.json</code> 中增加 <code>compilerOptions.paths</code> 和 <code>compilerOptions.baseUrl</code>，用于支持 TS 语法检查</li></ul><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;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 class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;/#/*&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;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 class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="VSCode-配置和断点调试"><a href="#VSCode-配置和断点调试" class="headerlink" title="VSCode 配置和断点调试"></a>VSCode 配置和断点调试</h3><p>配置好编辑器，能让开发更顺畅</p><h4 id="基本配置-1"><a href="#基本配置-1" class="headerlink" title="基本配置"></a>基本配置</h4><p>创建 <code>.vscode/settings.json</code> 文件，用于存储 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><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;typescript.tsdk&quot;</span><span class="punctuation">:</span> <span class="string">&quot;./node_modules/typescript/lib&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;volar.tsPlugin&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;volar.tsPluginStatus&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;npm.packageManager&quot;</span><span class="punctuation">:</span> <span class="string">&quot;pnpm&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;editor.tabSize&quot;</span><span class="punctuation">:</span> <span class="number">2</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;esbenp.prettier-vscode&quot;</span><span class="punctuation">,</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><br><span class="line">  <span class="attr">&quot;search.exclude&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;**/node_modules&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;**/*.log&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;**/*.log*&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;**/bower_components&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;**/dist&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;**/elehukouben&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;**/.git&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;**/.gitignore&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;**/.svn&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;**/.DS_Store&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;**/.idea&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;**/.vscode&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;**/yarn.lock&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;**/tmp&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;out&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;dist&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;node_modules&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;CHANGELOG.md&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;examples&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;res&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;screenshots&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;yarn-error.log&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;**/.yarn&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;files.exclude&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;**/.cache&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;**/.editorconfig&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;**/.eslintcache&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;**/bower_components&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;**/.idea&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;**/tmp&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;**/.git&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;**/.svn&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;**/.hg&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;**/CVS&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;**/.DS_Store&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;files.watcherExclude&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;**/.git/objects/**&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;**/.git/subtree-cache/**&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;**/.vscode/**&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;**/node_modules/**&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;**/tmp/**&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;**/bower_components/**&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;**/dist/**&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;**/yarn.lock&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;stylelint.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;stylelint.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;path-intellisense.mappings&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="string">&quot;$&#123;workspaceRoot&#125;/src&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;[javascriptreact]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;esbenp.prettier-vscode&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;[typescript]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;esbenp.prettier-vscode&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;[typescriptreact]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;esbenp.prettier-vscode&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;[html]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;esbenp.prettier-vscode&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;[css]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;esbenp.prettier-vscode&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;[less]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;esbenp.prettier-vscode&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;[scss]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;esbenp.prettier-vscode&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;[markdown]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;esbenp.prettier-vscode&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;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;[vue]&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;editor.defaultFormatter&quot;</span><span class="punctuation">:</span> <span class="string">&quot;johnsoncodehk.volar&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><h4 id="F5-断点调试"><a href="#F5-断点调试" class="headerlink" title="F5 断点调试"></a>F5 断点调试</h4><ul><li>创建 <code>.vscode/launch.json</code> 文件</li></ul><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;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;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;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;edge&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;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;edge&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;http://localhost:3100&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;webRoot&quot;</span><span class="punctuation">:</span> <span class="string">&quot;$&#123;workspaceFolder&#125;/src&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;sourceMaps&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">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><code>edge</code> 可换为 <code>Chrome</code> 或其他浏览器</p><ul><li>编辑 <code>vite.config.ts</code> 文件，增加</li></ul><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">build</span>: &#123;</span><br><span class="line">  <span class="attr">sourcemap</span>: <span class="keyword">import</span>.<span class="property">meta</span>.<span class="property">env</span>.<span class="property">DEV</span>,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>即 development 环境下启用 source map，开启后调试器才能正确找到执行语句所在代码位置</p><p><code>vite.config.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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; defineConfig &#125; <span class="keyword">from</span> <span class="string">&#x27;vite&#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">build</span>: &#123;</span><br><span class="line">    <span class="attr">sourcemap</span>: <span class="keyword">import</span>.<span class="property">meta</span>.<span class="property">env</span>.<span class="property">DEV</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="添加-Pinia"><a href="#添加-Pinia" class="headerlink" title="添加 Pinia"></a>添加 Pinia</h2><p>Pinia 是 Vue3 推荐的状态管理工具，对 TS 的支持很完善，用起来也比较舒服</p><h3 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h3><ul><li>在项目下运行</li></ul><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 pinia</span><br></pre></td></tr></table></figure><h3 id="创建文件"><a href="#创建文件" class="headerlink" title="创建文件"></a>创建文件</h3><ul><li><p>在 <code>src</code> 下创建 store 文件夹，在 store 文件夹下创建 <code>index.ts</code> 文件，便于统一管理</p></li><li><p><code>index.ts</code> 文件中添加代码</p></li></ul><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="keyword">import</span> <span class="keyword">type</span> &#123; <span class="title class_">App</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;vue&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; createPinia &#125; <span class="keyword">from</span> <span class="string">&#x27;pinia&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> store = <span class="title function_">createPinia</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_">setupStore</span>(<span class="params"><span class="attr">app</span>: <span class="title class_">App</span>&lt;<span class="title class_">Element</span>&gt;</span>) &#123;</span><br><span class="line">  app.<span class="title function_">use</span>(store);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> &#123; store &#125;;</span><br></pre></td></tr></table></figure><ul><li>在 <code>main.ts</code> 中修改代码如</li></ul><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; createApp &#125; <span class="keyword">from</span> <span class="string">&quot;vue&quot;</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">&quot;./App.vue&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; setupStore &#125; <span class="keyword">from</span> <span class="string">&quot;/@/store&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> app = <span class="title function_">createApp</span>(<span class="title class_">App</span>);</span><br><span class="line"><span class="title function_">setupStore</span>(app);</span><br><span class="line">app.<span class="title function_">mount</span>(<span class="string">&quot;#app&quot;</span>);</span><br></pre></td></tr></table></figure><ul><li>在 store 文件夹下创建 modules 文件夹，此后新增模块可以在这个文件夹中统一管理，如 <code>app.ts</code></li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; defineStore &#125; <span class="keyword">from</span> <span class="string">&#x27;pinia&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; store &#125; <span class="keyword">from</span> <span class="string">&#x27;/@/store&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">AppState</span> &#123;</span><br><span class="line">  <span class="attr">count</span>: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> useAppStore = <span class="title function_">defineStore</span>(&#123;</span><br><span class="line">  <span class="attr">id</span>: <span class="string">&#x27;app&#x27;</span>,</span><br><span class="line">  <span class="attr">state</span>: (): <span class="function"><span class="params">AppState</span> =&gt;</span> (&#123;</span><br><span class="line">    <span class="attr">count</span>: <span class="number">0</span>,</span><br><span class="line">  &#125;),</span><br><span class="line">  <span class="attr">getters</span>: &#123;</span><br><span class="line">    <span class="title function_">getCount</span>(): <span class="built_in">number</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">  &#125;,</span><br><span class="line">  <span class="attr">actions</span>: &#123;</span><br><span class="line">    <span class="title function_">setCount</span>(<span class="params"><span class="attr">val</span>: <span class="built_in">number</span></span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">count</span> = val;</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">function</span> <span class="title function_">useAppStoreWithOut</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">useAppStore</span>(store);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="使用-1"><a href="#使用-1" class="headerlink" title="使用"></a>使用</h3><p>在需要使用的地方</p><ul><li>若在 setup 函数中你可以这样</li></ul><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">const</span> appStore = <span class="title function_">useAppStore</span>();</span><br><span class="line"><span class="keyword">const</span> count = appStore.<span class="title function_">getCount</span>();</span><br></pre></td></tr></table></figure><ul><li>在非 setup 函数中你可以这样</li></ul><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">const</span> appStore = <span class="title function_">useAppStoreWithOut</span>();</span><br><span class="line"><span class="keyword">const</span> count = appStore.<span class="title function_">getCount</span>();</span><br></pre></td></tr></table></figure><ul><li>同时有 <code>get</code> 和 <code>set</code> 的 <code>computed</code></li></ul><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> appStore = <span class="title function_">useAppStore</span>();</span><br><span class="line"><span class="keyword">const</span> count = <span class="title function_">computed</span>(&#123;</span><br><span class="line">  <span class="attr">get</span>: <span class="function">() =&gt;</span> appStore.<span class="property">getCount</span>,</span><br><span class="line">  <span class="attr">set</span>: <span class="function">(<span class="params"><span class="attr">val</span>: <span class="built_in">number</span></span>) =&gt;</span> appStore.<span class="title function_">setCount</span>(val),</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="简单介绍"><a href="#简单介绍" class="headerlink" title="简单介绍"></a>简单介绍</h3><h4 id="getters"><a href="#getters" class="headerlink" title="getters"></a>getters</h4><p>pinia 中的 getters 和 vuex 中的 getters 功能相同</p><h4 id="actions"><a href="#actions" class="headerlink" title="actions"></a>actions</h4><ul><li>pinia 中 actions 可以不依赖 mutations，能够在 action 中直接修改状态值</li><li>pinia 中的 actions 支持多个参数</li><li>pinia 中的 actions 支持异步函数</li></ul><h2 id="添加路由"><a href="#添加路由" class="headerlink" title="添加路由"></a>添加路由</h2><p>如果你的网站不涉及多页面跳转，可以忽略此部分内容</p><h3 id="安装-1"><a href="#安装-1" class="headerlink" title="安装"></a>安装</h3><ul><li>在项目下运行</li></ul><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 vue-router</span><br></pre></td></tr></table></figure><h3 id="使用-2"><a href="#使用-2" class="headerlink" title="使用"></a>使用</h3><ul><li>在 <code>src</code> 下创建 router 文件夹，在 router 文件夹下创建 <code>index.ts</code> 文件和 <code>modules</code> 文件夹，便于统一管理</li></ul><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="keyword">type</span> &#123; <span class="title class_">App</span> &#125; <span class="keyword">from</span> <span class="string">&quot;vue&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; createRouter, createWebHashHistory, <span class="title class_">RouteRecordRaw</span> &#125; <span class="keyword">from</span> <span class="string">&quot;vue-router&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> modules = <span class="keyword">import</span>.<span class="property">meta</span>.<span class="title function_">globEager</span>(<span class="string">&quot;./modules/**/*.ts&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="attr">routeModuleList</span>: <span class="title class_">RouteRecordRaw</span>[] = [];</span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">keys</span>(modules).<span class="title function_">forEach</span>(<span class="function">(<span class="params">key</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> mod = modules[key].<span class="property">default</span> || &#123;&#125;;</span><br><span class="line">  <span class="keyword">const</span> modList = <span class="title class_">Array</span>.<span class="title function_">isArray</span>(mod) ? [...mod] : [mod];</span><br><span class="line">  routeModuleList.<span class="title function_">push</span>(...modList);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// app router</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> router = <span class="title function_">createRouter</span>(&#123;</span><br><span class="line">  <span class="attr">history</span>: <span class="title function_">createWebHashHistory</span>(<span class="keyword">import</span>.<span class="property">meta</span>.<span class="property">env</span>.<span class="property">VITE_PUBLIC_PATH</span> <span class="keyword">as</span> <span class="built_in">string</span>),</span><br><span class="line">  <span class="attr">routes</span>: routeModuleList,</span><br><span class="line">  <span class="attr">strict</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="attr">scrollBehavior</span>: <span class="function">() =&gt;</span> (&#123; <span class="attr">left</span>: <span class="number">0</span>, <span class="attr">top</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="comment">// config router</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">setupRouter</span>(<span class="params"><span class="attr">app</span>: <span class="title class_">App</span>&lt;<span class="title class_">Element</span>&gt;</span>) &#123;</span><br><span class="line">  app.<span class="title function_">use</span>(router);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>以上代码可以动态加载 <code>router/modules</code> 中的路由文件，此后各个模块的路由可以在此文件夹下创建</p><ul><li>在 <code>modules</code> 文件夹中创建路由文件，如 <code>home.ts</code></li></ul><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; <span class="title class_">RouteRecordRaw</span> &#125; <span class="keyword">from</span> <span class="string">&quot;vue-router&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="attr">home</span>: <span class="title class_">RouteRecordRaw</span> = &#123;</span><br><span class="line">  <span class="attr">path</span>: <span class="string">&quot;/home&quot;</span>,</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&quot;Home&quot;</span>,</span><br><span class="line">  <span class="attr">component</span>: <span class="function">() =&gt;</span> <span class="keyword">import</span>(<span class="string">&quot;/@/views/home/index.vue&quot;</span>),</span><br><span class="line">  <span class="attr">meta</span>: &#123;</span><br><span class="line">    <span class="attr">title</span>: <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">export</span> <span class="keyword">default</span> home;</span><br></pre></td></tr></table></figure><p>上面示例同时需要创建 <code>views/home/index.vue</code> 文件</p><ul><li>在 <code>main.ts</code> 中新增代码</li></ul><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_">setupRouter</span>(app);</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; createApp &#125; <span class="keyword">from</span> <span class="string">&quot;vue&quot;</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">&quot;./App.vue&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; setupRouter &#125; <span class="keyword">from</span> <span class="string">&quot;/@/router&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> app = <span class="title function_">createApp</span>(<span class="title class_">App</span>);</span><br><span class="line"><span class="title function_">setupRouter</span>(app);</span><br><span class="line">app.<span class="title function_">mount</span>(<span class="string">&quot;#app&quot;</span>);</span><br></pre></td></tr></table></figure><ul><li>在 <code>src/App.vue</code> 中添加</li></ul><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">router-view</span>&gt;</span><span class="tag">&lt;/<span class="name">router-view</span>&gt;</span></span><br></pre></td></tr></table></figure><p>如</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">template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">router-view</span>&gt;</span><span class="tag">&lt;/<span class="name">router-view</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>router-view</code> 是用来渲染路由对应的页面组件</p><h3 id="增加-nProgress"><a href="#增加-nProgress" class="headerlink" title="增加 nProgress"></a>增加 nProgress</h3><p>配置 nProgress 可以让页面顶部有个进度条</p><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></pre></td><td class="code"><pre><span class="line">yarn add nprogress</span><br><span class="line">yarn add @types/nprogress --dev</span><br></pre></td></tr></table></figure><h4 id="增加路由钩子"><a href="#增加路由钩子" class="headerlink" title="增加路由钩子"></a>增加路由钩子</h4><ul><li>创建文件 <code>src/router/guard.ts</code></li></ul><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; <span class="title class_">Router</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;vue-router&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> nProgress <span class="keyword">from</span> <span class="string">&#x27;nprogress&#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_">createProgressGuard</span>(<span class="params"><span class="attr">router</span>: <span class="title class_">Router</span></span>) &#123;</span><br><span class="line">  router.<span class="title function_">beforeEach</span>(<span class="title function_">async</span> () =&gt; &#123;</span><br><span class="line">    nProgress.<span class="title function_">start</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">  router.<span class="title function_">afterEach</span>(<span class="title function_">async</span> () =&gt; &#123;</span><br><span class="line">    nProgress.<span class="title function_">done</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">&#125;</span><br></pre></td></tr></table></figure><ul><li>修改文件 <code>src/router/index.ts</code> 中的 <code>setupRouter</code> 函数，增加以下代码</li></ul><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_">createProgressGuard</span>(router);</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="comment">// config router</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">setupRouter</span>(<span class="params"><span class="attr">app</span>: <span class="title class_">App</span>&lt;<span class="title class_">Element</span>&gt;</span>) &#123;</span><br><span class="line">  app.<span class="title function_">use</span>(router);</span><br><span class="line">  <span class="title function_">createProgressGuard</span>(router);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="设置进度条样式"><a href="#设置进度条样式" class="headerlink" title="设置进度条样式"></a>设置进度条样式</h4><ul><li>创建文件 <code>src/design/index.less</code></li></ul><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><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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-id">#nprogress</span> &#123;</span><br><span class="line">  <span class="attribute">pointer-events</span>: none;</span><br><span class="line"></span><br><span class="line">  <span class="selector-class">.bar</span> &#123;</span><br><span class="line">    <span class="attribute">position</span>: fixed;</span><br><span class="line">    <span class="attribute">top</span>: <span class="number">0</span>;</span><br><span class="line">    <span class="attribute">left</span>: <span class="number">0</span>;</span><br><span class="line">    <span class="attribute">z-index</span>: <span class="number">99999</span>;</span><br><span class="line">    <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">2px</span>;</span><br><span class="line">    <span class="attribute">background-color</span>: <span class="variable">@primary-color</span>;</span><br><span class="line">    <span class="attribute">opacity</span>: <span class="number">0.75</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>@primary-color</code> 是后面 <code>增加 stylelint + postcss + less</code> 部分增加的 <code>less</code> 变量</p><ul><li><code>main.ts</code> 中引入</li></ul><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="string">&#x27;/@/design/index.less&#x27;</span>;</span><br></pre></td></tr></table></figure><h2 id="添加-Prettier"><a href="#添加-Prettier" class="headerlink" title="添加 Prettier"></a>添加 Prettier</h2><p>Prettier 用于格式化代码</p><h3 id="安装-2"><a href="#安装-2" class="headerlink" title="安装"></a>安装</h3><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">yarn add prettier --dev</span><br></pre></td></tr></table></figure><h3 id="配置-1"><a href="#配置-1" class="headerlink" title="配置"></a>配置</h3><ul><li>项目目录下创建 <code>prettier.config.js</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></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">100</span>,</span><br><span class="line">  <span class="attr">semi</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="attr">vueIndentScriptAndStyle</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="attr">singleQuote</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="attr">trailingComma</span>: <span class="string">&#x27;all&#x27;</span>,</span><br><span class="line">  <span class="attr">proseWrap</span>: <span class="string">&#x27;never&#x27;</span>,</span><br><span class="line">  <span class="attr">htmlWhitespaceSensitivity</span>: <span class="string">&#x27;strict&#x27;</span>,</span><br><span class="line">  <span class="attr">endOfLine</span>: <span class="string">&#x27;auto&#x27;</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><ul><li>项目目录下创建 <code>.prettierignore</code> 文件，用于配置那些文件需要忽略检查</li></ul><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">/dist/*</span><br><span class="line">.local</span><br><span class="line">.output.js</span><br><span class="line">/node_modules/**</span><br><span class="line"></span><br><span class="line">**/*.svg</span><br><span class="line">**/*.sh</span><br><span class="line"></span><br><span class="line">/public/*</span><br></pre></td></tr></table></figure><h3 id="增加格式化脚本"><a href="#增加格式化脚本" class="headerlink" title="增加格式化脚本"></a>增加格式化脚本</h3><p>在 package.json 中的 scripts 中新增</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;lint:prettier&quot;</span><span class="punctuation">:</span> <span class="string">&quot;prettier --write  \&quot;src/**/*.&#123;js,json,tsx,css,less,scss,vue,html,md&#125;\&quot;&quot;</span><span class="punctuation">,</span></span><br></pre></td></tr></table></figure><p>之后运行 <code>npm run lint:prettier</code> 即可格式化全部代码</p><h2 id="添加-ESlint"><a href="#添加-ESlint" class="headerlink" title="添加 ESlint"></a>添加 ESlint</h2><p>ESlint 可以规范代码格式</p><h3 id="安装-3"><a href="#安装-3" class="headerlink" title="安装"></a>安装</h3><ul><li>在项目目录下执行以下命令安装插件</li></ul><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">yarn add @typescript-eslint/eslint-plugin --dev</span><br><span class="line">yarn add @typescript-eslint/parser --dev</span><br><span class="line">yarn add eslint --dev</span><br><span class="line">yarn add eslint-plugin-vue --dev</span><br><span class="line">yarn add vue-eslint-parser --dev</span><br></pre></td></tr></table></figure><ul><li>如果配合 prettier ，也需要安装</li></ul><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">yarn add eslint-config-prettier --dev</span><br><span class="line">yarn add eslint-plugin-prettier --dev</span><br></pre></td></tr></table></figure><ul><li>如果报错 <code>The engine &quot;node&quot; is incompatible with this module. Expected version &quot;&gt;= 16.9.0&quot;. Got &quot;***&quot;</code>，执行以下语句再重试</li></ul><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 config set ignore-engines true</span><br></pre></td></tr></table></figure><h3 id="配置-2"><a href="#配置-2" class="headerlink" title="配置"></a>配置</h3><ul><li>项目目录下创建 <code>.eslintrc.js</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><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></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">browser</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">node</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">es6</span>: <span class="literal">true</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">parser</span>: <span class="string">&#x27;vue-eslint-parser&#x27;</span>,</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;@typescript-eslint/parser&#x27;</span>,</span><br><span class="line">    <span class="attr">ecmaVersion</span>: <span class="number">2020</span>,</span><br><span class="line">    <span class="attr">sourceType</span>: <span class="string">&#x27;module&#x27;</span>,</span><br><span class="line">    <span class="attr">jsxPragma</span>: <span class="string">&#x27;React&#x27;</span>,</span><br><span class="line">    <span class="attr">ecmaFeatures</span>: &#123;</span><br><span class="line">      <span class="attr">jsx</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="attr">extends</span>: [</span><br><span class="line">    <span class="string">&#x27;plugin:vue/vue3-recommended&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;plugin:@typescript-eslint/recommended&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;prettier&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;plugin:prettier/recommended&#x27;</span>,</span><br><span class="line">  ],</span><br><span class="line">  <span class="attr">rules</span>: &#123;</span><br><span class="line">    <span class="string">&#x27;vue/script-setup-uses-vars&#x27;</span>: <span class="string">&#x27;error&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;@typescript-eslint/ban-ts-ignore&#x27;</span>: <span class="string">&#x27;off&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;@typescript-eslint/explicit-function-return-type&#x27;</span>: <span class="string">&#x27;off&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;@typescript-eslint/no-explicit-any&#x27;</span>: <span class="string">&#x27;off&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;@typescript-eslint/no-var-requires&#x27;</span>: <span class="string">&#x27;off&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;@typescript-eslint/no-empty-function&#x27;</span>: <span class="string">&#x27;off&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;vue/custom-event-name-casing&#x27;</span>: <span class="string">&#x27;off&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;no-use-before-define&#x27;</span>: <span class="string">&#x27;off&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;@typescript-eslint/no-use-before-define&#x27;</span>: <span class="string">&#x27;off&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;@typescript-eslint/ban-ts-comment&#x27;</span>: <span class="string">&#x27;off&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;@typescript-eslint/ban-types&#x27;</span>: <span class="string">&#x27;off&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;@typescript-eslint/no-non-null-assertion&#x27;</span>: <span class="string">&#x27;off&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;@typescript-eslint/explicit-module-boundary-types&#x27;</span>: <span class="string">&#x27;off&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;@typescript-eslint/no-unused-vars&#x27;</span>: [</span><br><span class="line">      <span class="string">&#x27;error&#x27;</span>,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">argsIgnorePattern</span>: <span class="string">&#x27;^_&#x27;</span>,</span><br><span class="line">        <span class="attr">varsIgnorePattern</span>: <span class="string">&#x27;^_&#x27;</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">    ],</span><br><span class="line">    <span class="string">&#x27;no-unused-vars&#x27;</span>: [</span><br><span class="line">      <span class="string">&#x27;error&#x27;</span>,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">argsIgnorePattern</span>: <span class="string">&#x27;^_&#x27;</span>,</span><br><span class="line">        <span class="attr">varsIgnorePattern</span>: <span class="string">&#x27;^_&#x27;</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">    ],</span><br><span class="line">    <span class="string">&#x27;space-before-function-paren&#x27;</span>: <span class="string">&#x27;off&#x27;</span>,</span><br><span class="line"></span><br><span class="line">    <span class="string">&#x27;vue/attributes-order&#x27;</span>: <span class="string">&#x27;off&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;vue/one-component-per-file&#x27;</span>: <span class="string">&#x27;off&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;vue/html-closing-bracket-newline&#x27;</span>: <span class="string">&#x27;off&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;vue/max-attributes-per-line&#x27;</span>: <span class="string">&#x27;off&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;vue/multiline-html-element-content-newline&#x27;</span>: <span class="string">&#x27;off&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;vue/singleline-html-element-content-newline&#x27;</span>: <span class="string">&#x27;off&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;vue/attribute-hyphenation&#x27;</span>: <span class="string">&#x27;off&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;vue/require-default-prop&#x27;</span>: <span class="string">&#x27;off&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;vue/require-explicit-emits&#x27;</span>: <span class="string">&#x27;off&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;vue/html-self-closing&#x27;</span>: [</span><br><span class="line">      <span class="string">&#x27;error&#x27;</span>,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">html</span>: &#123;</span><br><span class="line">          <span class="attr">void</span>: <span class="string">&#x27;always&#x27;</span>,</span><br><span class="line">          <span class="attr">normal</span>: <span class="string">&#x27;never&#x27;</span>,</span><br><span class="line">          <span class="attr">component</span>: <span class="string">&#x27;always&#x27;</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="attr">svg</span>: <span class="string">&#x27;always&#x27;</span>,</span><br><span class="line">        <span class="attr">math</span>: <span class="string">&#x27;always&#x27;</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">    ],</span><br><span class="line">    <span class="string">&#x27;vue/multi-word-component-names&#x27;</span>: <span class="string">&#x27;off&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><ul><li>项目目录下创建 <code>.eslintignore</code> 文件，用于配置那些文件需要忽略检查</li></ul><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></pre></td><td class="code"><pre><span class="line">*.sh</span><br><span class="line">node_modules</span><br><span class="line">*.md</span><br><span class="line">*.woff</span><br><span class="line">*.ttf</span><br><span class="line">.vscode</span><br><span class="line">.idea</span><br><span class="line">dist</span><br><span class="line">/public</span><br><span class="line">/docs</span><br><span class="line">.local</span><br><span class="line">/bin</span><br><span class="line">Dockerfile</span><br></pre></td></tr></table></figure><h3 id="增加检查脚本"><a href="#增加检查脚本" class="headerlink" title="增加检查脚本"></a>增加检查脚本</h3><p>在 package.json 中的 scripts 中新增</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;lint:eslint&quot;</span><span class="punctuation">:</span> <span class="string">&quot;eslint --cache --max-warnings 0  \&quot;&#123;src,mock&#125;/**/*.&#123;vue,ts,tsx&#125;\&quot; --fix&quot;</span><span class="punctuation">,</span></span><br></pre></td></tr></table></figure><p>之后运行 <code>npm run lint:eslint</code> 即可检查全部代码是否有不规范的地方</p><h2 id="增加-Windi-CSS"><a href="#增加-Windi-CSS" class="headerlink" title="增加 Windi CSS"></a>增加 Windi CSS</h2><p>Windi CSS 是一个功能类优先的 CSS 框架，与 Tailwind CSS 用法相同，但速度更快</p><h3 id="安装-4"><a href="#安装-4" class="headerlink" title="安装"></a>安装</h3><ul><li>在项目目录下执行以下命令安装插件</li></ul><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">yarn add windicss --dev</span><br><span class="line">yarn add vite-plugin-windicss --dev</span><br></pre></td></tr></table></figure><h3 id="配置-3"><a href="#配置-3" class="headerlink" title="配置"></a>配置</h3><ul><li>修改 <code>vite.config.ts</code> 文件，增加如下代码</li></ul><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">plugins: [WindiCSS()],</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; defineConfig &#125; <span class="keyword">from</span> <span class="string">&#x27;vite&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">WindiCSS</span> <span class="keyword">from</span> <span class="string">&#x27;vite-plugin-windicss&#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">plugins</span>: [<span class="title class_">WindiCSS</span>()],</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><ul><li>修改 <code>main.ts</code> 文件，增加如下代码</li></ul><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="string">&#x27;virtual:windi.css&#x27;</span></span><br></pre></td></tr></table></figure><ul><li>增加 <code>windi.config.ts</code> 文件</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; defineConfig &#125; <span class="keyword">from</span> <span class="string">&#x27;vite-plugin-windicss&#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">darkMode</span>: <span class="string">&#x27;class&#x27;</span>,</span><br><span class="line">  <span class="attr">plugins</span>: [<span class="title function_">createEnterPlugin</span>()],</span><br><span class="line">  <span class="attr">theme</span>: &#123;</span><br><span class="line">    <span class="attr">extend</span>: &#123;</span><br><span class="line">      <span class="attr">zIndex</span>: &#123;</span><br><span class="line">        <span class="string">&#x27;-1&#x27;</span>: <span class="string">&#x27;-1&#x27;</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="attr">colors</span>: &#123;</span><br><span class="line">        <span class="attr">primary</span>: <span class="string">&#x27;#0084f4&#x27;</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="attr">screens</span>: &#123;</span><br><span class="line">        <span class="attr">sm</span>: <span class="string">&#x27;576px&#x27;</span>,</span><br><span class="line">        <span class="attr">md</span>: <span class="string">&#x27;768px&#x27;</span>,</span><br><span class="line">        <span class="attr">lg</span>: <span class="string">&#x27;992px&#x27;</span>,</span><br><span class="line">        <span class="attr">xl</span>: <span class="string">&#x27;1200px&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;2xl&#x27;</span>: <span class="string">&#x27;1600px&#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"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">createEnterPlugin</span>(<span class="params">maxOutput = <span class="number">6</span></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">createCss</span> = (<span class="params"><span class="attr">index</span>: <span class="built_in">number</span>, d = <span class="string">&#x27;x&#x27;</span></span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> upd = d.<span class="title function_">toUpperCase</span>();</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      [<span class="string">`*&gt; .enter-<span class="subst">$&#123;d&#125;</span>:nth-child(<span class="subst">$&#123;index&#125;</span>)`</span>]: &#123;</span><br><span class="line">        <span class="attr">transform</span>: <span class="string">`translate<span class="subst">$&#123;upd&#125;</span>(50px)`</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">      [<span class="string">`*&gt; .-enter-<span class="subst">$&#123;d&#125;</span>:nth-child(<span class="subst">$&#123;index&#125;</span>)`</span>]: &#123;</span><br><span class="line">        <span class="attr">transform</span>: <span class="string">`translate<span class="subst">$&#123;upd&#125;</span>(-50px)`</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">      [<span class="string">`* &gt; .enter-<span class="subst">$&#123;d&#125;</span>:nth-child(<span class="subst">$&#123;index&#125;</span>),* &gt; .-enter-<span class="subst">$&#123;d&#125;</span>:nth-child(<span class="subst">$&#123;index&#125;</span>)`</span>]: &#123;</span><br><span class="line">        <span class="string">&#x27;z-index&#x27;</span>: <span class="string">`<span class="subst">$&#123;<span class="number">10</span> - index&#125;</span>`</span>,</span><br><span class="line">        <span class="attr">opacity</span>: <span class="string">&#x27;0&#x27;</span>,</span><br><span class="line">        <span class="attr">animation</span>: <span class="string">`enter-<span class="subst">$&#123;d&#125;</span>-animation 0.4s ease-in-out 0.3s`</span>,</span><br><span class="line">        <span class="string">&#x27;animation-fill-mode&#x27;</span>: <span class="string">&#x27;forwards&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;animation-delay&#x27;</span>: <span class="string">`<span class="subst">$&#123;(index * <span class="number">1</span>) / <span class="number">10</span>&#125;</span>s`</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">const</span> <span class="title function_">handler</span> = (<span class="params">&#123; addBase &#125;</span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> addRawCss = &#123;&#125;;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> index = <span class="number">1</span>; index &lt; maxOutput; index++) &#123;</span><br><span class="line">      <span class="title class_">Object</span>.<span class="title function_">assign</span>(addRawCss, &#123;</span><br><span class="line">        ...<span class="title function_">createCss</span>(index, <span class="string">&#x27;x&#x27;</span>),</span><br><span class="line">        ...<span class="title function_">createCss</span>(index, <span class="string">&#x27;y&#x27;</span>),</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="title function_">addBase</span>(&#123;</span><br><span class="line">      ...addRawCss,</span><br><span class="line">      [<span class="string">`@keyframes enter-x-animation`</span>]: &#123;</span><br><span class="line">        <span class="attr">to</span>: &#123;</span><br><span class="line">          <span class="attr">opacity</span>: <span class="string">&#x27;1&#x27;</span>,</span><br><span class="line">          <span class="attr">transform</span>: <span class="string">&#x27;translateX(0)&#x27;</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">      &#125;,</span><br><span class="line">      [<span class="string">`@keyframes enter-y-animation`</span>]: &#123;</span><br><span class="line">        <span class="attr">to</span>: &#123;</span><br><span class="line">          <span class="attr">opacity</span>: <span class="string">&#x27;1&#x27;</span>,</span><br><span class="line">          <span class="attr">transform</span>: <span class="string">&#x27;translateY(0)&#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">  <span class="keyword">return</span> &#123; handler &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="增加-stylelint-postcss-less"><a href="#增加-stylelint-postcss-less" class="headerlink" title="增加 stylelint + postcss + less"></a>增加 stylelint + postcss + less</h2><ul><li>stylelint 是一个现代的、强大的 CSS 检测工具，用这个比 eslint 检查 css 更强大</li><li>postcss 是一个使 CSS 更容易，更灵活，更快速工作的工具</li><li>less 是一个 CSS 预处理器，便于管理和重用样式表</li></ul><h3 id="安装-5"><a href="#安装-5" class="headerlink" title="安装"></a>安装</h3><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">yarn add less --dev</span><br><span class="line"></span><br><span class="line">yarn add postcss --dev</span><br><span class="line">yarn add postcss-html --dev</span><br><span class="line">yarn add postcss-less --dev</span><br><span class="line"></span><br><span class="line">yarn add stylelint --dev</span><br><span class="line">yarn add stylelint-config-html --dev</span><br><span class="line">yarn add stylelint-config-prettier --dev</span><br><span class="line">yarn add stylelint-config-recommended --dev</span><br><span class="line">yarn add stylelint-config-recommended-less --dev</span><br><span class="line">yarn add stylelint-config-standard --dev</span><br><span class="line">yarn add stylelint-config-standard-vue --dev</span><br><span class="line">yarn add stylelint-less --dev</span><br><span class="line">yarn add stylelint-order --dev</span><br></pre></td></tr></table></figure><h3 id="增加检查脚本-1"><a href="#增加检查脚本-1" class="headerlink" title="增加检查脚本"></a>增加检查脚本</h3><p>在 package.json 中的 scripts 中新增</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;lint:stylelint&quot;</span><span class="punctuation">:</span> <span class="string">&quot;stylelint --cache --fix \&quot;**/*.&#123;vue,less,postcss,css,scss&#125;\&quot; --cache --cache-location node_modules/.cache/stylelint/&quot;</span><span class="punctuation">,</span></span><br></pre></td></tr></table></figure><p>之后运行 <code>npm run lint:stylelint</code> 即可检查全部代码是否有 CSS 不规范的地方</p><h3 id="配置-4"><a href="#配置-4" class="headerlink" title="配置"></a>配置</h3><ul><li>项目目录下创建 <code>stylelint.config.js</code> 文件存放 stylelint 的配置</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></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">extends</span>: [</span><br><span class="line">    <span class="string">&#x27;stylelint-config-standard&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;stylelint-config-prettier&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;stylelint-config-recommended-less&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;stylelint-config-standard-vue&#x27;</span>,</span><br><span class="line">  ],</span><br><span class="line">  <span class="attr">plugins</span>: [<span class="string">&#x27;stylelint-order&#x27;</span>, <span class="string">&#x27;stylelint-less&#x27;</span>],</span><br><span class="line">  <span class="attr">overrides</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">files</span>: [<span class="string">&#x27;**/*.(less|css|vue|html)&#x27;</span>],</span><br><span class="line">      <span class="attr">customSyntax</span>: <span class="string">&#x27;postcss-less&#x27;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">files</span>: [<span class="string">&#x27;**/*.(html|vue)&#x27;</span>],</span><br><span class="line">      <span class="attr">customSyntax</span>: <span class="string">&#x27;postcss-html&#x27;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">  ],</span><br><span class="line">  <span class="attr">ignoreFiles</span>: [<span class="string">&#x27;**/*.js&#x27;</span>, <span class="string">&#x27;**/*.jsx&#x27;</span>, <span class="string">&#x27;**/*.tsx&#x27;</span>, <span class="string">&#x27;**/*.ts&#x27;</span>, <span class="string">&#x27;**/*.json&#x27;</span>, <span class="string">&#x27;**/*.md&#x27;</span>, <span class="string">&#x27;**/*.yaml&#x27;</span>],</span><br><span class="line">  <span class="attr">rules</span>: &#123;</span><br><span class="line">    <span class="string">&#x27;no-descending-specificity&#x27;</span>: <span class="literal">null</span>,</span><br><span class="line">    <span class="string">&#x27;selector-pseudo-element-no-unknown&#x27;</span>: [</span><br><span class="line">      <span class="literal">true</span>,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">ignorePseudoElements</span>: [<span class="string">&#x27;v-deep&#x27;</span>],</span><br><span class="line">      &#125;,</span><br><span class="line">    ],</span><br><span class="line">    <span class="string">&#x27;selector-pseudo-class-no-unknown&#x27;</span>: [</span><br><span class="line">      <span class="literal">true</span>,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">ignorePseudoClasses</span>: [<span class="string">&#x27;deep&#x27;</span>],</span><br><span class="line">      &#125;,</span><br><span class="line">    ],</span><br><span class="line">    <span class="string">&#x27;function-no-unknown&#x27;</span>: <span class="literal">null</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><ul><li>项目目录下创建 <code>.stylelintignore</code> 文件，用于配置那些文件需要忽略检查</li></ul><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">/dist/*</span><br><span class="line">/public/*</span><br><span class="line">public/*</span><br></pre></td></tr></table></figure><ul><li>在 <code>vite.config.ts</code> 文件中，增加下面预处理配置</li></ul><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">css: &#123;</span><br><span class="line">    preprocessorOptions: &#123;</span><br><span class="line">      less: &#123;</span><br><span class="line">        modifyVars: &#123;</span><br><span class="line">          &#x27;primary-color&#x27;: &#x27;#1e80ff&#x27;, //  Primary color</span><br><span class="line">          &#x27;success-color&#x27;: &#x27;#55D187&#x27;, //  Success color</span><br><span class="line">          &#x27;error-color&#x27;: &#x27;#ED6F6F&#x27;, //  False color</span><br><span class="line">          &#x27;warning-color&#x27;: &#x27;#EFBD47&#x27;, //   Warning color</span><br><span class="line">          &#x27;font-size-base&#x27;: &#x27;14px&#x27;, //  Main font size</span><br><span class="line">          &#x27;border-radius-base&#x27;: &#x27;2px&#x27;, //  Component/float fillet</span><br><span class="line">          &#x27;app-content-background&#x27;: &#x27;#fafafa&#x27;, //   Link color</span><br><span class="line">        &#125;,</span><br><span class="line">        javascriptEnabled: true,</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; defineConfig &#125; <span class="keyword">from</span> <span class="string">&#x27;vite&#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">css</span>: &#123;</span><br><span class="line">    <span class="attr">preprocessorOptions</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="string">&#x27;primary-color&#x27;</span>: <span class="string">&#x27;#1e80ff&#x27;</span>, <span class="comment">//  Primary color</span></span><br><span class="line">          <span class="string">&#x27;success-color&#x27;</span>: <span class="string">&#x27;#55D187&#x27;</span>, <span class="comment">//  Success color</span></span><br><span class="line">          <span class="string">&#x27;error-color&#x27;</span>: <span class="string">&#x27;#ED6F6F&#x27;</span>, <span class="comment">//  False color</span></span><br><span class="line">          <span class="string">&#x27;warning-color&#x27;</span>: <span class="string">&#x27;#EFBD47&#x27;</span>, <span class="comment">//   Warning color</span></span><br><span class="line">          <span class="string">&#x27;font-size-base&#x27;</span>: <span class="string">&#x27;14px&#x27;</span>, <span class="comment">//  Main font size</span></span><br><span class="line">          <span class="string">&#x27;border-radius-base&#x27;</span>: <span class="string">&#x27;2px&#x27;</span>, <span class="comment">//  Component/float fillet</span></span><br><span class="line">          <span class="string">&#x27;app-content-background&#x27;</span>: <span class="string">&#x27;#fafafa&#x27;</span>, <span class="comment">//   Link color</span></span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="attr">javascriptEnabled</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></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><p>使用 <code>husky</code> + <code>lint-staged</code> ，可以实现每次提交 git 前，自动检查代码的格式规范</p><h3 id="安装-6"><a href="#安装-6" class="headerlink" title="安装"></a>安装</h3><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">yarn add lint-staged --dev</span><br><span class="line">yarn add husky --dev</span><br></pre></td></tr></table></figure><h3 id="配置-5"><a href="#配置-5" class="headerlink" title="配置"></a>配置</h3><h4 id="lint-staged"><a href="#lint-staged" class="headerlink" title="lint-staged"></a>lint-staged</h4><p>在 package.json 中的 scripts 中新增</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;lint:staged&quot;</span><span class="punctuation">:</span> <span class="string">&quot;lint-staged&quot;</span><span class="punctuation">,</span></span><br></pre></td></tr></table></figure><p>之后运行 <code>npm run lint:staged</code> 即可手动检查</p><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><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="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;prettier --write&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;!(package)*.json,*.code-snippets,.!(browserslist)*rc&#125;&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;prettier --write--parser json&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;package.json&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 class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;*.vue&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;prettier --write&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;stylelint --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;scss,less,styl,html&#125;&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;stylelint --fix&quot;</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 class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;*.md&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></pre></td></tr></table></figure><h4 id="husky"><a href="#husky" class="headerlink" title="husky"></a>husky</h4><p>在 package.json 中的 scripts 中新增</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;prepare&quot;</span><span class="punctuation">:</span> <span class="string">&quot;husky install&quot;</span><span class="punctuation">,</span></span><br></pre></td></tr></table></figure><p>然后执行下面语句自动创建 <code>.husky</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">yarn prepare</span><br></pre></td></tr></table></figure><p>在此之后，每次执行 <code>yarn install</code> 语句，会自动执行上面的语句</p><p>然后创建文件 <code>.husky/pre-commit</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">!/bin/sh</span></span><br><span class="line">. &quot;$(dirname &quot;$0&quot;)/_/husky.sh&quot;</span><br><span class="line"></span><br><span class="line">npm run lint:staged</span><br></pre></td></tr></table></figure><h2 id="增加-husky-commitlint"><a href="#增加-husky-commitlint" class="headerlink" title="增加 husky + commitlint"></a>增加 husky + commitlint</h2><p>使用 <code>husky</code> + <code>commitlint</code> ，可以实现每次提交 git 前，自动检查格式规范</p><p>规划化提交格式，可用于自动更新 <code>CHANGELOG.md</code>、自动生成 Release 内容等功能</p><p>husky 按前面的 <code>增加 husky + lint-staged</code> 部分安装和配置，此处仅介绍 <code>commitlint</code> 相关</p><h3 id="安装-7"><a href="#安装-7" class="headerlink" title="安装"></a>安装</h3><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">yarn add @commitlint/cli --dev</span><br><span class="line">yarn add @commitlint/config-conventional --dev</span><br></pre></td></tr></table></figure><h3 id="配置-6"><a href="#配置-6" class="headerlink" title="配置"></a>配置</h3><p>增加文件 <code>.commitlintrc.js</code> 用于存放 <code>commitlint</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="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">extends</span>: [<span class="string">&quot;@commitlint/config-conventional&quot;</span>],</span><br><span class="line">  <span class="attr">rules</span>: &#123;</span><br><span class="line">    <span class="string">&quot;type-enum&quot;</span>: [</span><br><span class="line">      <span class="number">2</span>,</span><br><span class="line">      <span class="string">&quot;always&quot;</span>,</span><br><span class="line">      [</span><br><span class="line">        <span class="string">&quot;build&quot;</span>,</span><br><span class="line">        <span class="string">&quot;chore&quot;</span>,</span><br><span class="line">        <span class="string">&quot;ci&quot;</span>,</span><br><span class="line">        <span class="string">&quot;docs&quot;</span>,</span><br><span class="line">        <span class="string">&quot;feat&quot;</span>,</span><br><span class="line">        <span class="string">&quot;fix&quot;</span>,</span><br><span class="line">        <span class="string">&quot;perf&quot;</span>,</span><br><span class="line">        <span class="string">&quot;refactor&quot;</span>,</span><br><span class="line">        <span class="string">&quot;revert&quot;</span>,</span><br><span class="line">        <span class="string">&quot;style&quot;</span>,</span><br><span class="line">        <span class="string">&quot;test&quot;</span>,</span><br><span class="line">        <span class="string">&quot;typo&quot;</span>,</span><br><span class="line">      ],</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>新增文件 <code>.husky/commit-msg</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"><span class="meta">#!/bin/sh</span></span><br><span class="line">. <span class="string">&quot;<span class="subst">$(dirname <span class="string">&quot;<span class="variable">$0</span>&quot;</span>)</span>/_/husky.sh&quot;</span></span><br><span class="line"></span><br><span class="line">npx --no-install commitlint --edit <span class="variable">$1</span></span><br></pre></td></tr></table></figure><h2 id="增加-svg-支持"><a href="#增加-svg-支持" class="headerlink" title="增加 svg 支持"></a>增加 svg 支持</h2><p>配置后能够解析 svg 图标文件</p><h3 id="安装-8"><a href="#安装-8" class="headerlink" title="安装"></a>安装</h3><ul><li>在项目目录下执行以下命令安装插件</li></ul><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 vite-plugin-svg-icons --dev</span><br></pre></td></tr></table></figure><h3 id="配置-7"><a href="#配置-7" class="headerlink" title="配置"></a>配置</h3><ul><li>修改 <code>vite.config.ts</code> 文件，增加如下代码</li></ul><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">plugins: [SvgIconsPlugin(&#123;</span><br><span class="line">  iconDirs: [path.resolve(process.cwd(), &#x27;src/assets/icons&#x27;)],</span><br><span class="line">  symbolId: &#x27;icon-[dir]-[name]&#x27;,</span><br><span class="line">  svgoOptions: true,</span><br><span class="line">&#125;)],</span><br></pre></td></tr></table></figure><p>iconDirs 是配置图标文件目录，这里是 <code>src/assets/icons</code>，也可以修改为其他目录</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; defineConfig &#125; <span class="keyword">from</span> <span class="string">&#x27;vite&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; createSvgIconsPlugin &#125; <span class="keyword">from</span> <span class="string">&#x27;vite-plugin-svg-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 function_">defineConfig</span>(&#123;</span><br><span class="line">  <span class="attr">plugins</span>: [<span class="title function_">createSvgIconsPlugin</span>(&#123;</span><br><span class="line">    <span class="attr">iconDirs</span>: [path.<span class="title function_">resolve</span>(process.<span class="title function_">cwd</span>(), <span class="string">&#x27;src/assets/icons&#x27;</span>)],</span><br><span class="line">    <span class="attr">symbolId</span>: <span class="string">&#x27;icon-[dir]-[name]&#x27;</span>,</span><br><span class="line">    <span class="attr">svgoOptions</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><ul><li>修改 <code>main.ts</code> 文件，增加如下代码</li></ul><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 &#x27;virtual:svg-icons-register&#x27;;</span><br></pre></td></tr></table></figure><h3 id="封装组件"><a href="#封装组件" class="headerlink" title="封装组件"></a>封装组件</h3><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">&lt;svg aria-hidden=&quot;true&quot;&gt;</span><br><span class="line">  &lt;use :href=&quot;#icon-name&quot; fill=&quot;black&quot; /&gt;</span><br><span class="line">&lt;/svg&gt;</span><br></pre></td></tr></table></figure><p>在 <code>components/Icon</code> 下创建 <code>SvgIcon.vue</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><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></pre></td><td class="code"><pre><span class="line">&lt;template&gt;</span><br><span class="line">  &lt;svg</span><br><span class="line">    :class=&quot;[$attrs.class, spin &amp;&amp; &#x27;svg-icon-spin&#x27;]&quot;</span><br><span class="line">    :style=&quot;getStyle&quot;</span><br><span class="line">    :fill=&quot;color&quot;</span><br><span class="line">    aria-hidden=&quot;true&quot;</span><br><span class="line">  &gt;</span><br><span class="line">    &lt;use :xlink:href=&quot;symbolId&quot; /&gt;</span><br><span class="line">  &lt;/svg&gt;</span><br><span class="line">&lt;/template&gt;</span><br><span class="line">&lt;script lang=&quot;ts&quot;&gt;</span><br><span class="line">  import type &#123; CSSProperties &#125; from &#x27;vue&#x27;;</span><br><span class="line">  import &#123; defineComponent, computed &#125; from &#x27;vue&#x27;;</span><br><span class="line"></span><br><span class="line">  export default defineComponent(&#123;</span><br><span class="line">    name: &#x27;SvgIcon&#x27;,</span><br><span class="line">    props: &#123;</span><br><span class="line">      prefix: &#123;</span><br><span class="line">        type: String,</span><br><span class="line">        default: &#x27;icon&#x27;,</span><br><span class="line">      &#125;,</span><br><span class="line">      name: &#123;</span><br><span class="line">        type: String,</span><br><span class="line">        required: true,</span><br><span class="line">      &#125;,</span><br><span class="line">      size: &#123;</span><br><span class="line">        type: [Number, String],</span><br><span class="line">        default: 16,</span><br><span class="line">      &#125;,</span><br><span class="line">      spin: &#123;</span><br><span class="line">        type: Boolean,</span><br><span class="line">        default: false,</span><br><span class="line">      &#125;,</span><br><span class="line">      color: &#123;</span><br><span class="line">        type: String,</span><br><span class="line">        default: &#x27;&#x27;,</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">    setup(props) &#123;</span><br><span class="line">      const symbolId = computed(() =&gt; `#$&#123;props.prefix&#125;-$&#123;props.name&#125;`);</span><br><span class="line"></span><br><span class="line">      const getStyle = computed((): CSSProperties =&gt; &#123;</span><br><span class="line">        const &#123; size &#125; = props;</span><br><span class="line">        let s = `$&#123;size&#125;`;</span><br><span class="line">        s = `$&#123;s.replace(&#x27;px&#x27;, &#x27;&#x27;)&#125;px`;</span><br><span class="line">        return &#123;</span><br><span class="line">          width: s,</span><br><span class="line">          height: s,</span><br><span class="line">        &#125;;</span><br><span class="line">      &#125;);</span><br><span class="line">      return &#123; symbolId, getStyle &#125;;</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;);</span><br><span class="line">&lt;/script&gt;</span><br><span class="line">&lt;style lang=&quot;less&quot; scoped&gt;</span><br><span class="line">  @prefix-cls: ~&#x27;svg-icon&#x27;;</span><br><span class="line"></span><br><span class="line">  .@&#123;prefix-cls&#125; &#123;</span><br><span class="line">    display: inline-block;</span><br><span class="line">    overflow: hidden;</span><br><span class="line">    vertical-align: -0.15em;</span><br><span class="line">    fill: currentcolor;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  .svg-icon-spin &#123;</span><br><span class="line">    animation: loadingCircle 1s infinite linear;</span><br><span class="line">  &#125;</span><br><span class="line">&lt;/style&gt;</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">&lt;SvgIcon name=&quot;name&quot; color=&quot;red&quot;/&gt;</span><br></pre></td></tr></table></figure><h2 id="增加-iconify"><a href="#增加-iconify" class="headerlink" title="增加 iconify"></a>增加 iconify</h2><p>通过文件的方式使用 svg 还不够方便，用上更强大的 iconify 吧</p><p>iconify 是功能丰富的图标框架，可以与任意图标库一起使用</p><h3 id="安装-9"><a href="#安装-9" class="headerlink" title="安装"></a>安装</h3><ul><li>在项目目录下执行以下命令安装插件</li></ul><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">yarn add @iconify/iconify</span><br><span class="line">yarn add vite-plugin-purge-icons --dev</span><br><span class="line">yarn add @iconify/json --dev</span><br></pre></td></tr></table></figure><h3 id="配置-8"><a href="#配置-8" class="headerlink" title="配置"></a>配置</h3><ul><li>修改 <code>vite.config.ts</code> 文件，增加如下代码</li></ul><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">plugins: [PurgeIcons(&#123;&#125;)],</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; defineConfig &#125; <span class="keyword">from</span> <span class="string">&#x27;vite&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">PurgeIcons</span> <span class="keyword">from</span> <span class="string">&#x27;vite-plugin-purge-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 function_">defineConfig</span>(&#123;</span><br><span class="line">  <span class="attr">plugins</span>: [<span class="title class_">PurgeIcons</span>()],</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="使用-Iconify"><a href="#使用-Iconify" class="headerlink" title="使用 Iconify"></a>使用 Iconify</h3><p>在这里搜索图标即可使用，无需下载 <span class="exturl" data-url="aHR0cHM6Ly9pY29uLXNldHMuaWNvbmlmeS5kZXNpZ24v">https://icon-sets.iconify.design/<i class="fa fa-external-link-alt"></i></span></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">span</span> <span class="attr">class</span>=<span class="string">&quot;iconify&quot;</span> <span class="attr">data-icon</span>=<span class="string">&quot;ic:baseline-add-reaction&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="封装组件-1"><a href="#封装组件-1" class="headerlink" title="封装组件"></a>封装组件</h3><p>封装组件用起来更方便</p><p>在 <code>src/components</code> 下创建 <code>Icon</code> 文件夹和 <code>Icon/index.vue</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><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></pre></td><td class="code"><pre><span class="line">&lt;template&gt;</span><br><span class="line">  &lt;span</span><br><span class="line">    ref=&quot;elRef&quot;</span><br><span class="line">    :class=&quot;[$attrs.class, &#x27;app-iconify&#x27;, spin &amp;&amp; &#x27;app-iconify-spin&#x27;]&quot;</span><br><span class="line">    :style=&quot;getWrapStyle&quot;</span><br><span class="line">  &gt;&lt;/span&gt;</span><br><span class="line">&lt;/template&gt;</span><br><span class="line">&lt;script lang=&quot;ts&quot;&gt;</span><br><span class="line">  import type &#123; PropType &#125; from &#x27;vue&#x27;;</span><br><span class="line">  import &#123;</span><br><span class="line">    defineComponent,</span><br><span class="line">    ref,</span><br><span class="line">    watch,</span><br><span class="line">    onMounted,</span><br><span class="line">    nextTick,</span><br><span class="line">    unref,</span><br><span class="line">    computed,</span><br><span class="line">    CSSProperties,</span><br><span class="line">  &#125; from &#x27;vue&#x27;;</span><br><span class="line">  import Iconify from &#x27;@purge-icons/generated&#x27;;</span><br><span class="line"></span><br><span class="line">  export default defineComponent(&#123;</span><br><span class="line">    name: &#x27;Icon&#x27;,</span><br><span class="line">    props: &#123;</span><br><span class="line">      // icon name</span><br><span class="line">      icon: &#123; type: String, required: true &#125;,</span><br><span class="line">      // icon color</span><br><span class="line">      color: &#123; type: String, default: &#x27;&#x27; &#125;,</span><br><span class="line">      // icon size</span><br><span class="line">      size: &#123;</span><br><span class="line">        type: [String, Number] as PropType&lt;string | number&gt;,</span><br><span class="line">        default: 16,</span><br><span class="line">      &#125;,</span><br><span class="line">      spin: &#123; type: Boolean, default: false &#125;,</span><br><span class="line">      prefix: &#123; type: String, default: &#x27;&#x27; &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">    setup(props) &#123;</span><br><span class="line">      const elRef = ref&lt;HTMLDivElement | null&gt;(null);</span><br><span class="line"></span><br><span class="line">      const getIconRef = computed(() =&gt; `$&#123;props.prefix ? props.prefix + &#x27;:&#x27; : &#x27;&#x27;&#125;$&#123;props.icon&#125;`);</span><br><span class="line"></span><br><span class="line">      const update = async () =&gt; &#123;</span><br><span class="line">        const el = unref(elRef);</span><br><span class="line">        if (!el) return;</span><br><span class="line"></span><br><span class="line">        await nextTick();</span><br><span class="line">        const icon = unref(getIconRef);</span><br><span class="line">        if (!icon) return;</span><br><span class="line"></span><br><span class="line">        const span = document.createElement(&#x27;span&#x27;);</span><br><span class="line">        span.className = &#x27;iconify&#x27;;</span><br><span class="line">        span.dataset.icon = icon;</span><br><span class="line">        el.textContent = &#x27;&#x27;;</span><br><span class="line">        el.appendChild(span);</span><br><span class="line">      &#125;;</span><br><span class="line"></span><br><span class="line">      const getWrapStyle = computed((): CSSProperties =&gt; &#123;</span><br><span class="line">        const &#123; size, color &#125; = props;</span><br><span class="line">        let fs = size;</span><br><span class="line">        if (typeof size == &#x27;string&#x27;) &#123;</span><br><span class="line">          fs = parseInt(size, 10);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        return &#123;</span><br><span class="line">          fontSize: `$&#123;fs&#125;px`,</span><br><span class="line">          color: color,</span><br><span class="line">          display: &#x27;inline-flex&#x27;,</span><br><span class="line">        &#125;;</span><br><span class="line">      &#125;);</span><br><span class="line"></span><br><span class="line">      watch(() =&gt; props.icon, update, &#123; flush: &#x27;post&#x27; &#125;);</span><br><span class="line"></span><br><span class="line">      onMounted(update);</span><br><span class="line"></span><br><span class="line">      return &#123; elRef, getWrapStyle &#125;;</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;);</span><br><span class="line">&lt;/script&gt;</span><br><span class="line">&lt;style lang=&quot;less&quot;&gt;</span><br><span class="line">  .app-iconify &#123;</span><br><span class="line">    display: inline-block;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  span.iconify &#123;</span><br><span class="line">    display: block;</span><br><span class="line">    min-width: 1em;</span><br><span class="line">    min-height: 1em;</span><br><span class="line">    background-color: #5551;</span><br><span class="line">    border-radius: 100%;</span><br><span class="line">  &#125;</span><br><span class="line">&lt;/style&gt;</span><br><span class="line"></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">&lt;Icon icon=&quot;ant-design:aliyun-outlined&quot;/&gt;</span><br></pre></td></tr></table></figure><h3 id="与-svg-封装为一个组件"><a href="#与-svg-封装为一个组件" class="headerlink" title="与 svg 封装为一个组件"></a>与 svg 封装为一个组件</h3><p><code>Icon/index.vue</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><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></pre></td><td class="code"><pre><span class="line">&lt;template&gt;</span><br><span class="line">  &lt;SvgIcon</span><br><span class="line">    :size=&quot;size&quot;</span><br><span class="line">    :name=&quot;getSvgIcon&quot;</span><br><span class="line">    v-if=&quot;isSvgIcon&quot;</span><br><span class="line">    :class=&quot;[$attrs.class]&quot;</span><br><span class="line">    :spin=&quot;spin&quot;</span><br><span class="line">    :color=&quot;color&quot;</span><br><span class="line">  /&gt;</span><br><span class="line">  &lt;span</span><br><span class="line">    v-else</span><br><span class="line">    ref=&quot;elRef&quot;</span><br><span class="line">    :class=&quot;[$attrs.class, &#x27;app-iconify&#x27;, spin &amp;&amp; &#x27;app-iconify-spin&#x27;]&quot;</span><br><span class="line">    :style=&quot;getWrapStyle&quot;</span><br><span class="line">  &gt;&lt;/span&gt;</span><br><span class="line">&lt;/template&gt;</span><br><span class="line">&lt;script lang=&quot;ts&quot;&gt;</span><br><span class="line">  import type &#123; PropType &#125; from &#x27;vue&#x27;;</span><br><span class="line">  import &#123;</span><br><span class="line">    defineComponent,</span><br><span class="line">    ref,</span><br><span class="line">    watch,</span><br><span class="line">    onMounted,</span><br><span class="line">    nextTick,</span><br><span class="line">    unref,</span><br><span class="line">    computed,</span><br><span class="line">    CSSProperties,</span><br><span class="line">  &#125; from &#x27;vue&#x27;;</span><br><span class="line">  import SvgIcon from &#x27;./SvgIcon.vue&#x27;;</span><br><span class="line">  import Iconify from &#x27;@purge-icons/generated&#x27;;</span><br><span class="line"></span><br><span class="line">  const SVG_END_WITH_FLAG = &#x27;|svg&#x27;;</span><br><span class="line">  export default defineComponent(&#123;</span><br><span class="line">    name: &#x27;Icon&#x27;,</span><br><span class="line">    components: &#123; SvgIcon &#125;,</span><br><span class="line">    props: &#123;</span><br><span class="line">      // icon name</span><br><span class="line">      icon: &#123; type: String, required: true &#125;,</span><br><span class="line">      // icon color</span><br><span class="line">      color: &#123; type: String, default: &#x27;&#x27; &#125;,</span><br><span class="line">      // icon size</span><br><span class="line">      size: &#123;</span><br><span class="line">        type: [String, Number] as PropType&lt;string | number&gt;,</span><br><span class="line">        default: 16,</span><br><span class="line">      &#125;,</span><br><span class="line">      spin: &#123; type: Boolean, default: false &#125;,</span><br><span class="line">      prefix: &#123; type: String, default: &#x27;&#x27; &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">    setup(props) &#123;</span><br><span class="line">      const elRef = ref&lt;HTMLDivElement | null&gt;(null);</span><br><span class="line"></span><br><span class="line">      const isSvgIcon = computed(() =&gt; props.icon?.endsWith(SVG_END_WITH_FLAG));</span><br><span class="line">      const getSvgIcon = computed(() =&gt; props.icon.replace(SVG_END_WITH_FLAG, &#x27;&#x27;));</span><br><span class="line">      const getIconRef = computed(() =&gt; `$&#123;props.prefix ? props.prefix + &#x27;:&#x27; : &#x27;&#x27;&#125;$&#123;props.icon&#125;`);</span><br><span class="line"></span><br><span class="line">      const update = async () =&gt; &#123;</span><br><span class="line">        if (unref(isSvgIcon)) return;</span><br><span class="line"></span><br><span class="line">        const el = unref(elRef);</span><br><span class="line">        if (!el) return;</span><br><span class="line"></span><br><span class="line">        await nextTick();</span><br><span class="line">        const icon = unref(getIconRef);</span><br><span class="line">        if (!icon) return;</span><br><span class="line"></span><br><span class="line">        const svg = Iconify.renderSVG(icon, &#123;&#125;);</span><br><span class="line">        if (svg) &#123;</span><br><span class="line">          el.textContent = &#x27;&#x27;;</span><br><span class="line">          el.appendChild(svg);</span><br><span class="line">        &#125; else &#123;</span><br><span class="line">          const span = document.createElement(&#x27;span&#x27;);</span><br><span class="line">          span.className = &#x27;iconify&#x27;;</span><br><span class="line">          span.dataset.icon = icon;</span><br><span class="line">          el.textContent = &#x27;&#x27;;</span><br><span class="line">          el.appendChild(span);</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;;</span><br><span class="line"></span><br><span class="line">      const getWrapStyle = computed((): CSSProperties =&gt; &#123;</span><br><span class="line">        const &#123; size, color &#125; = props;</span><br><span class="line">        let fs = size;</span><br><span class="line">        if (typeof size == &#x27;string&#x27;) &#123;</span><br><span class="line">          fs = parseInt(size, 10);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        return &#123;</span><br><span class="line">          fontSize: `$&#123;fs&#125;px`,</span><br><span class="line">          color: color,</span><br><span class="line">          display: &#x27;inline-flex&#x27;,</span><br><span class="line">        &#125;;</span><br><span class="line">      &#125;);</span><br><span class="line"></span><br><span class="line">      watch(() =&gt; props.icon, update, &#123; flush: &#x27;post&#x27; &#125;);</span><br><span class="line"></span><br><span class="line">      onMounted(update);</span><br><span class="line"></span><br><span class="line">      return &#123; elRef, getWrapStyle, isSvgIcon, getSvgIcon &#125;;</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;);</span><br><span class="line">&lt;/script&gt;</span><br><span class="line">&lt;style lang=&quot;less&quot;&gt;</span><br><span class="line">  .app-iconify &#123;</span><br><span class="line">    display: inline-block;</span><br><span class="line">    // vertical-align: middle;</span><br><span class="line"></span><br><span class="line">    &amp;-spin &#123;</span><br><span class="line">      svg &#123;</span><br><span class="line">        animation: loadingCircle 1s infinite linear;</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.iconify &#123;</span><br><span class="line">    display: block;</span><br><span class="line">    min-width: 1em;</span><br><span class="line">    min-height: 1em;</span><br><span class="line">    background-color: #5551;</span><br><span class="line">    border-radius: 100%;</span><br><span class="line">  &#125;</span><br><span class="line">&lt;/style&gt;</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></pre></td><td class="code"><pre><span class="line">&lt;Icon icon=&quot;lavcode|svg&quot; color=&quot;red&quot; size=&quot;50&quot;/&gt;</span><br><span class="line">&lt;Icon icon=&quot;ant-design:aliyun-outlined&quot;  color=&quot;red&quot; size=&quot;50&quot;/&gt;</span><br></pre></td></tr></table></figure><h2 id="完整的-vite-config-ts"><a href="#完整的-vite-config-ts" class="headerlink" title="完整的 vite.config.ts"></a>完整的 vite.config.ts</h2><p>根据此教程，完整的 <code>vite.config.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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">ConfigEnv</span>, loadEnv, <span class="title class_">UserConfig</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;vite&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> vue <span class="keyword">from</span> <span class="string">&#x27;@vitejs/plugin-vue&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; resolve &#125; <span class="keyword">from</span> <span class="string">&#x27;path&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">WindiCSS</span> <span class="keyword">from</span> <span class="string">&#x27;vite-plugin-windicss&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">PurgeIcons</span> <span class="keyword">from</span> <span class="string">&#x27;vite-plugin-purge-icons&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; createSvgIconsPlugin &#125; <span class="keyword">from</span> <span class="string">&#x27;vite-plugin-svg-icons&#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="keyword">import</span> &#123; createProxy &#125; <span class="keyword">from</span> <span class="string">&#x27;/@/build/proxy&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; wrapperEnv &#125; <span class="keyword">from</span> <span class="string">&#x27;/@/build/env&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">pathResolve</span>(<span class="params"><span class="attr">dir</span>: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">resolve</span>(process.<span class="title function_">cwd</span>(), <span class="string">&#x27;.&#x27;</span>, dir);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="title function_">default</span> (&#123; mode &#125;: <span class="title class_">ConfigEnv</span>): <span class="function"><span class="params">UserConfig</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> root = process.<span class="title function_">cwd</span>();</span><br><span class="line">  <span class="keyword">const</span> env = <span class="title function_">loadEnv</span>(mode, root);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> viteEnv = <span class="title function_">wrapperEnv</span>(env);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> &#123;</span><br><span class="line">    <span class="variable constant_">VITE_GLOB_API_PROXY_PREFIX</span>,</span><br><span class="line">    <span class="variable constant_">VITE_GLOB_PROXY_API_URL</span>,</span><br><span class="line">    <span class="variable constant_">VITE_PORT</span>,</span><br><span class="line">    <span class="variable constant_">VITE_PUBLIC_PATH</span>,</span><br><span class="line">    <span class="variable constant_">VITE_DROP_CONSOLE</span>,</span><br><span class="line">  &#125; = viteEnv;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="attr">base</span>: <span class="variable constant_">VITE_PUBLIC_PATH</span>,</span><br><span class="line">    root,</span><br><span class="line">    <span class="attr">plugins</span>: [</span><br><span class="line">      <span class="title function_">vue</span>(),</span><br><span class="line">      <span class="title class_">WindiCSS</span>(),</span><br><span class="line">      <span class="title class_">PurgeIcons</span>(),</span><br><span class="line">      <span class="title function_">createSvgIconsPlugin</span>(&#123;</span><br><span class="line">        <span class="attr">iconDirs</span>: [path.<span class="title function_">resolve</span>(process.<span class="title function_">cwd</span>(), <span class="string">&#x27;src/assets/icons&#x27;</span>)],</span><br><span class="line">        <span class="attr">symbolId</span>: <span class="string">&#x27;icon-[dir]-[name]&#x27;</span>,</span><br><span class="line">        <span class="attr">svgoOptions</span>: <span class="literal">true</span>,</span><br><span class="line">      &#125;),</span><br><span class="line">    ],</span><br><span class="line">    <span class="attr">resolve</span>: &#123;</span><br><span class="line">      <span class="attr">alias</span>: [</span><br><span class="line">        &#123;</span><br><span class="line">          <span class="attr">find</span>: <span class="regexp">/\/@\//</span>,</span><br><span class="line">          <span class="attr">replacement</span>: <span class="title function_">pathResolve</span>(<span class="string">&#x27;src&#x27;</span>) + <span class="string">&#x27;/&#x27;</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">          <span class="attr">find</span>: <span class="regexp">/\/#\//</span>,</span><br><span class="line">          <span class="attr">replacement</span>: <span class="title function_">pathResolve</span>(<span class="string">&#x27;types&#x27;</span>) + <span class="string">&#x27;/&#x27;</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="attr">css</span>: &#123;</span><br><span class="line">      <span class="attr">preprocessorOptions</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="string">&#x27;primary-color&#x27;</span>: <span class="string">&#x27;#1e80ff&#x27;</span>, <span class="comment">//  Primary color</span></span><br><span class="line">            <span class="string">&#x27;success-color&#x27;</span>: <span class="string">&#x27;#55D187&#x27;</span>, <span class="comment">//  Success color</span></span><br><span class="line">            <span class="string">&#x27;error-color&#x27;</span>: <span class="string">&#x27;#ED6F6F&#x27;</span>, <span class="comment">//  False color</span></span><br><span class="line">            <span class="string">&#x27;warning-color&#x27;</span>: <span class="string">&#x27;#EFBD47&#x27;</span>, <span class="comment">//   Warning color</span></span><br><span class="line">            <span class="string">&#x27;font-size-base&#x27;</span>: <span class="string">&#x27;14px&#x27;</span>, <span class="comment">//  Main font size</span></span><br><span class="line">            <span class="string">&#x27;border-radius-base&#x27;</span>: <span class="string">&#x27;2px&#x27;</span>, <span class="comment">//  Component/float fillet</span></span><br><span class="line">            <span class="string">&#x27;app-content-background&#x27;</span>: <span class="string">&#x27;#fafafa&#x27;</span>, <span class="comment">//   Link color</span></span><br><span class="line">          &#125;,</span><br><span class="line">          <span class="attr">javascriptEnabled</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">    <span class="attr">esbuild</span>: &#123;</span><br><span class="line">      <span class="attr">pure</span>: <span class="variable constant_">VITE_DROP_CONSOLE</span> ? [<span class="string">&#x27;console.log&#x27;</span>, <span class="string">&#x27;debugger&#x27;</span>] : [],</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">build</span>: &#123;</span><br><span class="line">      <span class="attr">sourcemap</span>: <span class="keyword">import</span>.<span class="property">meta</span>.<span class="property">env</span>.<span class="property">DEV</span></span><br><span class="line">      <span class="attr">outDir</span>: <span class="string">&#x27;dist&#x27;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">server</span>: &#123;</span><br><span class="line">      <span class="attr">host</span>: <span class="literal">true</span>,</span><br><span class="line">      <span class="attr">port</span>: <span class="variable constant_">VITE_PORT</span>,</span><br><span class="line">      <span class="attr">proxy</span>: <span class="title function_">createProxy</span>([[<span class="variable constant_">VITE_GLOB_API_PROXY_PREFIX</span>, <span class="variable constant_">VITE_GLOB_PROXY_API_URL</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="完整的-package-json"><a href="#完整的-package-json" class="headerlink" title="完整的 package.json"></a>完整的 package.json</h2><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><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></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;vue3-vite-ts-template&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.2&quot;</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;hal-wang&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;hi@hal.wang&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://github.com/hal-wang&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;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;vite&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;vue-tsc --noEmit &amp;&amp; vite build&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;preview&quot;</span><span class="punctuation">:</span> <span class="string">&quot;vite preview&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;lint:prettier&quot;</span><span class="punctuation">:</span> <span class="string">&quot;prettier --write  \&quot;src/**/*.&#123;js,json,tsx,css,less,scss,vue,html,md&#125;\&quot;&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;lint:eslint&quot;</span><span class="punctuation">:</span> <span class="string">&quot;eslint --cache --max-warnings 0  \&quot;&#123;src,mock&#125;/**/*.&#123;vue,ts,tsx&#125;\&quot; --fix&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;lint:stylelint&quot;</span><span class="punctuation">:</span> <span class="string">&quot;stylelint --cache --fix \&quot;**/*.&#123;vue,less,postcss,css,scss&#125;\&quot; --cache --cache-location node_modules/.cache/stylelint/&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;lint:staged&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;prepare&quot;</span><span class="punctuation">:</span> <span class="string">&quot;husky install&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;dependencies&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;@iconify/iconify&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^3.1.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;nprogress&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;pinia&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^2.0.33&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;vue&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^3.2.47&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;vue-router&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^4.1.6&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;@commitlint/cli&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^17.4.4&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;@commitlint/config-conventional&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^17.4.4&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;@iconify/json&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^2.2.36&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;@types/node&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^18.15.3&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;@types/nprogress&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;@typescript-eslint/eslint-plugin&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^5.55.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;@typescript-eslint/parser&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^5.55.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;@vitejs/plugin-vue&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^4.1.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;eslint&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^8.36.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;eslint-config-prettier&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^8.7.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;eslint-plugin-prettier&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^4.2.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;eslint-plugin-vue&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^9.9.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;husky&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^8.0.3&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;less&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^4.1.3&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;lint-staged&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^13.2.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;postcss&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^8.4.21&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;postcss-html&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^1.5.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;postcss-less&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^6.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;prettier&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^2.8.4&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;stylelint&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^15.3.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;stylelint-config-html&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^1.1.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;stylelint-config-prettier&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^9.0.5&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;stylelint-config-recommended&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^11.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;stylelint-config-recommended-less&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^1.0.4&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;stylelint-config-standard&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^31.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;stylelint-config-standard-vue&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^1.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;stylelint-less&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^1.0.6&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;stylelint-order&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^6.0.3&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;typescript&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^5.0.2&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;vite&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^4.2.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;vite-plugin-purge-icons&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^0.9.2&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;vite-plugin-svg-icons&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^2.0.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;vite-plugin-windicss&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^1.8.10&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;vue-eslint-parser&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^9.1.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;vue-tsc&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^1.2.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;windicss&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^3.5.6&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/hal-wang/vue3-vite-ts-template.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;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/hal-wang/vue3-vite-ts-template/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/hal-wang/vue3-vite-ts-template&quot;</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;prettier --write&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;!(package)*.json,*.code-snippets,.!(browserslist)*rc&#125;&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;prettier --write--parser json&quot;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;package.json&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 class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;*.vue&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;prettier --write&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;stylelint --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;scss,less,styl,html&#125;&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;stylelint --fix&quot;</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 class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;*.md&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>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文将从零开始搭建一个 &lt;code&gt;Vue3&lt;/code&gt; + &lt;code&gt;Vite&lt;/code&gt; + &lt;code&gt;Pinia&lt;/code&gt; + &lt;code&gt;TS&lt;/code&gt; 入门项目&lt;/p&gt;
&lt;p&gt;源码：&lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cHM6Ly9naXRodWIuY29tL2hhbC13YW5nL3Z1ZTMtdml0ZS10cy10ZW1wbGF0ZQ==&quot;&gt;https://github.com/hal-wang/vue3-vite-ts-template&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure class=&quot;highlight plaintext&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;git clone https://github.com/hal-wang/vue3-vite-ts-template.git&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;ul&gt;
&lt;li&gt;Vue3 + Vite&lt;/li&gt;
&lt;li&gt;Pinia: 新的状态管理工具，替代 Vuex&lt;/li&gt;
&lt;li&gt;Vue Router 4: 路由管理&lt;/li&gt;
&lt;li&gt;TS + setup: TS 语法糖写法&lt;/li&gt;
&lt;li&gt;Prettier: 格式化工具&lt;/li&gt;
&lt;li&gt;ESlint: 格式标准工具&lt;/li&gt;
&lt;li&gt;Windi CSS: 功能类优先的 CSS 框架，与 Tailwind CSS 用法相同，但速度更快&lt;/li&gt;
&lt;li&gt;iconify + svg: iconify 是功能丰富的图标框架，加上 svg 文件解析，让你选图标随心所欲&lt;/li&gt;
&lt;li&gt;huskey + lint-staged 每次提交代码校验格式规范&lt;/li&gt;
&lt;li&gt;huskey + commitlint 每次提交代码校验提交消息规范&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="记录" scheme="https://blog.hal.wang/categories/%E8%AE%B0%E5%BD%95/"/>
    
    
    <category term="Vue" scheme="https://blog.hal.wang/tags/Vue/"/>
    
    <category term="Vite" scheme="https://blog.hal.wang/tags/Vite/"/>
    
    <category term="Pinia" scheme="https://blog.hal.wang/tags/Pinia/"/>
    
    <category term="TS" scheme="https://blog.hal.wang/tags/TS/"/>
    
    <category term="prettier" scheme="https://blog.hal.wang/tags/prettier/"/>
    
  </entry>
  
  <entry>
    <title>在 Windows 上流畅使用 MacOS 虚拟机</title>
    <link href="https://blog.hal.wang/7afa8fc1/"/>
    <id>https://blog.hal.wang/7afa8fc1/</id>
    <published>2022-03-10T12:50:53.000Z</published>
    <updated>2026-03-18T15:50:10.586Z</updated>
    
    <content type="html"><![CDATA[<p>本教程使用 WSL2 + KVM 运行 MacOS 虚拟机，MacOS 运行在 Linux 虚拟机中的 KVM 虚拟机，即嵌套虚拟化，但由于 Windows 对 WSL2 优化很好，个人感觉此方案比其他方案更好。</p><p>步骤较多，操作较繁琐，但成果很值得。</p><span id="more"></span><p><img src="./finished.png" alt="finished"></p><center><p><em>在 Windows 宿主机中成果截图</em></p></center><h2 id="前提条件"><a href="#前提条件" class="headerlink" title="前提条件"></a>前提条件</h2><ul><li>Windows 10&#x2F;11 22000+</li><li>开启 WSL2，详见 <span class="exturl" data-url="aHR0cHM6Ly9kb2NzLm1pY3Jvc29mdC5jb20vemgtY24vd2luZG93cy93c2wvaW5zdGFsbA==">https://docs.microsoft.com/zh-cn/windows/wsl/install<i class="fa fa-external-link-alt"></i></span></li></ul><p>文中所出现的 WSL，如果没有特指，都是 WSL2。</p><h2 id="与其他方案相比"><a href="#与其他方案相比" class="headerlink" title="与其他方案相比"></a>与其他方案相比</h2><h3 id="VMware-VitualBox"><a href="#VMware-VitualBox" class="headerlink" title="VMware&#x2F;VitualBox"></a>VMware&#x2F;VitualBox</h3><h4 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h4><ul><li>上手简单，安装快捷，“懒人版”更是无脑式安装</li><li>VMware 搭配 <code>VMware Tools</code> 更是可以与宿主机互相复制文件，也能自适应窗口尺寸</li></ul><h4 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h4><ul><li>运行不如此方案流畅</li><li>启动速度慢，固态硬盘启动时间可能也要几分钟</li><li>与 Hyper-V 兼容有问题，在开启 Hyper-V 的情况下，由于宿主机也是运行在 Hyper-V 的虚拟机，而 VMware&#x2F;VitualBox 不支持嵌套虚拟化，不能虚拟化 CPU 的 MacOS 虚拟机能卡出翔。</li></ul><h3 id="Docker-OSX"><a href="#Docker-OSX" class="headerlink" title="Docker-OSX"></a>Docker-OSX</h3><p>Docker 中运行 MacOS 较成熟的项目是 <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL3NpY2tjb2Rlcy9Eb2NrZXItT1NY">https://github.com/sickcodes/Docker-OSX<i class="fa fa-external-link-alt"></i></span></p><h4 id="优点-1"><a href="#优点-1" class="headerlink" title="优点"></a>优点</h4><p>上手简单，安装快捷，一行命令就可以启动一个运行 MacOS 的容器</p><h4 id="缺点-1"><a href="#缺点-1" class="headerlink" title="缺点"></a>缺点</h4><p>Docker 中的 MacOS 也是运行在 Linux 中的 KVM</p><ul><li>Docker 容器没有 WSL 启动便捷</li><li>没有 WSL 优化的好，因此流畅度不如此方案</li></ul><h2 id="WSL2-开启-GUI"><a href="#WSL2-开启-GUI" class="headerlink" title="WSL2 开启 GUI"></a>WSL2 开启 GUI</h2><blockquote><p>此部分参考 <span class="exturl" data-url="aHR0cHM6Ly9kb2NzLm1pY3Jvc29mdC5jb20vemgtY24vd2luZG93cy93c2wvdHV0b3JpYWxzL2d1aS1hcHBz">https://docs.microsoft.com/zh-cn/windows/wsl/tutorials/gui-apps<i class="fa fa-external-link-alt"></i></span></p></blockquote><p>此部分用于在 Windows 中以窗口形式操作 MacOS 虚拟机</p><p>完成后你也可以运行其他 Linux GUI 应用程序，你可以像本地应用一样运行 WSL Linux 中的应用程序，也可以将应用添加到开始菜单、固定到任务栏等</p><p>安装后可以得到无缝的 Linux + Windows 桌面体验</p><h3 id="安装-vGPU-驱动"><a href="#安装-vGPU-驱动" class="headerlink" title="安装 vGPU 驱动"></a>安装 vGPU 驱动</h3><p>安装 vGPU 驱动后可以使用虚拟 GPU，可以使用硬件加速 OpenGL 渲染</p><p>根据你电脑显卡下载安装：</p><ul><li><span class="exturl" data-url="aHR0cHM6Ly93d3cuaW50ZWwuY29tL2NvbnRlbnQvd3d3L3VzL2VuL2Rvd25sb2FkLzE5MzQ0L2ludGVsLWdyYXBoaWNzLXdpbmRvd3MtMTAtd2luZG93cy0xMS1kY2gtZHJpdmVycy5odG1s">Intel GPU 驱动程序<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly93d3cuYW1kLmNvbS9lbi9zdXBwb3J0L2tiL3JlbGVhc2Utbm90ZXMvcm4tcmFkLXdpbi13c2wtc3VwcG9ydA==">AMD GPU 驱动程序<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly9kZXZlbG9wZXIubnZpZGlhLmNvbS9jdWRhL3dzbA==">NVIDIA GPU 驱动程序<i class="fa fa-external-link-alt"></i></span></li></ul><h3 id="更新-wsl"><a href="#更新-wsl" class="headerlink" title="更新 wsl"></a>更新 wsl</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">wsl --update</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">wsl --shutdown</span><br></pre></td></tr></table></figure><h3 id="GUI-测试"><a href="#GUI-测试" class="headerlink" title="GUI 测试"></a>GUI 测试</h3><p>测试一下 GUI 能否正常使用</p><p><code>Nautilus</code>是 gnome 的文件管理器，这里用来安装测试</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">sudo apt install nautilus -y</span><br></pre></td></tr></table></figure><p>安装完成后可以直接打开，像 windows 应用一样</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">nautilus</span><br></pre></td></tr></table></figure><p><img src="./nautilus.png" alt="nautilus"></p><center><p><em>在 Windows 宿主机中 Nautilus 运行截图</em></p></center><p>以后在 windows 的 cmd 中执行 <code>wsl nautilus</code> 即可直接打开</p><h2 id="允许-WSL-嵌套虚拟化"><a href="#允许-WSL-嵌套虚拟化" class="headerlink" title="允许 WSL 嵌套虚拟化"></a>允许 WSL 嵌套虚拟化</h2><p>默认 WSL 没有支持嵌套虚拟化，需要修改一下配置。</p><p>在 Windows 中，用户文件夹下编辑或新建文件 <code>C:\Users\%User%\.wslconfig</code>。<em>（User 是你的 Windows 系统用户名）</em></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><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><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">[wsl2]</span><br><span class="line">networkingMode=bridged</span><br><span class="line">vmSwitch=ex</span><br><span class="line">memory=32G</span><br><span class="line">processors=8</span><br><span class="line">swap=32G</span><br><span class="line">localhostForwarding=true</span><br><span class="line">nestedVirtualization=true</span><br><span class="line">pageReporting=true</span><br><span class="line">kernelCommandLine=intel_iommu=on iommu=pt kvm.ignore_msrs=1 kvm-intel.nested=1 kvm-intel.ept=1 kvm-intel.emulate_invalid_guest_state=0 kvm-intel.enable_shadow_vmcs=1 kvm-intel.enable_apicv=1</span><br></pre></td></tr></table></figure><h3 id="简单说明"><a href="#简单说明" class="headerlink" title="简单说明"></a>简单说明</h3><p>此部分参考 <span class="exturl" data-url="aHR0cHM6Ly9kb2NzLm1pY3Jvc29mdC5jb20vemgtY24vd2luZG93cy93c2wvd3NsLWNvbmZpZw==">https://docs.microsoft.com/zh-cn/windows/wsl/wsl-config<i class="fa fa-external-link-alt"></i></span></p><ul><li>nestedVirtualization 关键，是否允许嵌套虚拟化</li><li>memory 允许的 WSL 内存</li><li>processors 虚拟 CPU 线程</li><li>swap 要向 WSL 2 VM 添加的交换空间量，0 表示无交换文件。 交换存储是内存需求超过硬件设备限制时使用的基于磁盘的 RAM。</li></ul><h3 id="重启-WSL"><a href="#重启-WSL" class="headerlink" title="重启 WSL"></a>重启 WSL</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">wsl --shutdown</span><br></pre></td></tr></table></figure><h2 id="开始安装-MacOS"><a href="#开始安装-MacOS" class="headerlink" title="开始安装 MacOS"></a>开始安装 MacOS</h2><p>使用 OSX-KVM 安装 MacOS 虚拟机</p><p>此部分参考 <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2tob2xpYS9PU1gtS1ZN">https://github.com/kholia/OSX-KVM<i class="fa fa-external-link-alt"></i></span></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></pre></td><td class="code"><pre><span class="line">sudo apt-get install qemu uml-utilities virt-manager git wget libguestfs-tools p7zip-full make -y</span><br></pre></td></tr></table></figure><h4 id="给-KVM-增加一个开关配置"><a href="#给-KVM-增加一个开关配置" class="headerlink" title="给 KVM 增加一个开关配置"></a>给 KVM 增加一个开关配置</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">echo 1 &gt; /sys/module/kvm/parameters/ignore_msrs</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><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo usermod -aG kvm $(whoami)</span><br><span class="line">sudo usermod -aG libvirt $(whoami)</span><br></pre></td></tr></table></figure><h4 id="拉取-OSX-KVM"><a href="#拉取-OSX-KVM" class="headerlink" title="拉取 OSX-KVM"></a>拉取 OSX-KVM</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">git clone https://github.com/kholia/OSX-KVM.git</span><br><span class="line"></span><br><span class="line">cd OSX-KVM</span><br></pre></td></tr></table></figure><h3 id="下载-MacOS-安装镜像"><a href="#下载-MacOS-安装镜像" class="headerlink" title="下载 MacOS 安装镜像"></a>下载 MacOS 安装镜像</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">./fetch-macOS-v2.py</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></pre></td><td class="code"><pre><span class="line">1. High Sierra (10.13)</span><br><span class="line">2. Mojave (10.14)</span><br><span class="line">3. Catalina (10.15)</span><br><span class="line">4. Big Sur (11.6) - RECOMMENDED</span><br><span class="line">5. Monterey (latest)</span><br><span class="line"></span><br><span class="line">Choose a product to download (1-5):</span><br></pre></td></tr></table></figure><p>实测 4 和 5 都可以，而且完成后 Big Sur 也能正常升级到 Monterey，其他没试。</p><p>下载完成后会有 BaseSystem.dmg 文件，需要转为 img 格式</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">dmg2img BaseSystem.dmg BaseSystem.img</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">qemu-img create -f qcow2 mac_hdd_ng.img 128G</span><br></pre></td></tr></table></figure><p>其中 <code>mac_hdd_ng.img</code> 是文件名，可以任意修改</p><h3 id="执行脚本"><a href="#执行脚本" class="headerlink" title="执行脚本"></a>执行脚本</h3><p>先修改 <code>OpenCore-Boot.sh</code> 文件</p><ul><li><code>ALLOCATED_RAM</code> 运行内存，建议最低改为 8G</li><li><code>CPU_THREADS</code> CPU 线程</li><li><code>CPU_CORES</code> CUP 核心数</li><li><code>-drive id=MacHDD,if=none,file=&quot;$REPO_PATH/mac.img&quot;,format=qcow2</code> 其中的 <code>$REPO_PATH/mac_hdd_ng.img</code> 为上一步创建的虚拟磁盘文件</li></ul><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">./OpenCore-Boot.sh</span><br></pre></td></tr></table></figure><p>执行完成后应该会弹出 QEMU 窗口。</p><p>如果没有，请确认前面的步骤 WSL2 是否正确开启 GUI，vGPU 驱动是否已正常安装。</p><p>再次启动也是运行这个脚本</p><h3 id="正在安装"><a href="#正在安装" class="headerlink" title="正在安装"></a>正在安装</h3><p>安装流程和其他方式安装大致相同，只用注意一点，安装完成后，再次启动会多出个引导盘，选择多出来的那个。</p><h4 id="安装界面"><a href="#安装界面" class="headerlink" title="安装界面"></a>安装界面</h4><p>显示要一个多小时，甚至二两多小时，实则不用那么久</p><p><img src="./install.png" alt="安装界面"></p><center><p><em>正在安装 MacOS</em></p></center><h4 id="安装完成后启动"><a href="#安装完成后启动" class="headerlink" title="安装完成后启动"></a>安装完成后启动</h4><p>选择如图第二个启动项</p><p><img src="./startup.png" alt="startup"></p><center><p><em>启动项</em></p></center><p>还需要安装一会，才能进入系统。</p><h3 id="为什么选择-OXS-KVM-而不是-macOS-Simple-KVM"><a href="#为什么选择-OXS-KVM-而不是-macOS-Simple-KVM" class="headerlink" title="为什么选择 OXS-KVM 而不是 macOS-Simple-KVM"></a>为什么选择 OXS-KVM 而不是 macOS-Simple-KVM</h3><p><code>macOS-Simple-KVM</code> 项目地址：<span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2ZveGxldC9tYWNPUy1TaW1wbGUtS1ZN">https://github.com/foxlet/macOS-Simple-KVM<i class="fa fa-external-link-alt"></i></span></p><p>这个项目好像已经停滞不更新，截至目前已经一年多没更新了。</p><p>引导方式是四叶草，最高版本只支持 mojave，连 xcode 都不能用</p><p>故放弃</p><h2 id="使用-virt-manager-管理"><a href="#使用-virt-manager-管理" class="headerlink" title="使用 virt-manager 管理"></a>使用 virt-manager 管理</h2><p>现在虽然你已经能成功使用 MacOS 了，但不方便管理，而且每次启动都需要运行脚本，如果有多个虚拟机就更难管理。</p><p>你可以放弃这一步，若放弃这一步：</p><ol><li>你以后每次启动虚拟机，只能用命令 <code>./OpenCore-Boot.sh</code></li><li>虚拟机运行期间，命令行窗口不能关闭</li><li>虚拟机运行期间，QEMU 窗口不能关闭</li><li>若修改虚拟机配置，需要修改脚本文件，不方便操作</li><li>不能快捷启动，此部分完成后可以在 Windows 宿主机中运行 <code>wsl virt-manager</code> 直接打开管理界面</li></ol><p>我们现在开始使用 <code>virt-manager</code> 管理这个虚拟机，提升使用体验。</p><h3 id="启用-systemd"><a href="#启用-systemd" class="headerlink" title="启用 systemd"></a>启用 systemd</h3><p>如果不是 WSL，Linux 应该都能使用 <code>systemctl</code> 命令，但 WSL 的启动方式决定其不支持 <code>systemctl</code> 命令，因此也无法开启 <code>libvirtd</code>，如果执行 <code>service libvirtd start</code> 会报找不到这个命令的错误，所以 <code>virt-manager</code> 就无法连接到 WSL 中的虚拟机。</p><p>但我们可以使用 <code>genie</code> 工具启用 <code>systemctl</code>。</p><p>启用 <code>systemctl</code> 的部分参考 <span class="exturl" data-url="aHR0cHM6Ly9naXN0LmdpdGh1Yi5jb20vZGpmZHl1cnVpcnkvNjcyMGZhYTNmOWZjNTliZmRmNjI4NGVlMWY0MWY5NTA=">https://gist.github.com/djfdyuruiry/6720faa3f9fc59bfdf6284ee1f41f950<i class="fa fa-external-link-alt"></i></span></p><p>此部分是在 WSL Linux 系统中操作</p><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">cd /tmp</span><br><span class="line">wget --content-disposition \</span><br><span class="line">  &quot;https://gist.githubusercontent.com/djfdyuruiry/6720faa3f9fc59bfdf6284ee1f41f950/raw/952347f805045ba0e6ef7868b18f4a9a8dd2e47a/install-sg.sh&quot;</span><br></pre></td></tr></table></figure><h4 id="修改脚本"><a href="#修改脚本" class="headerlink" title="修改脚本"></a>修改脚本</h4><p>这一步是可选操作，为了安装新版 genie</p><p>genie Release 列表可以在这里查看 <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2Fya2FuZS1zeXN0ZW1zL2dlbmllL3JlbGVhc2Vz">https://github.com/arkane-systems/genie/releases<i class="fa fa-external-link-alt"></i></span></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">vim /tmp/install-sg.sh</span><br></pre></td></tr></table></figure><p>目前最新版是，2.2，因此修改 <code>GENIE_VERSION</code> 值为 2.2，如</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">GENIE_VERSION=&quot;2.2&quot;</span><br></pre></td></tr></table></figure><h4 id="执行脚本-1"><a href="#执行脚本-1" 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></pre></td><td class="code"><pre><span class="line">chmod +x /tmp/install-sg.sh</span><br><span class="line">/tmp/install-sg.sh &amp;&amp; rm /tmp/install-sg.sh</span><br></pre></td></tr></table></figure><ul><li>如果报错 <code>Errors were encountered while processing: systemd-genie</code></li></ul><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">apt install systemd-genie</span><br></pre></td></tr></table></figure><ul><li>如果报错 <code>Unmet dependencies. Try &#39;apt --fix-broken install&#39; with no packages (or specify a solution). root@PC:/tmp# apt update</code></li></ul><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">apt --fix-broken install</span><br></pre></td></tr></table></figure><h3 id="开启-libvirtd"><a href="#开启-libvirtd" class="headerlink" title="开启 libvirtd"></a>开启 libvirtd</h3><p>在 WSL 中执行</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">genie -c systemctl start libvirtd</span><br></pre></td></tr></table></figure><p>这个命令表示进入 genie 并执行 <code>systemctl start libvirtd</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">genie -s</span><br><span class="line">systemctl start libvirtd</span><br><span class="line">exit</span><br></pre></td></tr></table></figure><p>正常启动后就可以使用 virt-manager 了，但是我们还没有将刚才创建的虚拟机加入 virt-manager。</p><p>如果出现 <code>Waiting for systemd....!</code>，用 <code>Ctrl + C</code> 取消即可</p><p>**注意：**这时你不能使用 vGPU，因为启动方式没有采用 WSL 默认的方式。你需要退出 WSL 并重新用 <code>wsl</code> 命令进入 WSL，才可以继续操作</p><h3 id="将虚拟机加入-virt-manager"><a href="#将虚拟机加入-virt-manager" class="headerlink" title="将虚拟机加入 virt-manager"></a>将虚拟机加入 virt-manager</h3><p>此部分参考 <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2tob2xpYS9PU1gtS1ZN">https://github.com/kholia/OSX-KVM<i class="fa fa-external-link-alt"></i></span></p><ul><li>进入前面步骤使用 git 下载的 OSX-KVM 项目目录下，编辑虚拟机配置文件<code>macOS-libvirt-Catalina.xml</code>，把 <code>CHANGEME</code> 全部换为 OSX-KVM 所在路径，根据此教程就是你的 wsl 用户名</li></ul><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">vim ./macOS-libvirt-Catalina.xml</span><br></pre></td></tr></table></figure><ul><li>你也可以用以下脚本快速替换为用户路径：</li></ul><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">sed &quot;s/CHANGEME/$USER/g&quot; macOS-libvirt-Catalina.xml &gt; macOS.xml</span><br><span class="line"></span><br><span class="line">virt-xml-validate macOS.xml</span><br></pre></td></tr></table></figure><ul><li>执行下面命令将虚拟机加入 virt-manager</li></ul><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">virsh --connect qemu:///system define macOS.xml</span><br></pre></td></tr></table></figure><ul><li>执行 <code>virt-manager</code> 以打开 <code>virt-manager</code>，你将能看到名称为 <code>macOS</code> 的虚拟机</li></ul><p><img src="./virt-manager.png" alt="virt-manager"></p><center><p><em>在 Windows 宿主机中 virt-manager 截图</em></p></center><p>现在你可以用 UI 的方式，编辑或启动名为 macOS 的虚拟机了</p><h2 id="快捷启动"><a href="#快捷启动" class="headerlink" title="快捷启动"></a>快捷启动</h2><p><code>virt-manager</code> 正确配置后，在 Windows 宿主机中，创建 <code>.bat</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">wsl genie -c systemctl start libvirtd</span><br><span class="line">wsl virt-manager</span><br></pre></td></tr></table></figure><p>每次运行这个脚本就可以快速打开 <code>virt-manager</code> 了</p><h2 id="遇到的问题"><a href="#遇到的问题" class="headerlink" title="遇到的问题"></a>遇到的问题</h2><p>下面是可能会遇到的问题，有已解决的，也有未解决但有替代方案的</p><h3 id="virt-manager-检测不到-libvirtd"><a href="#virt-manager-检测不到-libvirtd" class="headerlink" title="virt-manager 检测不到 libvirtd"></a>virt-manager 检测不到 libvirtd</h3><p>在 <code>使用 virt-manager 管理</code> 部分有说明</p><h3 id="MacOS-一段时间不操作假死"><a href="#MacOS-一段时间不操作假死" class="headerlink" title="MacOS 一段时间不操作假死"></a>MacOS 一段时间不操作假死</h3><p>在 MacOS 中，关闭节能</p><p>设置位于 <code>系统偏好设置 -&gt; 节能</code></p><ul><li><code>此时间段后关闭显示器</code> 设为 <code>永不</code></li><li>取消勾选 <code>如果可能，使硬盘进入睡眠</code></li></ul><p><img src="./energy.png" alt="关闭节能"></p><h3 id="Waiting-for-systemd…"><a href="#Waiting-for-systemd…" class="headerlink" title="Waiting for systemd….!"></a>Waiting for systemd….!</h3><p>运行 <code>genie</code> 命令可能出现这个问题，是由于 <code>genie</code> 在等待 systemd 回应</p><p>你可以 <code>Control + C</code> 取消并继续操作，也可以永久性的解决这个问题，参考</p><p><span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2Fya2FuZS1zeXN0ZW1zL2dlbmllL3dpa2kvU3lzdGVtZC11bml0cy1rbm93bi10by1iZS1wcm9ibGVtYXRpYy11bmRlci1XU0w=">https://github.com/arkane-systems/genie/wiki/Systemd-units-known-to-be-problematic-under-WSL<i class="fa fa-external-link-alt"></i></span></p><h3 id="和-Windows-宿主机的文件-剪切板传输"><a href="#和-Windows-宿主机的文件-剪切板传输" class="headerlink" title="和 Windows 宿主机的文件&#x2F;剪切板传输"></a>和 Windows 宿主机的文件&#x2F;剪切板传输</h3><p>可以用其他传输工具和剪切板共享工具</p><p>暂时没有直接复制粘贴的方法</p><h3 id="虚拟机中无法收到-Win-键的响应"><a href="#虚拟机中无法收到-Win-键的响应" class="headerlink" title="虚拟机中无法收到 Win 键的响应"></a>虚拟机中无法收到 Win 键的响应</h3><p>即徽标键，在 MacOS 中是 Command 键，影响如 <code>Command + C</code>, <code>Command + V</code> 等快捷键</p><p>可以临时改建，或用 VNC 连接 MacOS 使用</p><p>暂未没有更好的方法</p><h3 id="反复进入启动项选择界面"><a href="#反复进入启动项选择界面" class="headerlink" title="反复进入启动项选择界面"></a>反复进入启动项选择界面</h3><ul><li><p>可能是 CPU 性能不够，或者线程数给的不够，这种只能增加配置或者换电脑</p></li><li><p>也可能是 CPU 配置不正确，将 CPU 配置改为和 KVM 宿主机相同，即与 WSL CPU 配置相同，如图</p></li></ul><p><img src="./cpu-config.png" alt="配置CPU"></p><center><p><em>CPU 配置</em></p></center><h3 id="虚拟机显示大小与窗口大小不匹配"><a href="#虚拟机显示大小与窗口大小不匹配" class="headerlink" title="虚拟机显示大小与窗口大小不匹配"></a>虚拟机显示大小与窗口大小不匹配</h3><p>依次点击 KVM 中的菜单 <code>View -&gt; Resize to VM</code>，如图：</p><p><img src="./screen.png" alt="配置CPU"></p><center><p><em>虚拟机显示大小与窗口大小不匹配</em></p></center><p>或勾选 <code>Fullscreen</code> 以全屏</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本教程使用 WSL2 + KVM 运行 MacOS 虚拟机，MacOS 运行在 Linux 虚拟机中的 KVM 虚拟机，即嵌套虚拟化，但由于 Windows 对 WSL2 优化很好，个人感觉此方案比其他方案更好。&lt;/p&gt;
&lt;p&gt;步骤较多，操作较繁琐，但成果很值得。&lt;/p&gt;</summary>
    
    
    
    <category term="记录" scheme="https://blog.hal.wang/categories/%E8%AE%B0%E5%BD%95/"/>
    
    
    <category term="Windows" scheme="https://blog.hal.wang/tags/Windows/"/>
    
    <category term="Linux" scheme="https://blog.hal.wang/tags/Linux/"/>
    
    <category term="KVM" scheme="https://blog.hal.wang/tags/KVM/"/>
    
    <category term="MacOS" scheme="https://blog.hal.wang/tags/MacOS/"/>
    
    <category term="WSL" scheme="https://blog.hal.wang/tags/WSL/"/>
    
  </entry>
  
  <entry>
    <title>Docker 总结</title>
    <link href="https://blog.hal.wang/8699d8dc/"/>
    <id>https://blog.hal.wang/8699d8dc/</id>
    <published>2021-10-14T08:34:10.000Z</published>
    <updated>2026-03-18T15:50:10.582Z</updated>
    
    <content type="html"><![CDATA[<p>命令参考：<span class="exturl" data-url="aHR0cHM6Ly9kb2NzLmRvY2tlci5jb20vZW5naW5lL3JlZmVyZW5jZS9ydW4v">https://docs.docker.com/engine/reference/run/<i class="fa fa-external-link-alt"></i></span></p><h2 id="常用命令"><a href="#常用命令" class="headerlink" title="常用命令"></a>常用命令</h2><ul><li><code>docker ps</code> 列出正在运行的容器<ul><li><code>-a</code> 列出所有容器，包括未运行的</li><li><code>-q</code> 只列出容器 id <span id="more"></span></li><li><code>-n=?</code> 前几条</li></ul></li><li><code>docker container ls</code> 等同于 <code>docker ps</code></li><li><code>docker image ls</code> 列出镜像<ul><li><code>-q</code> 只列出镜像 id</li></ul></li><li><code>docker iamges</code> 等同于 <code>docker image ls</code></li><li><code>docker volume ls</code> 列出数据卷<ul><li><code>-q</code> 只列出数据卷 id</li></ul></li><li><code>docker pull [OPTIONS] NAME[:TAG|@DIGEST]</code> 从 hub.docker.com 拉取镜像</li><li><code>docker create [OPTIONS] IMAGE [COMMAND] [ARG...]</code> 创建一个新的容器<ul><li><code>--name</code> 容器名称</li><li><code>-v</code> 数据卷</li><li><code>-e</code> 环境变量</li><li><code>-p</code> 端口映射</li><li><code>-it</code> 使用交互方式进行</li></ul></li><li><code>docker run [OPTIONS] IMAGE [COMMAND] [ARG...]</code> 创建并运行一个容器<ul><li><code>-d</code> 后台运行</li></ul></li><li><code>docker rm 容器id</code> 删除指定容器，参数和 <code>docker run</code> 很像<ul><li><code>docker rm -f $(docker ps -aq)</code> 删除所有容器</li><li><code>docker --rm</code> 用完后删除，一般用于测试</li></ul></li><li><code>docker rm iamge</code> 删除镜像<ul><li><code>docker rmi -f $(docker iamges -aq)</code> 删除所有镜像</li></ul></li><li><code>docker rmi</code> 和 <code>docker rm iamge</code> 相同</li><li><code>docker exec [OPTIONS] CONTAINER COMMAND [ARG...]</code> 进入容器后开启一个新的终端</li><li><code>docker attach [OPTIONS] CONTAINER</code> 进入容器正在执行的终端</li><li><code>docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-</code> 拷贝文件</li></ul><h2 id="DockerFile-常用命令"><a href="#DockerFile-常用命令" class="headerlink" title="DockerFile 常用命令"></a>DockerFile 常用命令</h2><ul><li>FROM 基础镜像，一切从这里开始构建</li><li>MAINTAINER 镜像的作者，姓名+邮箱</li><li>RUN 镜像构建时要运行的脚本</li><li>WORKDIR 镜像的工作目录</li><li>VOLUME 挂载的目录</li><li>EXPOST 暴漏端口</li><li>CMD 指定容器启动时要运行的命令，只有最后一个会生效，可被替代</li><li>ENTRYPOINT 和 CMD 类似，但可以追加命令</li><li>ONBUILD 当构建一个被继承 DockerFile 时会运行</li><li>COPY 类似 ADD，拷贝文件至镜像中</li><li>ENV 环境变量</li></ul><h2 id="Docker-网络"><a href="#Docker-网络" class="headerlink" title="Docker 网络"></a>Docker 网络</h2><p>docker 使用桥接模式连接网络，使用技术是 <code>evth-pair</code>，<code>evth-pair</code> 是一对虚拟设备接口，他们都是成对出现的，一端连着协议，一端彼此相连。因此每启动一个容器，docker 会给容器分配一个 ip，并给宿主机分配一个 ip</p><p>宿主机和 docker 容器相互之间都能通过内网互联</p><h3 id="网络模式"><a href="#网络模式" class="headerlink" title="网络模式"></a>网络模式</h3><ul><li>bridge 桥接 docker（默认，自己创建也使用桥接）</li><li>none 不配置网络</li><li>host 和宿主机共享网络</li><li>container 容器网络连通（用的少，局限大）</li></ul><h3 id="创建网络"><a href="#创建网络" class="headerlink" title="创建网络"></a>创建网络</h3><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">docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 &lt;name&gt;</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 network connect &lt;net1&gt; &lt;net2&gt;</span><br></pre></td></tr></table></figure><p>执行后，就将 net1 的网络配置复制到了 net2 的网络下</p><h2 id="MySQL"><a href="#MySQL" class="headerlink" title="MySQL"></a>MySQL</h2><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 run --name mysql5 -v mysql5:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=&lt;password&gt; -d -p 3356:3306 mysql:5.7</span><br></pre></td></tr></table></figure><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 run --name mysql8 -v mysql8:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=&lt;password&gt; -d -p 3386:3306 mysql:8.4 --lower-case-table-names=1</span><br></pre></td></tr></table></figure><h2 id="MSSQL"><a href="#MSSQL" class="headerlink" title="MSSQL"></a>MSSQL</h2><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 run --name=mssql2022 -e &#x27;ACCEPT_EULA=Y&#x27; -e &#x27;SA_PASSWORD=yourStrong(!)Password&#x27; -e &quot;MSSQL_COLLATION=Chinese_PRC_CI_AS&quot; -p 14322:1433 -v mssql2022:/var/opt/mssql/data -v mssql2022backup:/DatabaseBackup -d mcr.microsoft.com/mssql/server:2022-latest</span><br></pre></td></tr></table></figure><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 run --name=mssql2025 -e &#x27;ACCEPT_EULA=Y&#x27; -e &#x27;SA_PASSWORD=yourStrong(!)Password&#x27; -e &quot;MSSQL_COLLATION=Chinese_PRC_CI_AS&quot; -p 14325:1433 -v mssql2025:/var/opt/mssql/data -v mssql2025backup:/DatabaseBackup -d mcr.microsoft.com/mssql/server:2025-latest</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 exec -it &lt;container_id|container_name&gt; /opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P &lt;your_password&gt;</span><br></pre></td></tr></table></figure><h3 id="开启远程访问"><a href="#开启远程访问" class="headerlink" title="开启远程访问"></a>开启远程访问</h3><p>连接数据库后运行</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><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">EXEC</span> sys.sp_configure N<span class="string">&#x27;remote access&#x27;</span>, N<span class="string">&#x27;1&#x27;</span></span><br><span class="line">GO</span><br><span class="line">RECONFIGURE <span class="keyword">WITH</span> OVERRIDE</span><br><span class="line">GO</span><br></pre></td></tr></table></figure><h2 id="MongoDB"><a href="#MongoDB" class="headerlink" title="MongoDB"></a>MongoDB</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></pre></td><td class="code"><pre><span class="line">docker run -it --name mongo5 -p 27017:27017 -v mongo5:/etc/mongo -v mongo5db:/data/db -v mongo5configdb:/data/configdb -d mongo:5 --auth</span><br><span class="line"></span><br><span class="line"># 进入容器，并使用 admin 连接</span><br><span class="line">$ docker exec -it mongo5 mongo admin</span><br><span class="line"># 创建一个名为 admin，密码为 123456 的用户。</span><br><span class="line">&gt;  db.createUser(&#123; user:&#x27;admin&#x27;,pwd:&#x27;123456&#x27;,roles:[ &#123; role:&#x27;userAdminAnyDatabase&#x27;, db: &#x27;admin&#x27;&#125;,&quot;readWriteAnyDatabase&quot;]&#125;);</span><br><span class="line"># 尝试使用上面创建的用户信息进行连接。</span><br><span class="line">&gt; db.auth(&#x27;admin&#x27;, &#x27;123456&#x27;)</span><br></pre></td></tr></table></figure><h2 id="Redis"><a href="#Redis" class="headerlink" title="Redis"></a>Redis</h2><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 run -itd --name redis7 -v redis7:/data -p 6379:6379 redis:7.4</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;命令参考：&lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cHM6Ly9kb2NzLmRvY2tlci5jb20vZW5naW5lL3JlZmVyZW5jZS9ydW4v&quot;&gt;https://docs.docker.com/engine/reference/run/&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;常用命令&quot;&gt;&lt;a href=&quot;#常用命令&quot; class=&quot;headerlink&quot; title=&quot;常用命令&quot;&gt;&lt;/a&gt;常用命令&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;docker ps&lt;/code&gt; 列出正在运行的容器&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-a&lt;/code&gt; 列出所有容器，包括未运行的&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-q&lt;/code&gt; 只列出容器 id</summary>
    
    
    
    <category term="记录" scheme="https://blog.hal.wang/categories/%E8%AE%B0%E5%BD%95/"/>
    
    
    <category term="Docker" scheme="https://blog.hal.wang/tags/Docker/"/>
    
  </entry>
  
  <entry>
    <title>Blazor 学习笔记</title>
    <link href="https://blog.hal.wang/1e28067e/"/>
    <id>https://blog.hal.wang/1e28067e/</id>
    <published>2021-10-13T10:22:24.000Z</published>
    <updated>2026-03-18T15:50:10.582Z</updated>
    
    <content type="html"><![CDATA[<p>之前就听说 Blazor，以为就是和 RazorPage 差不多就没怎么看。今天仔细查了一下，才发现自己错过了如此强大的框架，赶紧学习一下！</p><h2 id="特点"><a href="#特点" class="headerlink" title="特点"></a>特点</h2><ul><li>Blazor 很像 Vue 或 React，可以构建丰富的交互式 Web 应用</li><li>Blazor 中的逻辑代码，完全是用 .net 代码写的，因此前端终于可以换个套路，不再用到 JS 了</li><li>页面语法仍然是 Razor ，但是多了一些特有的元素</li><li>Blazor 真正实现了双向绑定，而不是像 RazorPage 那样的弱绑定。因此现在可以真正的用 MVVM 思想来写前端了</li></ul><span id="more"></span><h2 id="学习资源"><a href="#学习资源" class="headerlink" title="学习资源"></a>学习资源</h2><ul><li>Blazor 开源地址</li></ul><p><span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2RvdG5ldC9hc3BuZXRjb3Jl">https://github.com/dotnet/aspnetcore<i class="fa fa-external-link-alt"></i></span></p><ul><li>微软官方 Blazor 网站</li></ul><p><span class="exturl" data-url="aHR0cHM6Ly9kb3RuZXQubWljcm9zb2Z0LmNvbS9hcHBzL2FzcG5ldC93ZWItYXBwcy9ibGF6b3I=">https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor<i class="fa fa-external-link-alt"></i></span></p><ul><li>Blazor Extensions - 精选 Blazor 扩展</li></ul><p><span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL0JsYXpvckV4dGVuc2lvbnM=">https://github.com/BlazorExtensions<i class="fa fa-external-link-alt"></i></span></p><ul><li>Study Blazor</li></ul><p><span class="exturl" data-url="aHR0cHM6Ly9zdHVkeWJsYXpvci5jb20v">https://studyblazor.com<i class="fa fa-external-link-alt"></i></span></p><ul><li>Ant Design Blazor</li></ul><p><span class="exturl" data-url="aHR0cHM6Ly9hbnQtZGVzaWduLWJsYXpvci5naXRlZS5pby8=">https://ant-design-blazor.gitee.io<i class="fa fa-external-link-alt"></i></span></p><h2 id="运行模式"><a href="#运行模式" class="headerlink" title="运行模式"></a>运行模式</h2><p>Blazor 分为两种模式</p><ol><li>Blazor Server 即服务端模式</li><li>Blazor WebAssembly 即 WebAssembly 模式</li></ol><h3 id="服务端模式"><a href="#服务端模式" class="headerlink" title="服务端模式"></a>服务端模式</h3><p>服务端模式是在服务端运行并渲染，浏览器使用 SignalR 实时推送至服务器，服务器再返回给浏览器 DOM 差异部分。</p><p>也就是说，服务端模式是通过 SignalR 实现实时交互的</p><h3 id="WebAssembly-模式"><a href="#WebAssembly-模式" class="headerlink" title="WebAssembly 模式"></a>WebAssembly 模式</h3><p>WebAssembly 模式所有的 .net 代码都是在浏览器中通过 JavaScript 运行的，因此首次加载页面会比较慢，需要下载一些文件。</p><p>WebAssembly 模式是基于 WebAssembly（缩写 WASM）实现的</p><p>WASM 已支持所有现代浏览器，并且优化速度很快，接近本地性能，得益于 WASM，传统软件有望使用 web 实现 </p>]]></content>
    
    
    <summary type="html">&lt;p&gt;之前就听说 Blazor，以为就是和 RazorPage 差不多就没怎么看。今天仔细查了一下，才发现自己错过了如此强大的框架，赶紧学习一下！&lt;/p&gt;
&lt;h2 id=&quot;特点&quot;&gt;&lt;a href=&quot;#特点&quot; class=&quot;headerlink&quot; title=&quot;特点&quot;&gt;&lt;/a&gt;特点&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Blazor 很像 Vue 或 React，可以构建丰富的交互式 Web 应用&lt;/li&gt;
&lt;li&gt;Blazor 中的逻辑代码，完全是用 .net 代码写的，因此前端终于可以换个套路，不再用到 JS 了&lt;/li&gt;
&lt;li&gt;页面语法仍然是 Razor ，但是多了一些特有的元素&lt;/li&gt;
&lt;li&gt;Blazor 真正实现了双向绑定，而不是像 RazorPage 那样的弱绑定。因此现在可以真正的用 MVVM 思想来写前端了&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="记录" scheme="https://blog.hal.wang/categories/%E8%AE%B0%E5%BD%95/"/>
    
    
    <category term="C#" scheme="https://blog.hal.wang/tags/C/"/>
    
    <category term="Blazor" scheme="https://blog.hal.wang/tags/Blazor/"/>
    
  </entry>
  
  <entry>
    <title>Linux 允许 root 账号 ssh 登录</title>
    <link href="https://blog.hal.wang/1f9e5277/"/>
    <id>https://blog.hal.wang/1f9e5277/</id>
    <published>2021-10-06T21:51:13.000Z</published>
    <updated>2026-03-18T15:50:10.582Z</updated>
    
    <content type="html"><![CDATA[<p>默认情况 Linux 不允许 root 账号 ssh 登录，但有些远程命令如 <code>scp</code> 却需要 sudo 权限，因此需要使用 root 账号 ssh 登录</p><h2 id="开启-root-账号-ssh-登录"><a href="#开启-root-账号-ssh-登录" class="headerlink" title="开启 root 账号 ssh 登录"></a>开启 root 账号 ssh 登录</h2><h3 id="编辑-etc-ssh-sshd-config-文件"><a href="#编辑-etc-ssh-sshd-config-文件" class="headerlink" title="编辑 /etc/ssh/sshd_config 文件"></a>编辑 <code>/etc/ssh/sshd_config</code> 文件</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">vim /etc/ssh/sshd_config</span><br></pre></td></tr></table></figure><span id="more"></span><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">#PermitRootLogin prohibit-password</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">PermitRootLogin yes</span><br></pre></td></tr></table></figure><p>注意删除前面 <code>#</code></p><h3 id="重启-SSH-服务"><a href="#重启-SSH-服务" class="headerlink" title="重启 SSH 服务"></a>重启 SSH 服务</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">systemctl restart sshd</span><br></pre></td></tr></table></figure><h2 id="无密码登录"><a href="#无密码登录" class="headerlink" title="无密码登录"></a>无密码登录</h2><p>配置 远程端公钥 和 本地端私钥，可无需密码连接</p><p>这里仅记录远程端 Linux 如何配置公钥</p><h3 id="编辑-root-ssh-authorized-keys-文件"><a href="#编辑-root-ssh-authorized-keys-文件" class="headerlink" title="编辑 /root/.ssh/authorized_keys 文件"></a>编辑 <code>/root/.ssh/authorized_keys</code> 文件</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">vim /root/.ssh/authorized_keys</span><br></pre></td></tr></table></figure><p>添加公钥内容</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">ssh root@&lt;hostname&gt;</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;默认情况 Linux 不允许 root 账号 ssh 登录，但有些远程命令如 &lt;code&gt;scp&lt;/code&gt; 却需要 sudo 权限，因此需要使用 root 账号 ssh 登录&lt;/p&gt;
&lt;h2 id=&quot;开启-root-账号-ssh-登录&quot;&gt;&lt;a href=&quot;#开启-root-账号-ssh-登录&quot; class=&quot;headerlink&quot; title=&quot;开启 root 账号 ssh 登录&quot;&gt;&lt;/a&gt;开启 root 账号 ssh 登录&lt;/h2&gt;&lt;h3 id=&quot;编辑-etc-ssh-sshd-config-文件&quot;&gt;&lt;a href=&quot;#编辑-etc-ssh-sshd-config-文件&quot; class=&quot;headerlink&quot; title=&quot;编辑 /etc/ssh/sshd_config 文件&quot;&gt;&lt;/a&gt;编辑 &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt; 文件&lt;/h3&gt;&lt;figure class=&quot;highlight plaintext&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;vim /etc/ssh/sshd_config&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    <category term="记录" scheme="https://blog.hal.wang/categories/%E8%AE%B0%E5%BD%95/"/>
    
    
    <category term="Linux" scheme="https://blog.hal.wang/tags/Linux/"/>
    
    <category term="SSH" scheme="https://blog.hal.wang/tags/SSH/"/>
    
  </entry>
  
  <entry>
    <title>在 Docker 中安装 mysql</title>
    <link href="https://blog.hal.wang/653fd091/"/>
    <id>https://blog.hal.wang/653fd091/</id>
    <published>2021-08-10T12:17:26.000Z</published>
    <updated>2026-03-18T15:50:10.586Z</updated>
    
    <content type="html"><![CDATA[<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 pull mysql:8</span><br></pre></td></tr></table></figure><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 run --name mysql8 -v mysql8:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=&lt;password&gt; -d -p 3306:3306 mysql:8 --lower-case-table-names=1 --default-authentication-plugin=mysql_native_password</span><br></pre></td></tr></table></figure><span id="more"></span><h2 id="命令解释"><a href="#命令解释" class="headerlink" title="命令解释"></a>命令解释</h2><ul><li>–name: 容器名称</li><li>-v: 数据卷</li><li>-e: 环境变量<ul><li>MYSQL_ROOT_PASSWORD: root 密码</li></ul></li><li>-p: 端口映射</li><li>–lower-case-table-names: 表明大小写规则</li><li>–default-authentication-plugin: 默认密码验证规则</li></ul><h2 id="my-ini-my-cnf"><a href="#my-ini-my-cnf" class="headerlink" title="my.ini&#x2F;my.cnf"></a>my.ini&#x2F;my.cnf</h2><p>在 docker 中修改 <code>my.ini/my.cnf</code> 不方便，而且每次创建容器都需要修改。但 docker 中的 mysql 支持加配置参数，相当于初始化即修改 <code>my.ini/my.cnf</code></p><p>如：</p><ul><li>–lower-case-table-names</li><li>–default-authentication-plugin</li></ul><p>在 windows 中，可能需要配置 <code>lower-case-table-names</code></p><p>使用 mysql8.0，目前很多情况下需要配置 <code>default-authentication-plugin</code> 为 <code>mysql_native_password</code></p>]]></content>
    
    
    <summary type="html">&lt;figure class=&quot;highlight plaintext&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;docker pull mysql:8&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;figure class=&quot;highlight plaintext&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;docker run --name mysql8 -v mysql8:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=&amp;lt;password&amp;gt; -d -p 3306:3306 mysql:8 --lower-case-table-names=1 --default-authentication-plugin=mysql_native_password&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    <category term="记录" scheme="https://blog.hal.wang/categories/%E8%AE%B0%E5%BD%95/"/>
    
    
    <category term="Docker" scheme="https://blog.hal.wang/tags/Docker/"/>
    
    <category term="MySQL" scheme="https://blog.hal.wang/tags/MySQL/"/>
    
  </entry>
  
</feed>
