<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Hzzly</title>
  
  <subtitle>上善若水，笃学敦行</subtitle>
  <link href="http://yoursite.com/atom.xml" rel="self"/>
  
  <link href="http://yoursite.com/"/>
  <updated>2022-07-12T15:36:10.731Z</updated>
  <id>http://yoursite.com/</id>
  
  <author>
    <name>hzzly</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>前端工程化-CI之Github Actions</title>
    <link href="http://yoursite.com/2022/07/12/%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%8C%96-CI%E4%B9%8BGithub%20Actions/"/>
    <id>http://yoursite.com/2022/07/12/%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%8C%96-CI%E4%B9%8BGithub%20Actions/</id>
    <published>2022-07-12T14:57:56.000Z</published>
    <updated>2022-07-12T15:36:10.731Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>前言：在 CI&#x2F;CD 和 DevOps 领域中，持续交付和持续部署是一个老生常谈的话题，也属于前端工程化的范畴。持续集成这个术语最早是在 1994 年由 Grady Booch 提出。持续集成是借助工具对软件项目进行持续的自动化的编译打包构建测试发布，来检查软件交付质量的一种行为。而持续部署是基于持续交付的优势自动将经过测试的代码推入生产环境的过程。接下来我们主要来聊聊前端工程化中的持续集成服务（CI）。</p></blockquote><p>目前社区已有一些出色的持续集成服务（CI）的选择：</p><ul><li><code>Jenkins</code></li><li><code>Travis CI</code></li><li><code>GitLab CI</code></li><li><code>CircleCI</code></li><li><code>GitHub Actions</code></li><li>…</li></ul><p>下面我们主要介绍一下 <code>GitHub Actions</code>：</p><p>它使用起来比较简单，只要在我们的仓库根目录建立<code>.github/workflows</code>文件夹，然后将我们的工作流配置(<strong>YAML 文件</strong>)放到这个目录下，就能启用<strong>GitHub Actions</strong>服务。</p><h1 id="GitHub-Actions-是什么"><a href="#GitHub-Actions-是什么" class="headerlink" title="GitHub Actions 是什么"></a>GitHub Actions 是什么</h1><p>Github Actions 是 Github 的持续集成服务（CI），在 2018 年 10 月推出。</p><p>我们知道，持续集成（CI）由很多操作组成，比如抓取代码、运行测试、登录远程服务器，发布到第三方服务等等。GitHub 把这些操作都称为 actions。</p><p>很多操作在不同项目里面是类似的，脚本完全可以共享。因此 <code>GitHub</code> 允许开发者把每个操作写成独立的脚本文件，存放到代码仓库并发布到<a href="https://github.com/marketplace?type=actions">官方市场</a>，使得其他开发者可以直接引用。</p><h1 id="GitHub-Actions-基本概念"><a href="#GitHub-Actions-基本概念" class="headerlink" title="GitHub Actions 基本概念"></a>GitHub Actions 基本概念</h1><p><code>GitHub Actions</code> 有一些自己的术语：</p><ul><li><strong>workflow</strong> （工作流程）：持续集成一次运行的过程，就是一个 workflow。</li><li><strong>job</strong> （任务）：一个 workflow 由一个或多个 jobs 构成，含义是一次持续集成的运行，可以完成多个任务。</li><li><strong>step</strong>（步骤）：每个 job 由多个 step 构成，一步步完成。</li><li><strong>action</strong> （动作）：每个 step 可以依次执行一个或多个命令（action）。</li></ul><h1 id="GitHub-Actions-使用"><a href="#GitHub-Actions-使用" class="headerlink" title="GitHub Actions 使用"></a>GitHub Actions 使用</h1><p>它使用起来比较简单，只要在我们的仓库根目录建立<code>.github/workflows</code>文件夹，然后将我们的工作流配置文件（<strong>YAML 格式文件</strong>）放到这个目录下。（<code>GitHub Actions</code> 的配置文件叫做 <code>workflow</code> 文件，文件名我们可以任意取，后缀名统一为<code>.yml</code>，比如 <code>ci.yml</code>。一个库可以有多个 workflow 文件，GitHub 只要发现<code>.github/workflows</code>目录里面有<code>.yml</code>文件，就会自动运行该文件）</p><p>如下是一个需要 CI 运行 <code>yarn ci</code> 的 <code>workflow</code> 文件示例：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># name 字段是 workflow 的名称。如果省略该字段，默认为当前 workflow 的文件名</span></span><br><span class="line"><span class="attr">name:</span> <span class="string">ci</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># on 字段指定触发 workflow 的条件，通常是某些事件，如下是 push 事件和 pull_request 事件触发 workflow</span></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line">  <span class="attr">push:</span></span><br><span class="line">    <span class="comment"># 指定只有 main 分支发生 push 事件时，才会触发 workflow</span></span><br><span class="line">    <span class="attr">branches:</span> [<span class="string">main</span>]</span><br><span class="line">  <span class="attr">pull_request:</span></span><br><span class="line">    <span class="attr">branches:</span> [<span class="string">main</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment"># jobs 字段，表示要执行的一项或多项任务</span></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="comment"># 每一项任务的 job_id，这里是 build 作为 job_id</span></span><br><span class="line">  <span class="attr">build:</span></span><br><span class="line">    <span class="comment"># name 字段是任务的说明</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">build</span></span><br><span class="line">    <span class="comment"># runs-on字段指定运行所需要的虚拟机环境。它是必填字段。有如下三种</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line">    <span class="comment"># runs-on: windows-latest</span></span><br><span class="line">    <span class="comment"># runs-on: macOS-latest</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># steps 字段指定每一项任务的运行步骤，可以包含一个或多个步骤</span></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">      <span class="comment"># 步骤名称</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Checkout</span></span><br><span class="line">        <span class="comment"># 使用的 action</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">actions/checkout@v3</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Setup</span> <span class="string">Node.js</span> <span class="string">environment</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">actions/setup-node@v3</span></span><br><span class="line">        <span class="comment"># 定义的输入参数，每个输入参数都是一个键/值对</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">node-version:</span> <span class="number">16</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Cache</span> <span class="string">node</span> <span class="string">modules</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">actions/cache@v3</span></span><br><span class="line">        <span class="comment"># 该步骤所需的环境变量</span></span><br><span class="line">        <span class="attr">env:</span></span><br><span class="line">          <span class="attr">cache-name:</span> <span class="string">cache-node-modules</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">path:</span> <span class="string">./node_modules</span></span><br><span class="line">          <span class="attr">key:</span> <span class="string">$&#123;&#123;</span> <span class="string">runner.os</span> <span class="string">&#125;&#125;-build-cache-node-modules-$&#123;&#123;</span> <span class="string">hashFiles(&#x27;**/package.json&#x27;)</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">restore-keys:</span> <span class="string">|</span></span><br><span class="line"><span class="string">            cache-node-modules-</span></span><br><span class="line"><span class="string"></span>      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Run</span> <span class="string">ci</span></span><br><span class="line">        <span class="comment"># 该步骤运行的命令或者 action</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">          yarn</span></span><br><span class="line"><span class="string">          yarn ci</span></span><br><span class="line"><span class="string"></span></span><br><span class="line">  <span class="comment"># 下面运行顺序依次为：job1、job2、job3</span></span><br><span class="line">  <span class="comment"># job1:</span></span><br><span class="line">  <span class="comment"># job2:</span></span><br><span class="line">  <span class="comment"># needs 字段指定当前任务的依赖关系，即运行顺序</span></span><br><span class="line">  <span class="comment"># needs: job1</span></span><br><span class="line">  <span class="comment"># job3:</span></span><br><span class="line">  <span class="comment"># needs: [job1, job2]</span></span><br></pre></td></tr></table></figure><p>更多语法参考：<a href="https://docs.github.com/cn/actions/using-workflows/workflow-syntax-for-github-actions">GitHub Actions 的工作流程语法</a></p><h2 id="示例：npm-包自动发布"><a href="#示例：npm-包自动发布" class="headerlink" title="示例：npm 包自动发布"></a>示例：npm 包自动发布</h2><p>因为我也会经常的在 <code>Github</code> 上开源一些工具类，同时需要同步发 布到 <code>npm</code> 上，因此也是希望代码提交后能自动的实现 <code>npm</code> 包的发布。接下来就主要记录一下使用 <code>GitHub Actions</code> 自动发布 <code>npm</code> 包。</p><p>eg：<a href="https://github.com/hzzlyxx/eslint-config-ts">hzzlyxx&#x2F;eslint-config-ts</a></p><h2 id="1、生成-npm-Access-Token"><a href="#1、生成-npm-Access-Token" class="headerlink" title="1、生成 npm Access Token"></a>1、生成 npm Access Token</h2><p><a href="https://docs.npmjs.com/creating-and-viewing-access-tokens">creating-and-viewing-access-tokens</a></p><ul><li><p>登录 <a href="https://www.npmjs.com/">npm 官网</a>，在头像下拉选项中选中 <strong>Access Tokens</strong> 进入界面</p></li><li><p>点击 <strong>Generate New Token</strong> 按钮，选择 <strong>Automation</strong> 或者 <strong>publish</strong> 选项，再点击 <strong>Generate Token</strong> 按钮</p></li><li><p>界面提示 <strong>Successfully generated a new token!</strong> ，复制生成的 <strong>token</strong></p></li></ul><h2 id="2、注入-secret"><a href="#2、注入-secret" class="headerlink" title="2、注入 secret"></a>2、注入 secret</h2><ul><li><p>点击仓库上 <strong>Settings</strong> 菜单进入设置界面</p></li><li><p>在左侧菜单栏选中 <strong>Secrets&#x2F;actions</strong> 进入，然后点击 <strong>New repository secret</strong> 按钮，输入表单信息，<strong>Name</strong> 为 <code>NPM_TOKEN</code>，<strong>Value</strong> 为上一步生成的 <code>npm Access Token</code>，最后点击 <strong>Add Secret</strong> 按钮添加完成</p></li></ul><h2 id="3、添加-yml-文件"><a href="#3、添加-yml-文件" class="headerlink" title="3、添加 yml 文件"></a>3、添加 yml 文件</h2><p>在仓库根目录建立<code>.github/workflows</code>文件夹，并新建 <code>npm.yml</code> 文件，填写如下内容：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">publish</span></span><br><span class="line"></span><br><span class="line"><span class="attr">on:</span> [<span class="string">push</span>, <span class="string">pull_request</span>]</span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">publish:</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">actions/checkout@v3</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">actions/setup-node@v3</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">node-version:</span> <span class="number">16</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Npm</span> <span class="string">Publish</span></span><br><span class="line">      <span class="attr">uses:</span> <span class="string">JS-DevTools/npm-publish@v1</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">token:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.NPM_TOKEN</span> <span class="string">&#125;&#125;</span></span><br></pre></td></tr></table></figure><h2 id="4、push-代码"><a href="#4、push-代码" class="headerlink" title="4、push 代码"></a>4、push 代码</h2><p>保存上面的文件后，将整个仓库推送到 GitHub。</p><p>GitHub 发现了 workflow 文件以后，就会自动运行。然后就可以在仓库的 <strong>Actions</strong> 菜单查看日志，日志默认保存 30 天。</p><p>Tips：package 的 version，是由代码库中的 package.json -&gt; version 指定的， 如果要重新发布，需要手动修改 version 值。</p><h2 id="5、npm-上查看"><a href="#5、npm-上查看" class="headerlink" title="5、npm 上查看"></a>5、npm 上查看</h2><p>回到 npm 上，查看账户下的 <code>packages</code>。如果有，说明自动发布成功了。</p><h1 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h1><p>到这，我们的<strong>GitHub Actions</strong>持续集成服务（CI）就告一段落了，后续还将探索其他 CI 方案以及前端工程化。</p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ul><li><a href="http://www.ruanyifeng.com/blog/2019/09/getting-started-with-github-actions.html">GitHub Actions 入门教程</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;前言：在 CI&amp;#x2F;CD 和 DevOps 领域中，持续交付和持续部署是一个老生常谈的话题，也属于前端工程化的范畴。持续集成这个术语最早是在 1994 年由 Grady Booch 提出。持续集成是借助工具对软件项目进行持续的自动化的编译打包</summary>
      
    
    
    
    
    <category term="CICD" scheme="http://yoursite.com/tags/CICD/"/>
    
  </entry>
  
  <entry>
    <title>webpack5升级之旅</title>
    <link href="http://yoursite.com/2022/03/21/webpack5%E5%8D%87%E7%BA%A7%E4%B9%8B%E6%97%85/"/>
    <id>http://yoursite.com/2022/03/21/webpack5%E5%8D%87%E7%BA%A7%E4%B9%8B%E6%97%85/</id>
    <published>2022-03-21T15:33:11.000Z</published>
    <updated>2022-03-21T15:34:58.664Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>前言：2020 年 10 月 10 日，webpack5 正式发布，并带来了诸多重大的变更，使前端的构建效率与质量大为提升。最近也是趁着空闲时间把项目里的 webapck4 进行了升级，接下来就介绍一下我的踩坑升级之旅吧。</p></blockquote><h2 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h2><h3 id="node-版本"><a href="#node-版本" class="headerlink" title="node 版本"></a>node 版本</h3><p>Webpack 5 对 Node.js 的版本要求至少是 10.13.0 (LTS)，如果还在使用旧版本的 Node.js，需要进行升级。</p><h3 id="升级-webpack"><a href="#升级-webpack" class="headerlink" title="升级 webpack"></a>升级 webpack</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn add webpack webpack-cli -D</span><br></pre></td></tr></table></figure><h3 id="检查依赖是否过期及升级相关包的版本"><a href="#检查依赖是否过期及升级相关包的版本" class="headerlink" title="检查依赖是否过期及升级相关包的版本"></a>检查依赖是否过期及升级相关包的版本</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn outdated</span><br></pre></td></tr></table></figure><p>将会列出<code>package.json</code>所有依赖的版本信息，包括当前安装版本，给予 semver 渴望的版本以及最新的版本。</p><p>接下来就说升级所有使用到的 plugin 和 loader 为最新的可用版本。</p><h3 id="升级废弃的配置项"><a href="#升级废弃的配置项" class="headerlink" title="升级废弃的配置项"></a>升级废弃的配置项</h3><p>如有使用以下的配置项，请升级至最新的版本：</p><ul><li><code>optimization.hashedModuleIds: true</code> → <code>optimization.moduleIds: &#39;hashed&#39;</code></li><li><code>optimization.namedChunks: true</code> → <code>optimization.chunkIds: &#39;named&#39;</code></li><li><code>optimization.namedModules: true</code> → <code>optimization.moduleIds: &#39;named&#39;</code></li><li><code>NamedModulesPlugin</code> → <code>optimization.moduleIds: &#39;named&#39;</code></li><li><code>NamedChunksPlugin</code> → <code>optimization.chunkIds: &#39;named&#39;</code></li><li><code>HashedModuleIdsPlugin</code> → <code>optimization.moduleIds: &#39;hashed&#39;</code></li><li><code>optimization.noEmitOnErrors: false</code> → <code>optimization.emitOnErrors: true</code></li><li><code>optimization.occurrenceOrder: true</code> → <code>optimization: &#123; chunkIds: &#39;total-size&#39;, moduleIds: &#39;size&#39; &#125;</code></li><li><code>optimization.splitChunks.cacheGroups.vendors</code> → <code>optimization.splitChunks.cacheGroups.defaultVendors</code></li><li><code>Compilation.entries</code> → <code>Compilation.entryDependencies</code></li><li><code>serve</code> → <code>serve</code> 已被移除，推荐使用 <a href="https://webpack.docschina.org/configuration/dev-server/"><code>DevServer</code></a></li><li><a href="https://webpack.docschina.org/configuration/module/#ruleoptions--rulequery"><code>Rule.query</code></a> (从 v3 开始被移除) → <code>Rule.options</code>&#x2F;<code>UseEntry.options</code></li></ul><h3 id="清理配置"><a href="#清理配置" class="headerlink" title="清理配置"></a>清理配置</h3><ul><li>将 <code>optimization.moduleIds</code> 和 <code>optimization.chunkIds</code> 从配置中移除，使用默认值会更合适，因为它们会在 <code>production 模式</code> 下支持长效缓存且可以在 <code>development</code> 模式&#96; 下进行调试。</li><li>将配置中使用的 <code>[hash]</code> 占位符改为 <code>[contenthash]</code>。效果一致，但事实证明会更为有效</li><li>正则表达式参数的 <code>IgnorePlugin</code>，现已支持传入一个 <code>options</code> 对象：&#96;new IgnorePlugin({ resourceRegExp: &#x2F;regExp&#x2F; })</li></ul><h2 id="踩坑记录点"><a href="#踩坑记录点" class="headerlink" title="踩坑记录点"></a>踩坑记录点</h2><h3 id="1、webpack-dev-server配置属性变更"><a href="#1、webpack-dev-server配置属性变更" class="headerlink" title="1、webpack-dev-server配置属性变更"></a>1、<a href="https://github.com/webpack/webpack-dev-server">webpack-dev-server</a>配置属性变更</h3><ul><li>contentBase -&gt; static.directory</li><li>移除 stats</li><li>移除 disableHostCheck</li><li>openPage -&gt; open</li><li>before -&gt; onBeforeSetupMiddleware</li><li>after -&gt; onAfterSetupMiddleware</li><li>…</li></ul><p>以上列举了一些常用配置修改，具体 v3 升级到 v4 的迁移指南可以查看官方文档 <a href="https://github.com/webpack/webpack-dev-server/blob/master/migration-v4.md">https://github.com/webpack/webpack-dev-server/blob/master/migration-v4.md</a></p><h3 id="2、多入口开启-hot-true-热更新无效"><a href="#2、多入口开启-hot-true-热更新无效" class="headerlink" title="2、多入口开启 hot: true 热更新无效"></a>2、多入口开启 <code>hot: true</code> 热更新无效</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">optimization</span>: &#123;</span><br><span class="line">  <span class="attr">runtimeChunk</span>: <span class="string">&#x27;single&#x27;</span></span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure><p>具体可以查看<a href="https://github.com/webpack/webpack-dev-server/issues/2792">webpack-dev-server&#x2F;issues&#x2F;2792</a></p><h3 id="3、JSON-模块需要使用具名导出"><a href="#3、JSON-模块需要使用具名导出" class="headerlink" title="3、JSON 模块需要使用具名导出"></a>3、JSON 模块需要使用具名导出</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pkg <span class="keyword">from</span> <span class="string">&#x27;./package.json&#x27;</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(pkg.<span class="property">version</span>);</span><br></pre></td></tr></table></figure><h3 id="4、webpack-merge-变更"><a href="#4、webpack-merge-变更" class="headerlink" title="4、webpack-merge 变更"></a>4、webpack-merge 变更</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack 4.x</span></span><br><span class="line"><span class="keyword">const</span> merge = <span class="built_in">require</span>(<span class="string">&#x27;webpack-merge&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// webpack 5.x</span></span><br><span class="line"><span class="keyword">const</span> &#123; merge &#125; = <span class="built_in">require</span>(<span class="string">&#x27;webpack-merge&#x27;</span>);</span><br></pre></td></tr></table></figure><h3 id="5、optimize-css-assets-webpack-plugin-变更（官方准备弃用了）"><a href="#5、optimize-css-assets-webpack-plugin-变更（官方准备弃用了）" class="headerlink" title="5、optimize-css-assets-webpack-plugin 变更（官方准备弃用了）"></a>5、optimize-css-assets-webpack-plugin 变更（官方准备弃用了）</h3><p>使用官方推荐的新插件：<a href="https://github.com/webpack-contrib/css-minimizer-webpack-plugin">css-minimizer-webpack-plugin</a></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn add css-minimizer-webpack-plugin -D</span><br></pre></td></tr></table></figure><h3 id="6、url-loader、file-loader、raw-loader（将来会被淘汰）"><a href="#6、url-loader、file-loader、raw-loader（将来会被淘汰）" class="headerlink" title="6、url-loader、file-loader、raw-loader（将来会被淘汰）"></a>6、url-loader、file-loader、raw-loader（将来会被淘汰）</h3><p>官方推荐：<a href="https://webpack.docschina.org/guides/asset-modules/">资源模块</a></p><h3 id="7、config-module-rules-里配置了-loaders"><a href="#7、config-module-rules-里配置了-loaders" class="headerlink" title="7、config.module.rules 里配置了 loaders"></a>7、config.module.rules 里配置了 loaders</h3><p>webpack5 不再支持 loaders 的属性，需要改成 <code>use</code></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">test</span>: <span class="regexp">/\.(sc|sa|c)ss$/</span>,</span><br><span class="line"><span class="attr">use</span>: [<span class="string">&#x27;style-loader&#x27;</span>, <span class="string">&#x27;css-loader&#x27;</span>, <span class="string">&#x27;postcss-loader&#x27;</span>, <span class="string">&#x27;sass-loader&#x27;</span>],</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="8、heap-eval-module-source-map-名称修改"><a href="#8、heap-eval-module-source-map-名称修改" class="headerlink" title="8、heap-eval-module-source-map 名称修改"></a>8、heap-eval-module-source-map 名称修改</h3><p>需要改成 <code>eval-cheap-module-source-map</code></p><h3 id="9、copy-webpack-plugin-报错"><a href="#9、copy-webpack-plugin-报错" class="headerlink" title="9、copy-webpack-plugin 报错"></a>9、copy-webpack-plugin 报错</h3><blockquote><p>ValidationError: Invalid options object. Copy Plugin has been initialized using an options object that does not match the API schema.</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">plugins</span>: [</span><br><span class="line">    <span class="comment">// webpack 4.x</span></span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">CopyWebpackPlugin</span>([<span class="string">&#x27;public&#x27;</span>])</span><br><span class="line"></span><br><span class="line">    <span class="comment">// webpack 5.x</span></span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">CopyWebpackPlugin</span>(&#123;</span><br><span class="line">      <span class="attr">patterns</span>: [</span><br><span class="line">        &#123; <span class="attr">from</span>: <span class="string">&#x27;public&#x27;</span>, <span class="attr">to</span>: <span class="string">&#x27;public&#x27;</span> &#125;</span><br><span class="line">      ]</span><br><span class="line">    &#125;)</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="10、去除-dll"><a href="#10、去除-dll" class="headerlink" title="10、去除 dll"></a>10、去除 dll</h3><p>在 webpack5 中已经不建议使用这种方式进行模块缓存，因为其已经内置了更好的 cache 方法</p><h3 id="11、cache-持久化缓存"><a href="#11、cache-持久化缓存" class="headerlink" title="11、cache 持久化缓存"></a>11、cache 持久化缓存</h3><p>通过配置 <a href="https://link.juejin.cn/?target=https://webpack.docschina.org/configuration/cache/%23root">cache</a> 缓存生成的 webpack 模块和 chunk，来改善构建速度。</p><p>webpack5 默认将构建的缓存结果放在 node_modules&#x2F;.cache 目录下，可以通过配置更改目录。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">cache</span>: &#123;</span><br><span class="line">    <span class="comment">// 将缓存类型设置为文件系统</span></span><br><span class="line">    <span class="attr">type</span>: <span class="string">&#x27;filesystem&#x27;</span>,</span><br><span class="line">    <span class="attr">buildDependencies</span>: &#123;</span><br><span class="line">      <span class="comment">/* 将你的 config 添加为 buildDependency，以便在改变 config 时获得缓存无效 */</span></span><br><span class="line">      <span class="attr">config</span>: [__filename],</span><br><span class="line">      <span class="comment">/* 如果有其他的东西被构建依赖，你可以在这里添加它们 */</span></span><br><span class="line">      <span class="comment">/* 注意，webpack.config，加载器和所有从你的配置中引用的模块都会被自动添加 */</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// 指定缓存的版本</span></span><br><span class="line">    <span class="attr">version</span>: <span class="string">&#x27;1.0&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><blockquote><p>Tips:</p><ul><li>cache 的属性 type 会在开发模式下被默认设置成 memory，而且在生产模式中被禁用，所以如果想要在生产打包时使用缓存需要显式的设置。</li><li>为了防止缓存过于固定，导致更改构建配置无感知，依然使用旧的缓存，默认情况下，每次修改构建配置文件都会导致重新开始缓存。当然也可以自己主动设置 version 来控制缓存的更新。</li></ul></blockquote><p>更多缓存配置可以参考官方文档 <a href="https://webpack.js.org/configuration/other-options/#cache">https://webpack.js.org/configuration/other-options/#cache</a></p><h3 id="12、Node-Polyfill-脚本被移除"><a href="#12、Node-Polyfill-脚本被移除" class="headerlink" title="12、Node Polyfill 脚本被移除"></a>12、Node Polyfill 脚本被移除</h3><p>webpack4 版本中附带了大多数 Node.js 核心模块的 polyfill，一旦前端使用了任何核心模块，这些模块就会自动应用，但是其实有些是不必要的。</p><p>webpack5 将不会自动为 Node.js 模块添加 polyfill，而是更专注的投入到前端模块的兼容中。因此需要开发者手动添加合适的 polyfill。</p><h3 id="13、Module-Federation"><a href="#13、Module-Federation" class="headerlink" title="13、Module Federation"></a>13、Module Federation</h3><p>Module Federation 使得使 JavaScript 应用得以从另一个 JavaScript 应用中动态地加载代码 —— 同时共享依赖。相当于 webpack 提供了线上 runtime 的环境，多个应用利用 CDN 共享组件或应用，不需要本地安装 npm 包再构建了。</p><p>具体配置可以查看官方文档 <a href="https://webpack.js.org/concepts/module-federation/">https://webpack.js.org/concepts/module-federation/</a></p><h2 id="我的-webpack5-项目配置参考"><a href="#我的-webpack5-项目配置参考" class="headerlink" title="我的 webpack5 项目配置参考"></a>我的 webpack5 项目配置参考</h2><blockquote><p><a href="https://github.com/hzzlyxx/webpack5">https://github.com/hzzlyxx/webpack5</a></p></blockquote><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>webpack5 正式发布已经有段时间了，总的来说：</p><ul><li>在加构建性能大幅度提升，依赖核心代码层面的持久缓存 cache，可大大加快二次构建速度</li><li>在打包体积上能有效减少，依赖更优的 Tree Shaking</li><li>默认支持浏览器长期缓存，降低配置门槛</li><li>令人激动的新特性 Module Federation，蕴含极大的可能性</li></ul><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://webpack.js.org/">https://webpack.js.org/</a></li><li><a href="https://webpack.js.org/migrate/5/">https://webpack.js.org/migrate/5/</a></li><li><a href="https://github.com/webpack/webpack-dev-server/blob/master/migration-v4.md">https://github.com/webpack/webpack-dev-server/blob/master/migration-v4.md</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;前言：2020 年 10 月 10 日，webpack5 正式发布，并带来了诸多重大的变更，使前端的构建效率与质量大为提升。最近也是趁着空闲时间把项目里的 webapck4 进行了升级，接下来就介绍一下我的踩坑升级之旅吧。&lt;/p&gt;
&lt;/blockq</summary>
      
    
    
    
    
    <category term="webpack" scheme="http://yoursite.com/tags/webpack/"/>
    
  </entry>
  
  <entry>
    <title>组件库文档v1.0</title>
    <link href="http://yoursite.com/2021/08/30/%E7%BB%84%E4%BB%B6%E5%BA%93%E6%96%87%E6%A1%A3v1.0/"/>
    <id>http://yoursite.com/2021/08/30/%E7%BB%84%E4%BB%B6%E5%BA%93%E6%96%87%E6%A1%A3v1.0/</id>
    <published>2021-08-30T06:52:13.000Z</published>
    <updated>2021-08-30T06:56:23.072Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>上篇我们具体介绍了一些组件库的搭建，这篇我们将介绍一下组件库文档 1.0 的构建生成过程。</p></blockquote><h2 id="需求"><a href="#需求" class="headerlink" title="需求"></a>需求</h2><p>首先来看一下组件库文档最基本的一些需求：</p><ul><li>提供组件的介绍说明</li><li>提供组件的属性列表 Props</li><li>提供组件调用的示例源码 Usage</li><li>提供组件调用的演示 demo</li></ul><h2 id="方案"><a href="#方案" class="headerlink" title="方案"></a>方案</h2><p>我们前期规范好了 markdown 说明文件以及 demo 文件，我们就可以直接通过 node 的读写文件再配合 react 菜单路由的方式进行生成：</p><ul><li>node 读写文件</li><li>react 路由</li></ul><h2 id="规范"><a href="#规范" class="headerlink" title="规范"></a>规范</h2><p>在上篇我们讲到对于一个组件，需要在开发组件时编写好组件的说明文档 markdown 文件以及 demo，文档规范如下：</p><figure class="highlight md"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">## Input</span></span><br><span class="line"></span><br><span class="line"><span class="section">#### 输入框组件。</span></span><br><span class="line"></span><br><span class="line"><span class="section">## Input Props：</span></span><br><span class="line"></span><br><span class="line">| 参数       | 说明                     | 类型                                         | 默认值  |</span><br><span class="line">| ---------- | ------------------------ | -------------------------------------------- | ------- |</span><br><span class="line">| disabled   | 是否禁用                 | boolean                                      | -       |</span><br><span class="line">| icon       | input 图标               | React.ReactElement                           | -       |</span><br><span class="line">| iconPlace  | input 图标渲染位置       | &#x27;left&#x27; \| &#x27;right&#x27;                            | &#x27;right&#x27; |</span><br><span class="line">| allowClear | 是否可清除，渲染清除图标 | boolean                                      | false   |</span><br><span class="line">| showNumber | 是否渲染字数计算         | boolean                                      | false   |</span><br><span class="line">| maxLength  | 最大输入长度             | number                                       | 70      |</span><br><span class="line">| onChange   | input 输入框改变的回调   | (e: ChangeEvent\<span class="language-xml">&lt;HTMLInputElement\&gt;</span>) =&gt; void | -       |</span><br><span class="line"></span><br><span class="line"><span class="quote">&gt; Tip：Input 的 props 不止上面列举的项，可传入原生 input 的所有属性</span></span><br><span class="line"></span><br><span class="line"><span class="section">### Usage</span></span><br><span class="line"></span><br><span class="line"><span class="code">```js</span></span><br><span class="line"><span class="code">import &#123; Input &#125; from &quot;@hzzly/components&quot;;</span></span><br><span class="line"><span class="code"></span></span><br><span class="line"><span class="code">const onChange = (e) =&gt; &#123;</span></span><br><span class="line"><span class="code">  console.log(&quot;Value: &quot;, e.target.value);</span></span><br><span class="line"><span class="code">&#125;;</span></span><br><span class="line"><span class="code"></span></span><br><span class="line"><span class="code">ReactDOM.render(&lt;Input icon=&#123;&lt;Icon /&gt;&#125; onChange=&#123;onChange&#125; /&gt;, mountNode);</span></span><br><span class="line"><span class="code">```</span></span><br></pre></td></tr></table></figure><p>这也是我们目前定义的组件 markdown 规范，这样书写一个 markdown 文件就能满足前三点的需求，最后一点我们单独定义了一个 demo 文件来进行组件的演示和调试。</p><h2 id="技术实现"><a href="#技术实现" class="headerlink" title="技术实现"></a>技术实现</h2><h3 id="node-读写-markdown-和-demo"><a href="#node-读写-markdown-和-demo" class="headerlink" title="node 读写 markdown 和 demo"></a>node 读写 markdown 和 demo</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 读取组件库</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">readComponents</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> markdowns = &#123;&#125;;</span><br><span class="line">  <span class="keyword">const</span> demos = &#123;&#125;;</span><br><span class="line">  <span class="keyword">const</span> components = [];</span><br><span class="line">  <span class="keyword">const</span> dir = path.<span class="title function_">join</span>(__dirname, <span class="string">&quot;../src&quot;</span>);</span><br><span class="line">  <span class="keyword">const</span> dirs = fs</span><br><span class="line">    .<span class="title function_">readdirSync</span>(dir)</span><br><span class="line">    .<span class="title function_">filter</span>(</span><br><span class="line">      <span class="function">(<span class="params">f</span>) =&gt;</span> f.<span class="title function_">indexOf</span>(<span class="string">&quot;.&quot;</span>) &lt; <span class="number">0</span> &amp;&amp; f.<span class="title function_">toLowerCase</span>().<span class="title function_">indexOf</span>(<span class="string">&quot;components&quot;</span>) &gt; -<span class="number">1</span></span><br><span class="line">    );</span><br><span class="line">  dirs.<span class="title function_">forEach</span>(<span class="function">(<span class="params">d</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> absolutePath = path.<span class="title function_">join</span>(dir, d);</span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="title function_">isDir</span>(absolutePath)) &#123;</span><br><span class="line">      fs.<span class="title function_">readdirSync</span>(absolutePath).<span class="title function_">forEach</span>(<span class="function">(<span class="params">f</span>) =&gt;</span> &#123;</span><br><span class="line">        components.<span class="title function_">push</span>(f);</span><br><span class="line">        <span class="keyword">const</span> mdPath = path.<span class="title function_">join</span>(dir, <span class="string">`./<span class="subst">$&#123;d&#125;</span>/<span class="subst">$&#123;f&#125;</span>/index.md`</span>);</span><br><span class="line">        <span class="keyword">const</span> demoPath = path.<span class="title function_">join</span>(dir, <span class="string">`./<span class="subst">$&#123;d&#125;</span>/<span class="subst">$&#123;f&#125;</span>/demo.tsx`</span>);</span><br><span class="line">        <span class="keyword">if</span> (fs.<span class="title function_">existsSync</span>(mdPath)) &#123;</span><br><span class="line">          markdowns[f] = fs.<span class="title function_">readFileSync</span>(mdPath).<span class="title function_">toString</span>();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (fs.<span class="title function_">existsSync</span>(demoPath)) &#123;</span><br><span class="line">          demos[f] = <span class="string">`../src/<span class="subst">$&#123;d&#125;</span>/<span class="subst">$&#123;f&#125;</span>/demo`</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">return</span> &#123;</span><br><span class="line">    components,</span><br><span class="line">    markdowns,</span><br><span class="line">    demos,</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 写入markdown字符串</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">writeMd</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> &#123; markdowns &#125; = <span class="title function_">readComponents</span>();</span><br><span class="line">  markdowns = &#123;</span><br><span class="line">    ...markdowns,</span><br><span class="line">    <span class="title class_">Welcome</span>: fs</span><br><span class="line">      .<span class="title function_">readFileSync</span>(path.<span class="title function_">join</span>(__dirname, <span class="string">`../src/welcome.md`</span>))</span><br><span class="line">      .<span class="title function_">toString</span>(),</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="keyword">const</span> str = <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(markdowns, <span class="string">&quot;&quot;</span>, <span class="string">&quot;\t&quot;</span>);</span><br><span class="line">  fs.<span class="title function_">writeFile</span>(</span><br><span class="line">    path.<span class="title function_">join</span>(__dirname, <span class="string">&quot;../src/md.ts&quot;</span>),</span><br><span class="line">    <span class="string">`export default <span class="subst">$&#123;str&#125;</span>`</span>,</span><br><span class="line">    <span class="function">(<span class="params">err</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (err) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">error</span>(err);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;写入配置成功!&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="自动生成组件及路由"><a href="#自动生成组件及路由" class="headerlink" title="自动生成组件及路由"></a>自动生成组件及路由</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 创建组件</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">createComponents</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; components, demos &#125; = <span class="title function_">readComponents</span>();</span><br><span class="line">  components.<span class="title function_">forEach</span>(<span class="function">(<span class="params">component</span>) =&gt;</span> <span class="variable language_">this</span>.<span class="title function_">createComponent</span>(component, demos));</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_">createComponent</span>(<span class="params">name, demos</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> to = path.<span class="title function_">resolve</span>(__dirname, <span class="string">`../src/pages/<span class="subst">$&#123;name&#125;</span>.tsx`</span>);</span><br><span class="line">  fs.<span class="title function_">ensureFileSync</span>(to); <span class="comment">// 确保文件存在(文件目录结构没有会新建)</span></span><br><span class="line">  <span class="keyword">const</span> template = <span class="string">`import React from &#x27;react&#x27;;</span></span><br><span class="line"><span class="string">import &#123; markdown &#125; from &#x27;@/utils&#x27;;</span></span><br><span class="line"><span class="string"><span class="subst">$&#123;demo[name] ? <span class="string">`import Demo from &#x27;<span class="subst">$&#123;demo[name]&#125;</span>&#x27;;`</span> : <span class="string">&quot;&quot;</span>&#125;</span></span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">interface Props &#123;&#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">const index: React.FC&lt;Props&gt; = () =&gt; &#123;</span></span><br><span class="line"><span class="string">  return (</span></span><br><span class="line"><span class="string">    &lt;&gt;</span></span><br><span class="line"><span class="string">      &lt;div</span></span><br><span class="line"><span class="string">        className=&quot;markdown&quot;</span></span><br><span class="line"><span class="string">        // eslint-disable-next-line react/no-danger</span></span><br><span class="line"><span class="string">        dangerouslySetInnerHTML=&#123;&#123; __html: markdown(&#x27;<span class="subst">$&#123;name&#125;</span>&#x27;) &#125;&#125;</span></span><br><span class="line"><span class="string">      /&gt;</span></span><br><span class="line"><span class="string">      &lt;div className=&quot;demo&quot;&gt;</span></span><br><span class="line"><span class="string">        &lt;h2 className=&quot;demo-title&quot;&gt;演示&lt;/h2&gt;</span></span><br><span class="line"><span class="string">        <span class="subst">$&#123;demo[name] ? <span class="string">&quot;&lt;Demo /&gt;&quot;</span> : <span class="string">&quot;&lt;p&gt;演示代码&lt;/p&gt;&quot;</span>&#125;</span></span></span><br><span class="line"><span class="string">      &lt;/div&gt;</span></span><br><span class="line"><span class="string">    &lt;/&gt;</span></span><br><span class="line"><span class="string">  )</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">export default index;</span></span><br><span class="line"><span class="string">`</span>;</span><br><span class="line">  fs.<span class="title function_">outputFileSync</span>(to, template);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 写入路由</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">writeRouter</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; components &#125; = <span class="title function_">readComponents</span>();</span><br><span class="line">  <span class="keyword">const</span> template = <span class="string">`</span></span><br><span class="line"><span class="string">import React from &#x27;react&#x27;;</span></span><br><span class="line"><span class="string">import &#123; RouteConfig &#125; from &quot;react-router-config&quot;;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">import Home from &#x27;@/pages/Home&#x27;;</span></span><br><span class="line"><span class="string">import Welcome from &#x27;@/pages/Welcome&#x27;;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">const MarkDown = React.lazy(() =&gt; import(/* webpackChunkName: &quot;IMarkDown&quot; */ &#x27;@/pages/MarkDown&#x27;));</span></span><br><span class="line"><span class="string"><span class="subst">$&#123;components</span></span></span><br><span class="line"><span class="subst"><span class="string">  .map(</span></span></span><br><span class="line"><span class="subst"><span class="string">    (component) =&gt;</span></span></span><br><span class="line"><span class="subst"><span class="string">      <span class="string">`const <span class="subst">$&#123;component&#125;</span> = React.lazy(() =&gt; import(/* webpackChunkName: &quot;I<span class="subst">$&#123;component&#125;</span>&quot; */ &#x27;@/pages/<span class="subst">$&#123;component&#125;</span>&#x27;));`</span></span></span></span><br><span class="line"><span class="subst"><span class="string">  )</span></span></span><br><span class="line"><span class="subst"><span class="string">  .join(<span class="string">&quot;\n&quot;</span>)&#125;</span></span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">const routes: RouteConfig[] = [</span></span><br><span class="line"><span class="string">  &#123;</span></span><br><span class="line"><span class="string">    path: &#x27;/&#x27;,</span></span><br><span class="line"><span class="string">    component: Home,</span></span><br><span class="line"><span class="string">    children: [</span></span><br><span class="line"><span class="string">      &#123; path: &quot;/welcome&quot;, component: Welcome, name: &#x27;Welcome&#x27; &#125;,</span></span><br><span class="line"><span class="string">      &#123; path: &quot;/markdown&quot;, component: MarkDown, name: &#x27;MarkDown测试&#x27; &#125;,</span></span><br><span class="line"><span class="string">      <span class="subst">$&#123;components</span></span></span><br><span class="line"><span class="subst"><span class="string">        .map((component) =&gt; &#123;</span></span></span><br><span class="line"><span class="subst"><span class="string">          <span class="keyword">if</span> (component.toLowerCase().indexOf(<span class="string">&quot;js&quot;</span>) &gt; -<span class="number">1</span>) &#123;</span></span></span><br><span class="line"><span class="subst"><span class="string">            <span class="keyword">return</span> <span class="string">`&#123; path: &quot;/<span class="subst">$&#123;component.toLowerCase()&#125;</span>&quot;, component: <span class="subst">$&#123;component&#125;</span>, name: &#x27;<span class="subst">$&#123;component&#125;</span>&#x27; &#125;,`</span>;</span></span></span><br><span class="line"><span class="subst"><span class="string">          &#125; <span class="keyword">else</span> &#123;</span></span></span><br><span class="line"><span class="subst"><span class="string">            <span class="keyword">return</span> <span class="string">`&#123; path: &quot;/<span class="subst">$&#123;component</span></span></span></span></span><br><span class="line"><span class="subst"><span class="string"><span class="subst"><span class="string">              .replace(/\B([A-Z])/g, <span class="string">&quot;-$1&quot;</span>)</span></span></span></span></span><br><span class="line"><span class="subst"><span class="string"><span class="subst"><span class="string">              .toLowerCase()&#125;</span>&quot;, component: <span class="subst">$&#123;component&#125;</span>, name: &#x27;<span class="subst">$&#123;component&#125;</span>&#x27; &#125;,`</span>;</span></span></span><br><span class="line"><span class="subst"><span class="string">          &#125;</span></span></span><br><span class="line"><span class="subst"><span class="string">        &#125;)</span></span></span><br><span class="line"><span class="subst"><span class="string">        .join(<span class="string">&quot;\n      &quot;</span>)&#125;</span></span></span><br><span class="line"><span class="string">    ],</span></span><br><span class="line"><span class="string">  &#125;,</span></span><br><span class="line"><span class="string">];</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">export default routes;</span></span><br><span class="line"><span class="string">`</span>;</span><br><span class="line">  fs.<span class="title function_">outputFileSync</span>(path.<span class="title function_">join</span>(__dirname, <span class="string">`../src/router/index.ts`</span>), template);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="markdown-解析"><a href="#markdown-解析" class="headerlink" title="markdown 解析"></a>markdown 解析</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// utils.ts</span></span><br><span class="line"><span class="keyword">import</span> marked <span class="keyword">from</span> <span class="string">&quot;marked&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> hljs <span class="keyword">from</span> <span class="string">&quot;highlight.js/lib/core&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> javascript <span class="keyword">from</span> <span class="string">&quot;highlight.js/lib/languages/javascript&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> md <span class="keyword">from</span> <span class="string">&quot;@/md&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">markdown</span>(<span class="params">component: string, isComponent = <span class="literal">true</span></span>): string &#123;</span><br><span class="line">  marked.<span class="title function_">setOptions</span>(&#123;</span><br><span class="line">    <span class="attr">renderer</span>: <span class="keyword">new</span> marked.<span class="title class_">Renderer</span>(),</span><br><span class="line">    <span class="attr">highlight</span>: <span class="function">(<span class="params">code: string</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> hljs.<span class="title function_">highlightAuto</span>(code).<span class="property">value</span>;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">pedantic</span>: <span class="literal">false</span>,</span><br><span class="line">    <span class="attr">gfm</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">tables</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">breaks</span>: <span class="literal">false</span>,</span><br><span class="line">    <span class="attr">sanitize</span>: <span class="literal">false</span>,</span><br><span class="line">    <span class="attr">smartLists</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">smartypants</span>: <span class="literal">false</span>,</span><br><span class="line">    <span class="attr">xhtml</span>: <span class="literal">false</span>,</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="keyword">if</span> (md[component]) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">marked</span>(md[component]);</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (isComponent) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">marked</span>(<span class="string">`## <span class="subst">$&#123;component&#125;</span>`</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">marked</span>(component);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>在日常开发的过程中，我们除了组件的代码编写外，还有很多流程上、边角上的工作需要做，这些事情往往都比较琐碎又必须要做。我们多借助工具去解决我们的工作中那些零星简单的任务。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;上篇我们具体介绍了一些组件库的搭建，这篇我们将介绍一下组件库文档 1.0 的构建生成过程。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;需求&quot;&gt;&lt;a href=&quot;#需求&quot; class=&quot;headerlink&quot; title=&quot;需求&quot;&gt;&lt;/a&gt;</summary>
      
    
    
    
    
    <category term="随笔" scheme="http://yoursite.com/tags/%E9%9A%8F%E7%AC%94/"/>
    
    <category term="组件库" scheme="http://yoursite.com/tags/%E7%BB%84%E4%BB%B6%E5%BA%93/"/>
    
  </entry>
  
  <entry>
    <title>组件库搭建实践</title>
    <link href="http://yoursite.com/2021/08/08/%E7%BB%84%E4%BB%B6%E5%BA%93%E6%90%AD%E5%BB%BA%E5%AE%9E%E8%B7%B5/"/>
    <id>http://yoursite.com/2021/08/08/%E7%BB%84%E4%BB%B6%E5%BA%93%E6%90%AD%E5%BB%BA%E5%AE%9E%E8%B7%B5/</id>
    <published>2021-08-08T15:04:16.000Z</published>
    <updated>2021-08-08T15:10:05.261Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>前言：从去年年底开始规划项目的重构，也是这样一个机遇，我开始负责项目的一个基础架构以及一些公共基础服务，主要搭建了组件库以及工具类库来提升后续开发以及重构的效率，期间也是踩了不少坑，接下来就作为整个搭建过程的一个总结，本篇主要是组件库的搭建与实践。</p></blockquote><p>一个项目或系统有着大量的业务场景和业务代码，相似的页面和代码层出不穷，那如何管理和抽象这些相似的代码和模块，这肯定是许多团队都会遇到的问题。不断的拷代码？还是抽象成 UI 组件或业务组件？显然后者更高效。</p><h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><p>之前的开发流程从产品设计到研发的过程中，最常出现在需求沟通与研发过程中由于缺少统一的规范和标准化体系来指导实践，导致实施环节各方沟通成本高。</p><ul><li><strong>认知：</strong>产品、研发、设计师对于同一需求都有自己理解的解决方案，缺少统一规范的约束，难以达成共识。</li><li><strong>效率：</strong>设计效率低，交互原型的维护成本及上下游团队的沟通成本高，易造成不专业的印象。</li><li><strong>品质：</strong>认知和效率的局限性，最终导致实施落地的产品质量和用户体验难以得到保障。</li></ul><h2 id="价值"><a href="#价值" class="headerlink" title="价值"></a>价值</h2><p><strong>组件库最大的价值在于提升整个团队的产研效率，使设计质量得以保障的同时提升产品整体的用户体验。</strong></p><ul><li><strong>保证产品体验的一致性：</strong>对于一个含有多业务系统的大型复杂产品，每个独立的业务系统虽然在功能上有一定区别，但整体的用户体验需要满足基本的一致性。</li><li><strong>提升设计师的效率：</strong>在需求量巨大且需求来自不同的业务线时，需要逐一绘制页面及组件，造成大量重复劳动，并且在评审及需求沟通环节还可能存在不断地细节调优，所以对于设计师而言，组件的高频复用能大大提升设计效率，使设计师更多的将精力聚焦于理解和解决用户的实际问题。</li><li><strong>提升产研团队的效率：</strong>通用场景及普通需求直接按规范进行设计和研发，减少上下游对同一页面及组件使用方式的不同理解而产生的多余沟通成本。</li><li><strong>利于技术的沉淀：</strong>从一个组件库可以扩展到其它的技术方案，比如懒加载、Tree Shaking、文档预览等等。</li></ul><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><p><strong>搭建统一的组件库</strong></p><h2 id="实践"><a href="#实践" class="headerlink" title="实践"></a>实践</h2><p>接下来将详细介绍搭建一个前端组件库需要涉及的流程和相关知识、工具，其中也是参考了一些主流开源组件库的做法。</p><h2 id="基础架构"><a href="#基础架构" class="headerlink" title="基础架构"></a>基础架构</h2><h3 id="组件库目录结构"><a href="#组件库目录结构" class="headerlink" title="组件库目录结构"></a>组件库目录结构</h3><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><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">├── ...</span><br><span class="line">├── package.json</span><br><span class="line">├── README.md // 文档说明</span><br><span class="line">├── tsconfig.json // ts 配置文件</span><br><span class="line">├── tsconfig.build.json // ts 编译配置</span><br><span class="line">├── build // 配置文件</span><br><span class="line">├── esm // es modules目标文件</span><br><span class="line">├── lib // umd目标文件</span><br><span class="line">├── docs // 文档</span><br><span class="line">└── src</span><br><span class="line">    ├── JsComponents // 原生js组件</span><br><span class="line">    ├── components // React 组件</span><br><span class="line">    ├── images</span><br><span class="line">    ├── style // 公共样式文件</span><br><span class="line">    └── index.ts // 入口文件</span><br></pre></td></tr></table></figure><h3 id="组件目录结构"><a href="#组件目录结构" class="headerlink" title="组件目录结构"></a>组件目录结构</h3><p>组件的目录结构参考了 antd 的规范</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Input</span><br><span class="line">├── Input.tsx // 组件</span><br><span class="line">├── demo.tsx // 示例演示demo</span><br><span class="line">├── index.md // 组件文档说明</span><br><span class="line">├── index.tsx // 组件入口文件</span><br><span class="line">└── style // 样式文件</span><br><span class="line">    ├── index.scss</span><br><span class="line">    └── index.ts</span><br></pre></td></tr></table></figure><h3 id="编译后的组件目录结构"><a href="#编译后的组件目录结构" class="headerlink" title="编译后的组件目录结构"></a>编译后的组件目录结构</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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">├── Input.d.ts</span><br><span class="line">├── Input.js</span><br><span class="line">├── index.d.ts</span><br><span class="line">├── index.js</span><br><span class="line">└── style</span><br><span class="line">    ├── index.css</span><br><span class="line">    ├── index.d.ts</span><br><span class="line">    ├── index.js</span><br><span class="line">    └── index.scss</span><br></pre></td></tr></table></figure><h3 id="规范"><a href="#规范" class="headerlink" title="规范"></a>规范</h3><p>无规矩不成方圆，在组件库开始之初就定义好规范，保证代码的严谨性，配置了以下规则：</p><ul><li>eslint&#x2F;@typescript-eslint</li><li>stylelint</li><li>git hooks</li><li>git commit</li><li>prettier</li></ul><h2 id="组件"><a href="#组件" class="headerlink" title="组件"></a>组件</h2><p>基础架构定义好之后就可以愉快的进行组件开发了，组件的基本开发流程：</p><ul><li>组件初始化</li><li>代码 coding</li><li>组件 demo</li><li>组件文档说明</li><li>组件库入口文件导出组件</li></ul><p>对于组件初始化，我们也是通过脚本自动化实现，减少这些繁琐重复的工作。</p><h3 id="样式"><a href="#样式" class="headerlink" title="样式"></a>样式</h3><p>对于组件的样式，一开始我们有两套方案：</p><ul><li>常规样式（css&#x2F;scss）</li><li>CSS-in-JS（<a href="https://www.styled-components.com/">styled-components</a>）</li></ul><p>我个人的话更喜欢 CSS-in-JS 的方案，不用去考虑样式的打包以及引用方式，但最终考虑到业务场景还需要输出原生的 css 代码提供旧项目引用，所以最后也是采用了常规样式，通过 gulp 打包输出 css 以及 scss 文件，后面打包的时候也会具体介绍一下。</p><h2 id="文档"><a href="#文档" class="headerlink" title="文档"></a>文档</h2><p>这里介绍一下目前的文档说明以及文档生成方案，首先对于每一个组件，需要在开发组件时编写好组件的说明文档（规则格式看下面组件文档）以及 demo，当我们运行组件库文档预览项目时会通过 node 脚本读取组件的说明文档以及 demo 自动生成对应的路由文件，这样我们就能实时预览文档以及 demo 演示。</p><h3 id="组件文档"><a href="#组件文档" class="headerlink" title="组件文档"></a>组件文档</h3><figure class="highlight md"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">## Input</span></span><br><span class="line"></span><br><span class="line"><span class="section">#### 输入框组件。</span></span><br><span class="line"></span><br><span class="line"><span class="section">## Input Props：</span></span><br><span class="line"></span><br><span class="line">| 参数       | 说明                     | 类型                                         | 默认值  |</span><br><span class="line">| ---------- | ------------------------ | -------------------------------------------- | ------- |</span><br><span class="line">| disabled   | 是否禁用                 | boolean                                      | -       |</span><br><span class="line">| icon       | input 图标               | React.ReactElement                           | -       |</span><br><span class="line">| iconPlace  | input 图标渲染位置       | &#x27;left&#x27; \| &#x27;right&#x27;                            | &#x27;right&#x27; |</span><br><span class="line">| allowClear | 是否可清除，渲染清除图标 | boolean                                      | false   |</span><br><span class="line">| showNumber | 是否渲染字数计算         | boolean                                      | false   |</span><br><span class="line">| maxLength  | 最大输入长度             | number                                       | 70      |</span><br><span class="line">| onChange   | input 输入框改变的回调   | (e: ChangeEvent\<span class="language-xml">&lt;HTMLInputElement\&gt;</span>) =&gt; void | -       |</span><br><span class="line"></span><br><span class="line"><span class="quote">&gt; Tip：Input 的 props 不止上面列举的项，可传入原生 input 的所有属性</span></span><br><span class="line"></span><br><span class="line"><span class="section">### Usage</span></span><br><span class="line"></span><br><span class="line"><span class="code">```js</span></span><br><span class="line"><span class="code">import &#123; Input &#125; from &quot;@hzzly/components&quot;;</span></span><br><span class="line"><span class="code"></span></span><br><span class="line"><span class="code">const onChange = (e) =&gt; &#123;</span></span><br><span class="line"><span class="code">  console.log(&quot;Value: &quot;, e.target.value);</span></span><br><span class="line"><span class="code">&#125;;</span></span><br><span class="line"><span class="code"></span></span><br><span class="line"><span class="code">ReactDOM.render(&lt;Input icon=&#123;&lt;Icon /&gt;&#125; onChange=&#123;onChange&#125; /&gt;, mountNode);</span></span><br><span class="line"><span class="code">```</span></span><br></pre></td></tr></table></figure><h3 id="组件库文档"><a href="#组件库文档" class="headerlink" title="组件库文档"></a>组件库文档</h3><p>组件库的文档一般都是对外可访问的，因此需要部署到服务器上，同时也需具备本地预览的功能。</p><p>可以自己搭一个文档站点，也可以使用目前主流的文档生成器（Docz、Storybook、VuePress）来生成文档站点。</p><p>这里我们采用的是自己搭建的一个单独的 React 项目，也就是上面的 <code>docs</code> 文件夹，其实自己搭也比较简单，首先是思路：</p><ul><li><code>readFile</code> 读取 <code>src</code> 下组件的 md 文件和 demo 文件保存起来</li><li><code>writeFile</code> 写入路由配置以及对应的路由文件</li><li>通过不同的路由渲染对应的组件文档和演示</li></ul><p>主要借助的是 node 读写文件的便捷性，对于文档自动生成方案在后面文章会具体介绍一下，先明确一下思路就行。</p><h2 id="打包"><a href="#打包" class="headerlink" title="打包"></a>打包</h2><p>对于打包后的文件，统一放在 <code>ems</code> 目录下，顾名思义我们需要打包成 <code>ESModule</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// packages.json</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  ...</span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;clean&quot;</span><span class="punctuation">:</span> <span class="string">&quot;rimraf dist&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;build&quot;</span><span class="punctuation">:</span> <span class="string">&quot;npm run clean &amp;&amp; tsc -p tsconfig.build.json &amp;&amp; gulp -f ./build/gulpfile.js&quot;</span><span class="punctuation">,</span> <span class="comment">// 组件库打包</span></span><br><span class="line">    <span class="attr">&quot;start&quot;</span><span class="punctuation">:</span> <span class="string">&quot;cross-env webpack-dev-server --config ./build/webpack.dev.js&quot;</span><span class="punctuation">,</span> <span class="comment">// 组件开发调试环境</span></span><br><span class="line">    <span class="attr">&quot;build:umd&quot;</span><span class="punctuation">:</span> <span class="string">&quot;cross-env NODE_ENV=&#x27;production&#x27; webpack --config ./build/webpack.umd.js&quot;</span><span class="punctuation">,</span> <span class="comment">// umd打包</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  ...</span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="编译打包-ts-x"><a href="#编译打包-ts-x" class="headerlink" title="编译打包 ts[x]"></a>编译打包 ts[x]</h3><p>在入口文件我们需要以 <code>ESModule</code> 的规范导出组件：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> &#123; <span class="keyword">default</span> <span class="keyword">as</span> <span class="title class_">Input</span> &#125; <span class="keyword">from</span> <span class="string">&quot;./components/Input&quot;</span>;</span><br></pre></td></tr></table></figure><p>打包工具的话我们就不能使用 <code>webpack</code> 来打包我们的组件库，可以使用 <code>rollup</code> 或 <code>TS </code>来编译组件，这里我们采用的是 <code>TS</code> 编译的方案，配置 <code>tsconfig.build.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></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;outDir&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ems&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;target&quot;</span><span class="punctuation">:</span> <span class="string">&quot;es6&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;module&quot;</span><span class="punctuation">:</span> <span class="string">&quot;esnext&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;moduleResolution&quot;</span><span class="punctuation">:</span> <span class="string">&quot;node&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;declaration&quot;</span><span class="punctuation">:</span> <span class="keyword">true</span><span class="punctuation">,</span> <span class="comment">// 生成声明文件 .d.ts</span></span><br><span class="line">    <span class="attr">&quot;skipLibCheck&quot;</span><span class="punctuation">:</span> <span class="keyword">true</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;allowSyntheticDefaultImports&quot;</span><span class="punctuation">:</span> <span class="keyword">true</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;experimentalDecorators&quot;</span><span class="punctuation">:</span> <span class="keyword">true</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;baseUrl&quot;</span><span class="punctuation">:</span> <span class="string">&quot;.&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;paths&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;@/*&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;./src/*&quot;</span><span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;jsx&quot;</span><span class="punctuation">:</span> <span class="string">&quot;react&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;include&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;src&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;exclude&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="comment">// 排除编译文件</span></span><br><span class="line">    <span class="string">&quot;node_modules&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;src/**/demo.tsx&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;**/*.md&quot;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="编译打包样式文件"><a href="#编译打包样式文件" class="headerlink" title="编译打包样式文件"></a>编译打包样式文件</h3><p>上面我们已经成功编译了 <code>ts</code> 或 <code>tsx</code> 文件，但是对于我们的样式文件还没处理（TS 无法编译样式文件），样式文件根据我们上面的代码规范，需要打包编译到对应的组件文件夹下，这样就可以跟着组件路径来引入：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">&quot;@hzzly/components/esm/input/style&quot;</span>;</span><br></pre></td></tr></table></figure><p>这里我们采用 <code>gulp</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 生成css到对应组件</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">scss2css</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> gulp</span><br><span class="line">    .<span class="title function_">src</span>(paths.<span class="property">styles</span>)</span><br><span class="line">    .<span class="title function_">pipe</span>(<span class="title function_">base64</span>(&#123; <span class="attr">maxImageSize</span>: <span class="number">2000</span> &#125;))</span><br><span class="line">    .<span class="title function_">pipe</span>(<span class="title function_">sass</span>()) <span class="comment">// 处理sass文件</span></span><br><span class="line">    .<span class="title function_">pipe</span>(<span class="title function_">autoprefixer</span>()) <span class="comment">// 根据browserslistrc增加前缀</span></span><br><span class="line">    .<span class="title function_">pipe</span>(<span class="title function_">cssmin</span>(&#123; <span class="attr">compatibility</span>: <span class="string">&quot;ie9&quot;</span> &#125;)) <span class="comment">// 压缩</span></span><br><span class="line">    .<span class="title function_">pipe</span>(gulp.<span class="title function_">dest</span>(paths.<span class="property">dest</span>.<span class="property">esm</span>));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 拷贝scss到对应的组件</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">copyScss</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> gulp</span><br><span class="line">    .<span class="title function_">src</span>(paths.<span class="property">styles</span>)</span><br><span class="line">    .<span class="title function_">pipe</span>(<span class="title function_">base64</span>(&#123; <span class="attr">maxWeightResource</span>: <span class="number">10000</span> &#125;))</span><br><span class="line">    .<span class="title function_">pipe</span>(gulp.<span class="title function_">dest</span>(paths.<span class="property">dest</span>.<span class="property">esm</span>));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="UMD"><a href="#UMD" class="headerlink" title="UMD"></a>UMD</h3><p>这里还有一个业务场景是 <code>JsComponents</code> 需要打包成 <code>umd</code> 的格式提供旧框架通过链接的方式直接引用，所以我们还配置了 <code>umd</code> 的打包规范，分别单独打包 js 原生组件, 将组件单独打包需要在 Webpack 中配置多个<code>entry</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> entry = &#123;&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> names = fs</span><br><span class="line">  .<span class="title function_">readdirSync</span>(path.<span class="title function_">resolve</span>(__dirname, <span class="string">`../src/JsComponents`</span>))</span><br><span class="line">  .<span class="title function_">filter</span>(<span class="function">(<span class="params">f</span>) =&gt;</span> f.<span class="title function_">indexOf</span>(<span class="string">&#x27;.&#x27;</span>) &lt; <span class="number">0</span>);</span><br><span class="line">names.<span class="title function_">forEach</span>(<span class="function">(<span class="params">name</span>) =&gt;</span> &#123;</span><br><span class="line">  entry[name.<span class="title function_">toLocaleLowerCase</span>()] = path.<span class="title function_">resolve</span>(__dirname, <span class="string">`../src/JsComponents/<span class="subst">$&#123;name&#125;</span>/index.ts`</span>);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  ...</span><br><span class="line">  entry,</span><br><span class="line">  <span class="attr">output</span>: &#123;</span><br><span class="line">    <span class="attr">path</span>: path.<span class="title function_">resolve</span>(__dirname, <span class="string">&#x27;../lib&#x27;</span>),</span><br><span class="line">    <span class="attr">filename</span>: <span class="string">&#x27;[name].min.js&#x27;</span>,</span><br><span class="line">    <span class="attr">publicPath</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></pre></td></tr></table></figure><h2 id="迭代维护"><a href="#迭代维护" class="headerlink" title="迭代维护"></a>迭代维护</h2><h3 id="CHANGELOG-md"><a href="#CHANGELOG-md" class="headerlink" title="CHANGELOG.md"></a>CHANGELOG.md</h3><p>组件日常维护占整个组件库生命周期的很大一部分，组件库做起来了以后，组件功能后续会不断迭代，也许是 bug fix，也可能 feature，这些组件的迭代我们通过 PR 和 issue 来管理，同时，我们需要管理好组件的 changelog，为了规范我们也是将 changelog 维护到一个 Markdown 文件里，通过 <code>conventional-changelog</code> 工具自动根据 commit message 生成 CHANGELOG.md**。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>到这里，也算是对最近在组件库的探索做了个总结，从零开始构建了一个比较完整的组件库，这段经历也是让我在架构思维以及业务层面有了新的认识，也学到了不少的知识。当然，还有很多不完善的地方，也是在慢慢优化完善，在组件化这条路上，我们还有很多事情要做，加油！！！</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://link.zhihu.com/?target=https://github.com/ant-design/ant-design/">Ant Design</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;前言：从去年年底开始规划项目的重构，也是这样一个机遇，我开始负责项目的一个基础架构以及一些公共基础服务，主要搭建了组件库以及工具类库来提升后续开发以及重构的效率，期间也是踩了不少坑，接下来就作为整个搭建过程的一个总结，本篇主要是组件库的搭建与实践。</summary>
      
    
    
    
    
    <category term="随笔" scheme="http://yoursite.com/tags/%E9%9A%8F%E7%AC%94/"/>
    
    <category term="组件库" scheme="http://yoursite.com/tags/%E7%BB%84%E4%BB%B6%E5%BA%93/"/>
    
  </entry>
  
  <entry>
    <title>Input组件字数限制引发的问题</title>
    <link href="http://yoursite.com/2021/06/11/Input%E7%BB%84%E4%BB%B6%E5%AD%97%E6%95%B0%E9%99%90%E5%88%B6%E5%BC%95%E5%8F%91%E7%9A%84%E9%97%AE%E9%A2%98/"/>
    <id>http://yoursite.com/2021/06/11/Input%E7%BB%84%E4%BB%B6%E5%AD%97%E6%95%B0%E9%99%90%E5%88%B6%E5%BC%95%E5%8F%91%E7%9A%84%E9%97%AE%E9%A2%98/</id>
    <published>2021-06-11T09:43:42.000Z</published>
    <updated>2021-06-11T09:46:49.194Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>前言：最近在完善 Input 组件字数计算的时候引发了不少的问题：1、到限制字数时，继续输入中文会替换末尾的文字；2、不同浏览器截断的表现不一样等等问题。</p></blockquote><p>先明确一下需求</p><h2 id="需求"><a href="#需求" class="headerlink" title="需求"></a>需求</h2><ul><li>输入到最大长度时，超出截断</li><li>首尾非文字（空格、换行等）不计入字数统计中</li><li>超出截断时保持已输入的文字以及开头非文字部分</li></ul><h2 id="尝试"><a href="#尝试" class="headerlink" title="尝试"></a>尝试</h2><h3 id="总体思路"><a href="#总体思路" class="headerlink" title="总体思路"></a>总体思路</h3><p>1、不能直接使用 input 的 maxLength，因为计算数字的时候会把首尾非文字也计算其中</p><p>2、监听 input 的 onChange 事件，当输入的长度大于最大长度时截断</p><p>根据以上思路，既然不能直接使用 input 的 maxLength，那就在 onChange 事件里进行逻辑处理。</p><h3 id="第一次尝试"><a href="#第一次尝试" class="headerlink" title="第一次尝试"></a>第一次尝试</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> v = e.<span class="property">target</span>.<span class="property">value</span>.<span class="title function_">trim</span>();</span><br><span class="line"><span class="keyword">if</span> (v.<span class="property">length</span> &gt; maxLength &amp;&amp; !(<span class="string">&quot;value&quot;</span> <span class="keyword">in</span> props)) &#123;</span><br><span class="line">  <span class="title function_">setNum</span>(maxLength);</span><br><span class="line">  inputRef.<span class="property">current</span>.<span class="property">value</span> = v.<span class="title function_">slice</span>(<span class="number">0</span>, maxLength);</span><br><span class="line">  <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">setNum</span>(v.<span class="property">length</span>);</span><br></pre></td></tr></table></figure><p><strong>存在的问题</strong></p><ul><li>输入到最大长度时开头空格或换行会被截掉</li><li>输入中文时会从尾部开始替换掉输入框的值</li><li>尾部空格较多时，截取的时候会出问题，计算数字的时候会把空格计算其中</li></ul><h3 id="第二次尝试"><a href="#第二次尝试" class="headerlink" title="第二次尝试"></a>第二次尝试</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; value &#125; = e.<span class="property">target</span>;</span><br><span class="line"><span class="keyword">const</span> v = value.<span class="title function_">trim</span>();</span><br><span class="line"><span class="keyword">if</span> (value.<span class="property">length</span> - v.<span class="property">length</span> !== <span class="number">0</span>) &#123;</span><br><span class="line">  <span class="title function_">setLength</span>(maxLength + (value.<span class="property">length</span> - v.<span class="property">length</span>)); <span class="comment">// 动态计算maxLength保证首尾空格或换行还在</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> (v.<span class="property">length</span> &gt; maxLength) &#123;</span><br><span class="line">  <span class="keyword">const</span> newValue = v.<span class="title function_">slice</span>(<span class="number">0</span>, -(v.<span class="property">length</span> - maxLength)).<span class="title function_">trim</span>(); <span class="comment">// 防止倒数第一个前面的值为空格</span></span><br><span class="line">  inputRef.<span class="property">current</span>.<span class="property">value</span> = value.<span class="title function_">slice</span>(<span class="number">0</span>, -(v.<span class="property">length</span> - maxLength));</span><br><span class="line">  <span class="title function_">setNum</span>(newValue.<span class="property">length</span>);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">  <span class="title function_">setNum</span>(v.<span class="property">length</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过上面两个逻辑判断和处理已经解决了第一次尝试的两个问题，那还有一个问题是不是没解决呢？其实在其他浏览器能解决输入中文时替换掉输入框的值，但是 Chrome 浏览器却不行，WTF！Chrome 竟然不行。其实原因是我上面提到的 Chrome 和其他浏览器输入中文时的表现不一样。</p><p>我们用两张图来看一下就明白了</p><p><img src="https://hzzlyxx.oss-cn-beijing.aliyuncs.com/blog/input/1.png" alt="Chrome"></p><p><img src="https://hzzlyxx.oss-cn-beijing.aliyuncs.com/blog/input/2.png" alt="其他浏览器"></p><p>第一张图是 Chrome 输入中文的表现，第二张图是其他浏览器输入中文的表现。是的，Chrome 输入中文时会实时把拼音显示在输入框内，这样就会导致在截取的时候判断不准确替换掉了尾部的值，那有没有什么解决方案呢？那肯定是有的，接下来就来优化一下这个问题。</p><h2 id="优化"><a href="#优化" class="headerlink" title="优化"></a>优化</h2><h3 id="判断是否中文输入"><a href="#判断是否中文输入" class="headerlink" title="判断是否中文输入"></a>判断是否中文输入</h3><p>当用户使用拼音输入法输入时，我们会发现 <code>onChange/onInput </code>取得的值是拼音值，但是很明显，我们需要计算的是用户输入的中文值的长度，而不是拼音值的长度。所以这里需要解决使用拼音输入法时会取得拼音值的问题。</p><p>我们先来看两个比较陌生的事件：</p><ul><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Element/compositionstart_event">compositionstart</a>：文本合成系统如 input method editor（即输入法编辑器）开始新的输入合成时会触发 <code>compositionstart</code> 事件。例如，当用户使用拼音输入法开始输入汉字或者使用语音输入时，这个事件就会被触发。</li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Element/compositionend_event">compositionend</a>：当文本段落的组成完成或取消时，<code> compositionend</code> 事件将被触发 (具有特殊字符的触发，需要一系列键和其他输入，如语音识别或移动中的字词建议)。例如，当用户使用拼音输入法输入汉字或者使用语音输入完毕或者取消时，这个事件就会被触发。</li></ul><p>因此我们可以声明一个标记<code>lock</code>，在<code>compositionstart</code>、<code>compositionend</code>两个事件过程之间的时候<code>lock</code>值为 true，在 input <code>onChange</code>事件中通过<code>lock</code>的值来判断当前输入的状态和截取。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (e.<span class="property">type</span> === <span class="string">&#x27;compositionstart&#x27;</span>) &#123;</span><br><span class="line">  lock.<span class="property">current</span> = <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> (e.<span class="property">type</span> === <span class="string">&quot;compositionend&quot;</span>) &#123;</span><br><span class="line">  lock.<span class="property">current</span> = <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> &#123; value &#125; = e.<span class="property">target</span>;</span><br><span class="line"><span class="keyword">const</span> v = value.<span class="title function_">trim</span>();</span><br><span class="line"><span class="keyword">if</span> (value.<span class="property">length</span> - v.<span class="property">length</span> !== <span class="number">0</span>) &#123;</span><br><span class="line">  <span class="title function_">setLength</span>(maxLength + (value.<span class="property">length</span> - v.<span class="property">length</span>))</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> (v.<span class="property">length</span> &gt; maxLength) &#123;</span><br><span class="line">  <span class="keyword">const</span> newValue = v.<span class="title function_">slice</span>(<span class="number">0</span>, -(v.<span class="property">length</span> - maxLength)).<span class="title function_">trim</span>();</span><br><span class="line">  <span class="keyword">if</span> (!lock.<span class="property">current</span>) &#123;</span><br><span class="line">    inputRef.<span class="property">current</span>.<span class="property">value</span> = value.<span class="title function_">slice</span>(<span class="number">0</span>, -(v.<span class="property">length</span> - maxLength));</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">setNum</span>(newValue.<span class="property">length</span>);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">  <span class="title function_">setNum</span>(v.<span class="property">length</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// html</span></span><br><span class="line">&lt;input</span><br><span class="line">  ref=&#123;inputRef&#125;</span><br><span class="line">  className=<span class="string">&quot;yp-input__inner&quot;</span></span><br><span class="line">  onChange=&#123;handleChange&#125;</span><br><span class="line">  onCompositionStart=&#123;handleChange&#125;</span><br><span class="line">  onCompositionEnd=&#123;handleChange&#125;</span><br><span class="line">  ...</span><br><span class="line">/&gt;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>那到这里一个 Input 组件字数计算的问题总算解决了，从中也是吸取了不少的经验和总结，继续加油！！！</p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;前言：最近在完善 Input 组件字数计算的时候引发了不少的问题：1、到限制字数时，继续输入中文会替换末尾的文字；2、不同浏览器截断的表现不一样等等问题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;先明确一下需求&lt;/p&gt;
&lt;h2 id=&quot;需求&quot;&gt;&lt;</summary>
      
    
    
    
    
    <category term="随笔" scheme="http://yoursite.com/tags/%E9%9A%8F%E7%AC%94/"/>
    
  </entry>
  
  <entry>
    <title>Monorepo的实践落地</title>
    <link href="http://yoursite.com/2021/04/26/Monorepo%E7%9A%84%E5%AE%9E%E8%B7%B5%E8%90%BD%E5%9C%B0/"/>
    <id>http://yoursite.com/2021/04/26/Monorepo%E7%9A%84%E5%AE%9E%E8%B7%B5%E8%90%BD%E5%9C%B0/</id>
    <published>2021-04-26T02:53:13.000Z</published>
    <updated>2021-04-26T02:59:59.517Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>前言：最近针对项目代码仓库进行了一次重构，之前代码管理缺少规范和模块化的思想，也是借着项目重构这次机会重新规划代码仓库，实践落地了一种新的项目管理方式——Monorepo，Monorepo的管理概念跟我们规划的项目代码管理非常贴合，再加上我们新增的组件库及工具库，打造了一套比较完整的工作流。</p></blockquote><p>一些概念性的的东西这里就不多说了，不太了解 <code>Monorepo</code> 的可以自行 google 一下，这里就默认已经了解过了。</p><p>要想从零开始定制一套完善的 Monorepo 的工程化工具，还是一件比较有难度的事情。不过社区已经提供了一些比较成熟的方案，我们可以拿来进行定制。</p><p>在重构方案选择上，我们选择了业界比较成熟的 <strong><a href="https://lerna.js.org/">lerna</a></strong> 和 <strong>yarn workspaces</strong>，用 <code>yarn</code> 来处理依赖问题，用 <code>lerna</code> 来处理发布问题。</p><p><a href="https://github.com/hzzlyxx/lerna">仓库地址</a></p><h2 id="lerna"><a href="#lerna" class="headerlink" title="lerna"></a>lerna</h2><p><code>lerna</code> 官方的定位：A tool for managing JavaScript projects with multiple packages（一个用来管理带有多个package的JavaScript项目）。</p><p>lerna不负责构建，测试等任务，它提出了一种集中管理package的目录模式，提供了一套自动化管理程序，让开发者不必再深耕到具体的组件里维护内容，在项目根目录就可以全局掌控，基于 npm scripts，使用者可以很好地完成组件构建，代码格式化等操作。</p><h3 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">npm install lerna –g</span><br><span class="line">//</span><br><span class="line">yarn add lerna global</span><br></pre></td></tr></table></figure><h3 id="基本命令"><a href="#基本命令" class="headerlink" title="基本命令"></a>基本命令</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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">// lerna 项目初始化</span><br><span class="line">lerna init // 固定模式</span><br><span class="line">lerna init -i // 独立模式</span><br><span class="line"></span><br><span class="line">// 创建子项目</span><br><span class="line">lerna create [包名]</span><br><span class="line"></span><br><span class="line">// 清除所有子项目的依赖</span><br><span class="line">lerna clean</span><br><span class="line"></span><br><span class="line">// 安装所有依赖</span><br><span class="line">lerna bootstrap</span><br><span class="line"></span><br><span class="line">// 发布</span><br><span class="line">lerna publish</span><br></pre></td></tr></table></figure><h2 id="重构的价值"><a href="#重构的价值" class="headerlink" title="重构的价值"></a>重构的价值</h2><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><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><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><br><span class="line">├── package.json</span><br><span class="line">├── README.md</span><br><span class="line">├── tool // 项目打包编译配置</span><br><span class="line">├── release // 打包目标文件夹</span><br><span class="line">└── src // 源代码</span><br><span class="line">    ├── css</span><br><span class="line">    ├── html</span><br><span class="line">    ├── images</span><br><span class="line">    ├── js</span><br><span class="line">    └── sass</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><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">// 重构后的代码目录：</span><br><span class="line">├── ... // 其它配置文件（代码规范）</span><br><span class="line">├── package.json</span><br><span class="line">├── README.md</span><br><span class="line">├── lerna.json // lerna 配置文件</span><br><span class="line">└── packages // 分割的小项目</span><br><span class="line">    ├── components</span><br><span class="line">    ├── utils</span><br><span class="line">    ├── ... // 多个业务模块</span><br></pre></td></tr></table></figure><p><strong>总结一下：</strong></p><ul><li>将之前混杂的代码库分割为独立版本控制的小项目（项目更清晰）</li><li>解决了包之间的依赖关系（新增了组件库以及工具库）</li><li>通过git仓库检查到改动并自动同步（公共基础库的发布）</li><li>根据提交的commit生成CHANGELOG版本日志文件（项目代码更规范）</li><li>TypeScript支持</li><li>统一技术栈</li><li>完善的工作流</li></ul><p>当然，<code>lerna</code> 还有更多的功能可以去发掘，还有很多可以结合 <code>lerna</code> 一起使用的工具。构建一套完善的仓库管理机制，可能它的收益不是一些量化的指标可以衡量出来的，也没有直接的价值输出，但它能在日常的工作中极大的提高工作效率，解放生产力，节省大量的人力成本。</p><p>下篇就来聊聊组件库的那些事。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;前言：最近针对项目代码仓库进行了一次重构，之前代码管理缺少规范和模块化的思想，也是借着项目重构这次机会重新规划代码仓库，实践落地了一种新的项目管理方式——Monorepo，Monorepo的管理概念跟我们规划的项目代码管理非常贴合，再加上我们新增的</summary>
      
    
    
    
    
    <category term="随笔" scheme="http://yoursite.com/tags/%E9%9A%8F%E7%AC%94/"/>
    
  </entry>
  
  <entry>
    <title>【记录】React TS中自定义DOM属性</title>
    <link href="http://yoursite.com/2021/04/16/%E3%80%90%E8%AE%B0%E5%BD%95%E3%80%91React%20TS%E4%B8%AD%E8%87%AA%E5%AE%9A%E4%B9%89DOM%E5%B1%9E%E6%80%A7/"/>
    <id>http://yoursite.com/2021/04/16/%E3%80%90%E8%AE%B0%E5%BD%95%E3%80%91React%20TS%E4%B8%AD%E8%87%AA%E5%AE%9A%E4%B9%89DOM%E5%B1%9E%E6%80%A7/</id>
    <published>2021-04-16T09:35:22.000Z</published>
    <updated>2021-04-16T09:41:18.723Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>前言：最近在项目中进行渐进式重构升级，技术栈也是从之前的jQuery时代转换成React和TypeScript。当然重构过程中也冒出了不少的问题，本文将记录一下React TS中自定义DOM属性的一些解决方案。</p></blockquote><p>先看一下需求</p><ul><li><strong>在原生DOM节点新增自定义属性</strong></li></ul><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">div</span> <span class="attr">mark</span>=<span class="string">&quot;mark&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><p>在React中这样写是能正常渲染的，React官方对于<a href="https://reactjs.org/blog/2017/09/08/dom-attributes-in-react-16.html">自定义DOM属性</a>也是有一篇文章特别说明，大致意思就是React 16之前对于未知的DOM属性。如果JSX具有React无法识别的属性，React只会跳过它，并不会渲染在DOM中，在React 16之后，进行了更改，所有未知属性都将最终出现在DOM中。</p><p>但是在TypeScript中这样写就会报错：</p><blockquote><p>类型“DetailedHTMLProps&lt;HTMLAttributes<HTMLDivElement>, HTMLDivElement&gt;”上不存在属性“mark”</p></blockquote><p>Google了一圈，在 <a href="https://stackoverflow.com/questions/40093655/how-do-i-add-attributes-to-existing-html-elements-in-typescript-jsx">stackoverflow</a> 看到一个差不多的问题，尝试按照useful最多的这样声明：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">declare <span class="variable language_">module</span> <span class="string">&#x27;react&#x27;</span> &#123;</span><br><span class="line">  interface <span class="title class_">HTMLProps</span>&lt;T&gt; &#123;</span><br><span class="line">    mark?:string;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然而还是报相同的错误，未解决。</p><p>最后在一个Answer中找到了解决方案，测试可行：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">declare <span class="variable language_">module</span> <span class="string">&#x27;react&#x27;</span> &#123;</span><br><span class="line">  interface <span class="title class_">HTMLAttributes</span>&lt;T&gt; <span class="keyword">extends</span> <span class="title class_">AriaAttributes</span>, <span class="title class_">DOMAttributes</span>&lt;T&gt; &#123;</span><br><span class="line">    mark?: string;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>添加这句声明后能顺利的解决ts的报错，但这也不是最好的解决方案，这是为什么呢？在添加了这句声明后，发现我们在上面声明的图片格式失效报错了：</p><blockquote><p>找不到模块“xxxxxx.png”或其相应的类型声明</p></blockquote><p>这里就直接列举出我尝试后的解决方案，可能不是最好的，但目前可以解决上面的两个报错问题：</p><p><strong>把自定义DOM属性类型声明和图片类型声明分开到不一样的.d.ts文件里，防止冲突报错</strong></p><p>如果有更好的解决方案，欢迎交流探讨。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;前言：最近在项目中进行渐进式重构升级，技术栈也是从之前的jQuery时代转换成React和TypeScript。当然重构过程中也冒出了不少的问题，本文将记录一下React TS中自定义DOM属性的一些解决方案。&lt;/p&gt;
&lt;/blockquote&gt;
</summary>
      
    
    
    
    
    <category term="随笔" scheme="http://yoursite.com/tags/%E9%9A%8F%E7%AC%94/"/>
    
    <category term="react" scheme="http://yoursite.com/tags/react/"/>
    
    <category term="TypeScript" scheme="http://yoursite.com/tags/TypeScript/"/>
    
    <category term="记录" scheme="http://yoursite.com/tags/%E8%AE%B0%E5%BD%95/"/>
    
  </entry>
  
  <entry>
    <title>Tree Shaking知多少</title>
    <link href="http://yoursite.com/2021/04/06/Tree-Shaking%E7%9F%A5%E5%A4%9A%E5%B0%91/"/>
    <id>http://yoursite.com/2021/04/06/Tree-Shaking%E7%9F%A5%E5%A4%9A%E5%B0%91/</id>
    <published>2021-04-06T09:16:31.000Z</published>
    <updated>2021-04-06T09:24:03.866Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>前言：最近在团队中负责公共基础服务的建设，封装了公共类库及组件库，以为能带来比较大的便捷，真是理想很丰满，现实很骨感，开发时通过ES Module引入及使用都很方便，的确达到了开发效率的提升，但后续打包发布测试时却遇到了难题，目标项目中未使用的代码也一并打包进来了，导致最后的bundle过大。那问题就很明显了，需要去除掉未使用的代码。</p></blockquote><p>下面跟分享下，我在Tree Shaking上的摸索历程。</p><h2 id="什么是Tree-Shaking"><a href="#什么是Tree-Shaking" class="headerlink" title="什么是Tree Shaking"></a>什么是Tree Shaking</h2><p>用个简单的栗子描述一下：我们将应用程序想象成一棵树。绿色叶子表示实际用到的 source code(源码) 和 library(库)，是树上活的树叶。灰色叶子表示未引用代码，是树上枯萎的树叶。为了除去死去的树叶，你必须摇动这棵树，使它们落下。</p><p>具体来说，在 webpack 项目中，有一个入口文件，相当于一棵树，入口文件有很多依赖的模块，相当于树叶。实际情况中，虽然依赖了某个模块，但其实只使用其中的某些功能。通过 Tree Shaking，将没有使用的模块摇掉，这样来达到删除无用代码（dead-code）的目的。</p><p>Tree Shaking 较早由 rollup 提出并实现，后来，webpack 从 webpack2 开始也增加了 Tree Shaking 的功能。</p><h2 id="Tree-Shaking的原理"><a href="#Tree-Shaking的原理" class="headerlink" title="Tree Shaking的原理"></a>Tree Shaking的原理</h2><p>Tree Shaking 本质是借助 ES module 的静态分析能力来消除无用的代码（dead-code）。</p><h3 id="静态分析能力"><a href="#静态分析能力" class="headerlink" title="静态分析能力"></a>静态分析能力</h3><p>Tree Shaking 的目的是减少文件的体积，节约带宽，提高加载速度，所以文件必须在浏览器加载之前完成瘦身，也就是在打包的时候完成这个功能。ES6 使用 import 和 export 可以在编译期确定模块间的依赖关系，这是 Tree Shaking 的必需条件，这也意味着被 Babel 编译成 ES5 的代码是不能 Tree Shaking 的。</p><h3 id="dead-code"><a href="#dead-code" class="headerlink" title="dead-code"></a>dead-code</h3><p>我们需要理解什么是无用的代码，满足以下特征，即是无用代码：</p><ul><li>代码不会被执行，不可达到</li><li>代码执行的结果不会被用到</li><li>代码只会影响死变量（只写不读）</li></ul><p>Tree Shaking的原理总结下来就是以下两点：</p><p>1、ES6的模块引入是静态分析的，故而可以在编译时正确判断到底加载了什么代码<br>2、分析程序流，判断哪些变量未被使用、引用，进而删除此代码</p><h2 id="TS-编译的类库"><a href="#TS-编译的类库" class="headerlink" title="TS 编译的类库"></a>TS 编译的类库</h2><p>原理理解后，那对于开头那个问题就比较好解决了。</p><p>查看我的tsconfig.json后果然配置有误，配置的 <code>&quot;target&quot;: &quot;ES5&quot;</code> 没有配置 <code>module</code> 导致 TS 编译成 <code>CommonJS</code> 模块了，也就无法满足 Tree Shaking 的条件。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 修改后的tsconfig.json</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  ...</span><br><span class="line">  <span class="attr">&quot;compilerOptions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;target&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ES5&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;module&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ESNext&quot;</span></span><br><span class="line">    ...</span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>修改完 TS 编译配置后，让目标项目重新打包后就符合预期，去除了无用的代码，bundle的体积也明显的减少了。</p><p>到这里，感觉万事大吉了，然而比较坑的是用 class 写的类库还是没办法消除，还是会被打包进去。</p><p>还是我们上面配置的<code>&quot;target&quot;: &quot;ES5&quot; </code>的问题，因为我们项目还需要兼容IE（万恶的IE），所以需要配置编译为es5，导致我们的 class 被编译成了 <code>IIFE</code> （立即调用函数表达式），又有一个新的问题：<strong>Webpack Tree Shaking不会清除IIFE</strong></p><h2 id="Webpack-Tree-Shaking"><a href="#Webpack-Tree-Shaking" class="headerlink" title="Webpack Tree Shaking"></a>Webpack Tree Shaking</h2><p>当我们用 <code>Webpack</code> 配合 <code>UglifyJS</code> 打包文件时，我们class类的IIFE又被打包进去了。这跟我们想象的完全不一样啊？为什么呢？无用的类不能消除，这还能叫做 Tree Shaking 吗。</p><p>在<a href="https://zhuanlan.zhihu.com/p/32831172">你的Tree-Shaking并没什么卵用</a>中有过分析，里面有一个例子比较好，见下文</p><p>原因很简单：<code>uglify没有完善的程序流分析。它可以简单的判断变量后续是否被引用、修改，但是不能判断一个变量完整的修改过程，不知道它是否已经指向了外部变量，所以很多有可能会产生副作用的代码，都只能保守的不删除</code>。</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><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> V8Engine = (<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">V8Engine</span> () &#123;&#125;</span><br><span class="line">  V8Engine.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123; <span class="keyword">return</span> <span class="string">&#x27;V8&#x27;</span> &#125;</span><br><span class="line">  <span class="keyword">return</span> V8Engine</span><br><span class="line">&#125;())</span><br><span class="line"><span class="keyword">var</span> V6Engine = (<span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">V6Engine</span> () &#123;&#125;</span><br><span class="line">  V6Engine.<span class="property"><span class="keyword">prototype</span></span> = V8Engine.<span class="property"><span class="keyword">prototype</span></span> <span class="comment">// &lt;---- side effect</span></span><br><span class="line">  V6Engine.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">toString</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123; <span class="keyword">return</span> <span class="string">&#x27;V6&#x27;</span> &#125;</span><br><span class="line">  <span class="keyword">return</span> V6Engine</span><br><span class="line">&#125;())</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="keyword">new</span> <span class="title function_">V8Engine</span>().<span class="title function_">toString</span>()) <span class="comment">// V6</span></span><br></pre></td></tr></table></figure><p><code>V6Engine</code>虽然没有被使用，但是它修改了V8Engine原型链上的属性，这就产生副作用了。如果 <code>V6Engine</code> 这个IIFE里面再搞一些全局变量的声明，那就当然不能删除了。那就没有解决方案吗？在<a href="https://zhuanlan.zhihu.com/p/32831172">你的Tree-Shaking并没什么卵用</a>中最后有提供解决方案。</p><p>如果想利用好 Webpack 的 Tree Shaking，我们需要规范自己的代码：</p><ul><li>使用 ES2015 模块语法（即 <code>import</code> 和 <code>export</code>）</li><li>确保没有编译器将您的 ES2015 模块语法转换为 CommonJS 的（顺带一提，这是现在常用的 @babel&#x2F;preset-env 的默认行为，详细信息请参阅<a href="https://babeljs.io/docs/en/babel-preset-env#modules">文档</a>）</li><li>尽量不写带有副作用的代码。诸如编写了立即执行函数，在函数里又使用了外部变量等</li><li>如果JavaScript库开发中，难以避免的产生各种副作用代码，可以将功能函数或者组件，打包成单独的文件或目录，以便于用户可以通过目录去加载。如有条件，也可为自己的库开发单独的webpack-loader，便于用户按需加载</li><li>配置TypeScript编译器以<code>&quot;esnext&quot;</code>用作 <code>module</code>，使代码能以 <code>ES module</code> 导出</li></ul><p>目前也是越来越多的 Npm 第三方模块考虑到了 Tree Shaking，并对其提供了支持。 相信 Tree Shaking 也会越来越成熟。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://webpack.js.org/guides/tree-shaking/">Webpack 官方文档</a></li><li><a href="https://zhuanlan.zhihu.com/p/32831172">你的Tree-Shaking并没什么卵用</a></li><li><a href="https://juejin.cn/post/6844903544756109319">Tree-Shaking性能优化实践 - 原理篇</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;前言：最近在团队中负责公共基础服务的建设，封装了公共类库及组件库，以为能带来比较大的便捷，真是理想很丰满，现实很骨感，开发时通过ES Module引入及使用都很方便，的确达到了开发效率的提升，但后续打包发布测试时却遇到了难题，目标项目中未使用的代码</summary>
      
    
    
    
    
    <category term="随笔" scheme="http://yoursite.com/tags/%E9%9A%8F%E7%AC%94/"/>
    
  </entry>
  
  <entry>
    <title>既爱又恨的定时器</title>
    <link href="http://yoursite.com/2021/01/10/%E6%97%A2%E7%88%B1%E5%8F%88%E6%81%A8%E7%9A%84%E5%AE%9A%E6%97%B6%E5%99%A8/"/>
    <id>http://yoursite.com/2021/01/10/%E6%97%A2%E7%88%B1%E5%8F%88%E6%81%A8%E7%9A%84%E5%AE%9A%E6%97%B6%E5%99%A8/</id>
    <published>2021-01-10T14:26:29.000Z</published>
    <updated>2021-04-06T09:18:03.759Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>前言：前两天开发了一个抽奖功能的需求，也是被定时器折磨了一番。</p></blockquote><h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><ul><li>React Hooks里的定时器</li><li>倒计时不准</li><li>应用退到手机后台后的定时器</li></ul><p>接下来咱们一一解决它。</p><h2 id="React-Hooks里的定时器"><a href="#React-Hooks里的定时器" class="headerlink" title="React Hooks里的定时器"></a>React Hooks里的定时器</h2><p>对于每个使用 React Hooks 的开发者来说，setInterval 是一个绕不过去的”坑“。由于React Hooks 特有的设计理念，如果用固有的思维模式去写 setInterval，很容易触发意想不到的 bug。</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><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><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">function</span> <span class="title function_">Test</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [count, setCount] = <span class="title function_">useState</span>(<span class="number">0</span>);</span><br><span class="line">  <span class="keyword">const</span> timer = <span class="title function_">useRef</span>();</span><br><span class="line">  </span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!timer.<span class="property">current</span>) &#123;</span><br><span class="line">      timer.<span class="property">current</span> = <span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="title function_">setCount</span>(count + <span class="number">1</span>);</span><br><span class="line">      &#125;, <span class="number">1000</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (timer.<span class="property">current</span>) &#123;</span><br><span class="line">        <span class="built_in">clearInterval</span>(timer.<span class="property">current</span>);</span><br><span class="line">        timer.<span class="property">current</span> = <span class="literal">null</span>;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;, []);</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>&#123;count&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样写确实很简洁，也符合我们固有的思维模式，但是它的实现效果却与我们的目标背道而驰。</p><p>预期的效果是页面上的数字会每秒增加 1 ，但实际是数字增加到 1 后便静止不动了。由于 useEffect 的依赖为空数组，所以 setInterval 只会在组件完成初次渲染后被调用一次，从而使得回调函数在之后每次被定时调用时，取到的 count 都是初次渲染时的值 0（闭包的原因），页面上的数值也会永远停留在 1。</p><p>解决方案：</p><p>我们使用useRef来解决来解决这种闭包引起的问题。（useRef 在 React Hooks 中的作用，正如官网说的，它像一个变量，类似于 this ，它就像一个盒子，你可以存放任何东西， <strong>useRef 每次都会返回相同的引用</strong>）</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><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">function</span> <span class="title function_">Test</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [count, setCount] = <span class="title function_">useState</span>(<span class="number">0</span>);</span><br><span class="line">  <span class="keyword">const</span> timer = <span class="title function_">useRef</span>();</span><br><span class="line">  <span class="keyword">const</span> time = <span class="title function_">useRef</span>(<span class="number">0</span>);</span><br><span class="line">  </span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!timer.<span class="property">current</span>) &#123;</span><br><span class="line">      timer.<span class="property">current</span> = <span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        time.<span class="property">current</span> += <span class="number">1</span>;</span><br><span class="line">        <span class="title function_">setCount</span>(time.<span class="property">current</span>);</span><br><span class="line">      &#125;, <span class="number">1000</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (timer.<span class="property">current</span>) &#123;</span><br><span class="line">        <span class="built_in">clearInterval</span>(timer.<span class="property">current</span>);</span><br><span class="line">        timer.<span class="property">current</span> = <span class="literal">null</span>;</span><br><span class="line">        time.<span class="property">current</span> = <span class="literal">null</span>;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;, []);</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>&#123;count&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="倒计时不准（定时器不准）"><a href="#倒计时不准（定时器不准）" class="headerlink" title="倒计时不准（定时器不准）"></a>倒计时不准（定时器不准）</h2><p>我们知道定时器的执行时间并不是确定的。这是由于 JS 是门非阻塞单线程语言，因为在最初 JS 就是为了和浏览器交互而诞生的。如果 JS 是门多线程的语言话，我们在多个线程中处理 DOM 就可能会发生问题（一个线程中新加节点，另一个线程中删除节点）。那么为什么说定时器的执行时间不是确定的呢？那就得来细数一下轮询（Event loop）了。</p><p><strong>Event Loop</strong> 的是计算机系统的一种运行机制。JS 语言就采用这种机制，来解决单线程运行带来的一些问题。</p><p>在 JavaScript 中，任务被分为两种，一种宏任务（MacroTask），一种叫微任务（MicroTask）。</p><p><strong>常见的宏任务有</strong>：script全部代码、setTimeout、setInterval、setImmediate（浏览器暂时不支持，只有IE10支持，具体可见MDN）、I&#x2F;O、UI Rendering。</p><p><strong>常见的微任务有</strong>：Process.nextTick（Node独有）、Promise、Object.observe(已废弃)、MutationObserver</p><p>一次 <strong>Event loop</strong> 顺序是这样的：</p><ol><li>执行同步代码，这属于宏任务</li><li>执行栈为空，查询是否有微任务需要执行</li><li>执行所有微任务</li><li>必要的话渲染 UI</li><li>然后开始下一轮 Event loop，执行宏任务中的异步代码</li></ol><p>通过上述的 <strong>Event loop</strong> 顺序可知，我们实际写的定时器（倒计时）1秒是不确定的。当我们倒计时还在定时器里减1就不准确了。</p><p>解决方案：</p><p>既然定时器不准，那我们就不能做减1的操作，可以利用时间戳的差值来计算。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Test</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [count, setCount] = <span class="title function_">useState</span>(<span class="number">3600</span>);</span><br><span class="line">  <span class="keyword">const</span> timer = <span class="title function_">useRef</span>();</span><br><span class="line">  <span class="keyword">const</span> time = <span class="title function_">useRef</span>(<span class="number">0</span>);</span><br><span class="line">  </span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    time.<span class="property">current</span> = count;</span><br><span class="line">    <span class="keyword">if</span> (!timer.<span class="property">current</span>) &#123;</span><br><span class="line">      <span class="keyword">let</span> start = <span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">getTime</span>();</span><br><span class="line">      <span class="keyword">let</span> end = <span class="number">0</span>;</span><br><span class="line">      timer.<span class="property">current</span> = <span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> s = (end - start) / <span class="number">1000</span>;</span><br><span class="line">        time.<span class="property">current</span> -= s;</span><br><span class="line">        <span class="title function_">setCount</span>(time.<span class="property">current</span>);</span><br><span class="line">        start = end;</span><br><span class="line">      &#125;, <span class="number">1000</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (timer.<span class="property">current</span>) &#123;</span><br><span class="line">        <span class="built_in">clearInterval</span>(timer.<span class="property">current</span>);</span><br><span class="line">        timer.<span class="property">current</span> = <span class="literal">null</span>;</span><br><span class="line">        time.<span class="property">current</span> = <span class="literal">null</span>;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;, []);</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>&#123;count&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="应用退到手机后台后的定时器"><a href="#应用退到手机后台后的定时器" class="headerlink" title="应用退到手机后台后的定时器"></a>应用退到手机后台后的定时器</h2><p>在手机端，如果应用被切换到后台（不是关闭应用，是切到后台），那么这时候定时器就会有问题。PC上的Firefox，Chrome和Safari等浏览器，都会自动把未激活页面中的JavaScript定时器（setTimeout，setInterval）间隔最小值改为1秒以上。这是因为间隔很小的定时器一般用来做UI更新（例如用定时器实现的动画），让用户不可见的页面上的定时器跑慢一些，既节省资源又不会影响体验。对移动浏览器来说，内存，CPU，带宽等资源更加宝贵，设备移动的上浏览器往往会<strong>直接冻结</strong>所有未激活页面上的所有定时器。</p><p>解决方案：</p><p>通过监听 <strong>visibilitychange</strong> 方法，如果退到后台记录下当前时间，等切回来，再算一下当前时间，然后计算时间差，最后用当时定格的那个时间去减去这个时间差，再赋值给这个定时器，就ok了。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> start = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">let</span> end = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">let</span> startS = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">const</span> hiddenProperty =</span><br><span class="line">  <span class="string">&#x27;hidden&#x27;</span> <span class="keyword">in</span> <span class="variable language_">document</span> ?</span><br><span class="line">  <span class="string">&#x27;hidden&#x27;</span> :</span><br><span class="line">  <span class="string">&#x27;webkitHidden&#x27;</span> <span class="keyword">in</span> <span class="variable language_">document</span> ?</span><br><span class="line">  <span class="string">&#x27;webkitHidden&#x27;</span> :</span><br><span class="line">  <span class="string">&#x27;mozHidden&#x27;</span> <span class="keyword">in</span> <span class="variable language_">document</span> ?</span><br><span class="line">  <span class="string">&#x27;mozHidden&#x27;</span> :</span><br><span class="line">  <span class="literal">null</span>;</span><br><span class="line"><span class="keyword">if</span> (hiddenProperty == <span class="literal">null</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> visibilityChangeEvent = hiddenProperty.<span class="title function_">replace</span>(<span class="regexp">/hidden/i</span>, <span class="string">&#x27;visibilitychange&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> onVisibilityChange = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!<span class="variable language_">document</span>[hiddenProperty]) &#123;</span><br><span class="line">    end = <span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">getTime</span>(); <span class="comment">// 页面切回来的时间戳</span></span><br><span class="line">    <span class="keyword">const</span> s = <span class="title class_">Math</span>.<span class="title function_">ceil</span>((end - start) / <span class="number">1000</span>); <span class="comment">// 时间差</span></span><br><span class="line">    <span class="keyword">const</span> timeC = startS - s; <span class="comment">// 当时定格的那个时间去减去这个时间差 = 最新的倒计时</span></span><br><span class="line">    <span class="keyword">if</span> (timeC &gt; <span class="number">0</span>) &#123;</span><br><span class="line">      <span class="title function_">setTime</span>(timeC);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="title function_">setTime</span>(-<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    startS = time; <span class="comment">// 当前倒计时时间戳</span></span><br><span class="line">    start = <span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">getTime</span>(); <span class="comment">// 页面退到后台的时间戳</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(visibilityChangeEvent, onVisibilityChange);</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>到这里，基本就针对碰到的定时器的三个问题讲了一遍，不对之处，还请多多指正！</p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;前言：前两天开发了一个抽奖功能的需求，也是被定时器折磨了一番。&lt;/p&gt;
&lt;/blockquote&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;l</summary>
      
    
    
    
    
    <category term="随笔" scheme="http://yoursite.com/tags/%E9%9A%8F%E7%AC%94/"/>
    
    <category term="js" scheme="http://yoursite.com/tags/js/"/>
    
  </entry>
  
  <entry>
    <title>记一次dva升级后的bug</title>
    <link href="http://yoursite.com/2020/03/13/%E8%AE%B0%E4%B8%80%E6%AC%A1dva%E5%8D%87%E7%BA%A7%E5%90%8E%E7%9A%84bug/"/>
    <id>http://yoursite.com/2020/03/13/%E8%AE%B0%E4%B8%80%E6%AC%A1dva%E5%8D%87%E7%BA%A7%E5%90%8E%E7%9A%84bug/</id>
    <published>2020-03-13T13:24:32.000Z</published>
    <updated>2020-03-13T13:37:00.212Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>前言：去年的时候针对公司老项目（dva环境）进行了一次优化升级，升级后没出现啥问题，因为疫情的关系，导致一直在家办公，最近针对老项目又加了个需求，用自己的电脑从Gogs上down下来<code>npm install</code>之后运行就报错了。</p></blockquote><p>安装完依赖npm start的时候，就开始报如下的错误：</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">Failed to compile</span><br><span class="line">./node_modules/history/esm/history.js</span><br><span class="line">Module not found: Can<span class="string">&#x27;t resolve &#x27;</span>@babel/runtime/helpers/esm/extends<span class="string">&#x27; in &#x27;</span>xxxx\prong-console-tenant\node_modules\<span class="built_in">history</span>\esm<span class="string">&#x27;</span></span><br></pre></td></tr></table></figure><p>一般删了node_modules重新安装依赖能解决80%的问题，但这次重新安装却不起作用了。于是就开始了折腾之旅了，去Google搜了一圈，找了几个解决方案尝试之后发现都不太行。</p><p>最后在Github issues里找到了解决思路：<code>history</code> 版本问题。</p><p>按以下步骤检查：</p><p>1、<code>npm list history</code> 查看history安装情况；</p><p>2、如果版本不是4.7.2，卸载history， <code>npm un history</code>；</p><p>3、<code>npm list history</code> 确保history已全部删除；</p><p>4、<code>npm install --save history@4.7.2</code> 安装指定版本；</p><p>按照如上步骤就把这个错误解决了，运行成功。完美！</p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;前言：去年的时候针对公司老项目（dva环境）进行了一次优化升级，升级后没出现啥问题，因为疫情的关系，导致一直在家办公，最近针对老项目又加了个需求，用自己的电脑从Gogs上down下来&lt;code&gt;npm install&lt;/code&gt;之后运行就报错了。</summary>
      
    
    
    
    
    <category term="随笔" scheme="http://yoursite.com/tags/%E9%9A%8F%E7%AC%94/"/>
    
    <category term="异常" scheme="http://yoursite.com/tags/%E5%BC%82%E5%B8%B8/"/>
    
  </entry>
  
  <entry>
    <title>js处理long型丢失精度问题</title>
    <link href="http://yoursite.com/2020/01/21/JavaScript%E5%A4%84%E7%90%86Long%E5%9E%8B%E4%B8%A2%E5%A4%B1%E7%B2%BE%E5%BA%A6/"/>
    <id>http://yoursite.com/2020/01/21/JavaScript%E5%A4%84%E7%90%86Long%E5%9E%8B%E4%B8%A2%E5%A4%B1%E7%B2%BE%E5%BA%A6/</id>
    <published>2020-01-21T06:03:51.000Z</published>
    <updated>2021-06-11T09:48:11.548Z</updated>
    
    <content type="html"><![CDATA[<p>最近项目后端为 Prong 开发了一个基于 snowflake 算法的 Java 分布式 ID 组件，将实体主键从原来的 String 类型的 UUID 修改成了 Long 型的分布式 ID。修改后发现前端显示的 ID 和数据库中的 ID 不一致。例如数据库中存储的是：812782555915911412，显示出来却成了 812782555915911400，后面 2 位变成了 0，精度丢失了：</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="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">812782555915911412</span>);</span><br><span class="line"><span class="number">812782555915911400</span>;</span><br></pre></td></tr></table></figure><h2 id="原因"><a href="#原因" class="headerlink" title="原因"></a>原因</h2><p>这是因为 JavaScript 中数字的精度是有限的，Java 的 Long 类型的数字超出了 JavaScript 的处理范围。JavaScript 内部只有一种数字类型 Number，所有数字都是采用 IEEE 754 标准定义的双精度 64 位格式存储，即使整数也是如此。这就是说，JavaScript 语言的底层根本没有整数，所有数字都是小数（64 位浮点数）。其结构如图：</p><p><img src="https://hzzlyxx.oss-cn-beijing.aliyuncs.com/blog/long/long.png" alt="https://hzzlyxx.oss-cn-beijing.aliyuncs.com/blog/long/long.png"></p><p>各位的含义如下：</p><ul><li>1 位（s） 用来表示符号位，0 表示正数，1 表示负数</li><li>11 位（e） 用来表示指数部分</li><li>52 位（f） 表示小数部分（即有效数字）</li></ul><p>双精度浮点数(<code>double</code>)并不是能够精确表示范围内的所有数， 虽然双精度浮点型的范围看上去很大: 。 可以表示的最大整数可以很大，但能够精确表示，使用算数运算的并没有这么大。因为小数部分最大是 <code>52</code> 位，因此 JavaScript 中能精准表示的最大整数是 ，十进制即 <code>9007199254740991</code>。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Math</span>.<span class="title function_">pow</span>(<span class="number">2</span>, <span class="number">53</span>) - <span class="number">1</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(1L&lt;&lt;<span class="number">53</span>);</span><br><span class="line"><span class="number">9007199254740991</span></span><br></pre></td></tr></table></figure><p>JavaScript 有所谓的最大和最小安全值：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Number</span>.<span class="property">MAX_SAFE_INTEGER</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Number</span>.<span class="property">MIN_SAFE_INTEGER</span>);</span><br><span class="line"><span class="number">9007199254740991</span> - <span class="number">9007199254740991</span>;</span><br></pre></td></tr></table></figure><p><code>安全</code> 意思是说能够 <code>one-by-one</code> 表示的整数，也就是说在范围内，双精度数表示和整数是一对一的，在这个范围以内，所有的整数都有唯一的浮点数表示，这叫做安全整数。</p><p>而超过这个范围，会有两个或更多整数的双精度表示是相同的；即超过这个范围，有的整数是无法精确表示的，只能大约(round)到与它相近的浮点数（说到底就是科学计数法）表示，这种情况下叫做<code>不安全整数</code>，例如：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Number</span>.<span class="property">MAX_SAFE_INTEGER</span> + <span class="number">1</span>); <span class="comment">// 结果：9007199254740992，精度未丢失</span></span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Number</span>.<span class="property">MAX_SAFE_INTEGER</span> + <span class="number">2</span>); <span class="comment">// 结果：9007199254740992，精度丢失</span></span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Number</span>.<span class="property">MAX_SAFE_INTEGER</span> + <span class="number">3</span>); <span class="comment">// 结果：9007199254740994，精度未丢失</span></span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Number</span>.<span class="property">MAX_SAFE_INTEGER</span> + <span class="number">4</span>); <span class="comment">// 结果：9007199254740996，精度丢失</span></span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Number</span>.<span class="property">MAX_SAFE_INTEGER</span> + <span class="number">5</span>); <span class="comment">// 结果：9007199254740996，精度未丢失</span></span><br></pre></td></tr></table></figure><p>而 <code>Java</code> 的 <code>Long</code> 类型的有效位数是 63 位（扣除一位符号位），其最大值为，十进制为 9223372036854775807。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">    System.out.println(Long.MAX_VALUE);</span><br><span class="line">    System.out.println((<span class="number">1L</span>&lt;&lt;<span class="number">63</span>) -<span class="number">1</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="number">9223372036854775807</span></span><br><span class="line"><span class="number">9223372036854775807</span></span><br></pre></td></tr></table></figure><p>所以只要 java 传给 JavaScript 的 <code>Long</code> 类型的值超过 9007199254740991，就有可能产生精度丢失，从而导致数据和逻辑出错。</p><blockquote><p>和其他编程语言（如 C 和 Java）不同，JavaScript 不区分整数值和浮点数值，所有数字在 JavaScript 中均用浮点数值表示，所以在进行数字运算的时候要特别注意精度缺失问题。容易造成混淆的是，某些运算只有整数才能完成，此时 JavaScript 会自动把 64 位浮点数，转成 32 位整数，然后再进行运算，由于浮点数不是精确的值，所以涉及小数的比较和运算要特别小心。</p><p>进一步阅读：<a href="https://wangdoc.com/javascript/types/number.html">JavaScript 教程 - 数据类型 - 数值</a></p></blockquote><h2 id="解决方法"><a href="#解决方法" class="headerlink" title="解决方法"></a>解决方法</h2><p>解决办法就是让 Javascript 把数字当成字符串进行处理。对 Javascript 来说如果不进行运算，数字和字符串处理起来没有什么区别。当然如果需要进行运算，只能采用其他方法，例如 JavaScript 的一些开源库 bignum、bigint 等支持长整型的处理。Java 进行 JSON 处理的时候是能够正确处理 long 型的，只需要将数字转化成字符串就可以了。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;最近项目后端为 Prong 开发了一个基于 snowflake 算法的 Java 分布式 ID 组件，将实体主键从原来的 String 类型的 UUID 修改成了 Long 型的分布式 ID。修改后发现前端显示的 ID 和数据库中的 ID 不一致。例如数据库中存储的是：81</summary>
      
    
    
    
    
    <category term="随笔" scheme="http://yoursite.com/tags/%E9%9A%8F%E7%AC%94/"/>
    
    <category term="JavaScript" scheme="http://yoursite.com/tags/JavaScript/"/>
    
  </entry>
  
  <entry>
    <title>h5手机键盘弹出收起的处理</title>
    <link href="http://yoursite.com/2020/01/19/h5%E6%89%8B%E6%9C%BA%E9%94%AE%E7%9B%98%E5%BC%B9%E5%87%BA%E6%94%B6%E8%B5%B7%E7%9A%84%E5%A4%84%E7%90%86/"/>
    <id>http://yoursite.com/2020/01/19/h5%E6%89%8B%E6%9C%BA%E9%94%AE%E7%9B%98%E5%BC%B9%E5%87%BA%E6%94%B6%E8%B5%B7%E7%9A%84%E5%A4%84%E7%90%86/</id>
    <published>2020-01-19T07:30:32.000Z</published>
    <updated>2020-01-21T06:38:42.473Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>前言：前端时间也是应项目的需求开始了h5移动端的折腾之旅，在目前中台的基础上扩展了两个ToC移动端项目，下面就是在h5移动端表单页面键盘弹出收起兼容性的一些总结。</p></blockquote><h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><p>在 h5 项目中，我们会经常遇到一些表单页面，在输入框获取焦点时，会自动触发键盘弹起，而键盘弹出在 IOS 与 Android 的 webview 中表现并非一致，同时当我们主动触发键盘收起时也同样存在差异化。</p><h3 id="键盘弹出"><a href="#键盘弹出" class="headerlink" title="键盘弹出"></a>键盘弹出</h3><ul><li>IOS：IOS系统 的键盘处在窗口的最上层，当键盘弹起时，webview 的高度 height 并没有改变，只是 scrollTop 发生变化，页面可以滚动。且页面可以滚动的最大限度为弹出的键盘的高度，而只有键盘弹出时页面恰好也滚动到最底部时，scrollTop 的变化值为键盘的高度，其他情况下则无法获取。这就导致在 IOS 情况下难以获取键盘的真实高度。</li><li>Android: 在Android系统中，键盘也是处在窗口的最上层，键盘弹起时，如果输入框在靠近底部的话，就会被键盘挡住，只有你输入的时候输入框才会滚动到可视化区域。</li></ul><h3 id="键盘收起"><a href="#键盘收起" class="headerlink" title="键盘收起"></a>键盘收起</h3><ul><li>IOS：触发键盘上的按钮收起键盘或者输入框以外的页面区域时，输入框会失去焦点，因此会触发输入框的 blur 事件；当键盘收起时，页面底部会出现一个空白区域，页面会被顶起。</li><li>Android: 触发键盘上的按钮收起键盘时，输入框并不会失去焦点，因此不会触发页面的 blur 事件；触发输入框以外的区域时，输入框会失去焦点，触发输入框的 blur 事件。</li></ul><h2 id="期望的结果"><a href="#期望的结果" class="headerlink" title="期望的结果"></a>期望的结果</h2><p>针对不同系统触发键盘弹出收起时的差异化，我们希望功能流畅的同时，尽量保持用户体验的一致性。</p><h2 id="对症下药"><a href="#对症下药" class="headerlink" title="对症下药"></a>对症下药</h2><p>上面我们理清了目前市面上两大主要系统的差异性，接下来就需对症下药了。</p><p>在 h5 中目前没有接口可以直接监听键盘事件，但我们可以通过分析键盘弹出、收起的触发过程及表现形式，来判断键盘是弹出还是收起的状态。</p><ul><li><p>键盘弹出：输入框获取焦点时会自动触发键盘的弹起动作，因此，我们可以监听 focusin 事件，在里面实现键盘弹出后所需的页面逻辑。</p></li><li><p>键盘收起：当触发其他页面区域收起键盘时，我们可以监听 focusout 事件，在里面实现键盘收起后所需的页面逻辑。而在通过键盘按钮收起键盘时在 ios 与 android 端存在差异化表现，下面具体分析：</p><ul><li>IOS：触发了 focusout 事件，仍然通过该办法监听。</li><li>Android：没有触发 focusout 事件。在 android 中，键盘的状态切换（弹出、收起）不仅和输入框关联，同时还会影响到 webview 高度的变化，那我们就可以通过监听 webview height 的变化来判断键盘是否收起。</li></ul></li></ul><h3 id="系统判断"><a href="#系统判断" class="headerlink" title="系统判断"></a>系统判断</h3><p>在实践中我们可以通过 userAgent 来判断目前的系统：</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> ua = <span class="variable language_">window</span>.<span class="property">navigator</span>.<span class="property">userAgent</span>.<span class="title function_">toLocaleLowerCase</span>();</span><br><span class="line"><span class="keyword">const</span> isIOS = <span class="regexp">/iphone|ipad|ipod/</span>.<span class="title function_">test</span>(ua);</span><br><span class="line"><span class="keyword">const</span> isAndroid = <span class="regexp">/android/</span>.<span class="title function_">test</span>(ua);</span><br></pre></td></tr></table></figure><h3 id="IOS-处理"><a href="#IOS-处理" class="headerlink" title="IOS 处理"></a>IOS 处理</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> isReset = <span class="literal">true</span>; <span class="comment">//是否归位</span></span><br><span class="line"></span><br><span class="line"><span class="variable language_">this</span>.<span class="property">focusinHandler</span> = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  isReset = <span class="literal">false</span>; <span class="comment">//聚焦时键盘弹出，焦点在输入框之间切换时，会先触发上一个输入框的失焦事件，再触发下一个输入框的聚焦事件</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">this</span>.<span class="property">focusoutHandler</span> = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  isReset = <span class="literal">true</span>;</span><br><span class="line">  <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">//当焦点在弹出层的输入框之间切换时先不归位</span></span><br><span class="line">    <span class="keyword">if</span> (isReset) &#123;</span><br><span class="line">        <span class="variable language_">window</span>.<span class="title function_">scroll</span>(<span class="number">0</span>, <span class="number">0</span>); <span class="comment">//确定延时后没有聚焦下一元素，是由收起键盘引起的失焦，则强制让页面归位</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;, <span class="number">30</span>);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;focusin&#x27;</span>, <span class="variable language_">this</span>.<span class="property">focusinHandler</span>);</span><br><span class="line"><span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;focusout&#x27;</span>, <span class="variable language_">this</span>.<span class="property">focusoutHandler</span>);</span><br></pre></td></tr></table></figure><h3 id="Android-处理"><a href="#Android-处理" class="headerlink" title="Android 处理"></a>Android 处理</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> originHeight = <span class="variable language_">document</span>.<span class="property">documentElement</span>.<span class="property">clientHeight</span> || <span class="variable language_">document</span>.<span class="property">body</span>.<span class="property">clientHeight</span>;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">this</span>.<span class="property">resizeHandler</span> = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> resizeHeight = <span class="variable language_">document</span>.<span class="property">documentElement</span>.<span class="property">clientHeight</span> || <span class="variable language_">document</span>.<span class="property">body</span>.<span class="property">clientHeight</span>;</span><br><span class="line">  <span class="keyword">const</span> activeElement = <span class="variable language_">document</span>.<span class="property">activeElement</span>;</span><br><span class="line">  <span class="keyword">if</span> (resizeHeight &lt; originHeight) &#123;</span><br><span class="line">    <span class="comment">// 键盘弹起后逻辑</span></span><br><span class="line">    <span class="keyword">if</span> (activeElement &amp;&amp; (activeElement.<span class="property">tagName</span> === <span class="string">&quot;INPUT&quot;</span> || activeElement.<span class="property">tagName</span> === <span class="string">&quot;TEXTAREA&quot;</span>)) &#123;</span><br><span class="line">      <span class="built_in">setTimeout</span>(<span class="function">()=&gt;</span>&#123;</span><br><span class="line">        activeElement.<span class="title function_">scrollIntoView</span>(&#123; <span class="attr">block</span>: <span class="string">&#x27;center&#x27;</span> &#125;);<span class="comment">//焦点元素滚到可视区域的问题</span></span><br><span class="line">      &#125;,<span class="number">0</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// 键盘收起后逻辑</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;resize&#x27;</span>, <span class="variable language_">this</span>.<span class="property">resizeHandler</span>);</span><br></pre></td></tr></table></figure><h2 id="react-封装"><a href="#react-封装" class="headerlink" title="react 封装"></a>react 封装</h2><p>在 react 中我们可以写一个类装饰器来修饰表单组件。</p><blockquote><p>类装饰器：类装饰器在类声明之前被声明（紧靠着类声明）。 类装饰器应用于类构造函数，可以用来监视，修改或替换类定义。</p></blockquote><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// keyboard.tsx</span></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * @Description: 键盘处理装饰器</span></span><br><span class="line"><span class="comment"> * @Author: hzzly</span></span><br><span class="line"><span class="comment"> * @LastEditors: hzzly</span></span><br><span class="line"><span class="comment"> * @Date: 2020-01-09 09:36:40</span></span><br><span class="line"><span class="comment"> * @LastEditTime: 2020-01-10 12:08:47</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">React</span>, &#123; <span class="title class_">Component</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">keyboard</span> = (<span class="params"></span>) =&gt; <span class="function">(<span class="params">WrappedComponent: any</span>) =&gt;</span></span><br><span class="line">  <span class="keyword">class</span> <span class="title class_">HOC</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Component</span> &#123;</span><br><span class="line">    <span class="attr">focusinHandler</span>: (<span class="function">() =&gt;</span> <span class="keyword">void</span>) | <span class="literal">undefined</span>;</span><br><span class="line">    <span class="attr">focusoutHandler</span>: (<span class="function">() =&gt;</span> <span class="keyword">void</span>) | <span class="literal">undefined</span>;</span><br><span class="line">    <span class="attr">resizeHandler</span>: (<span class="function">() =&gt;</span> <span class="keyword">void</span>) | <span class="literal">undefined</span>;</span><br><span class="line">    <span class="title function_">componentDidMount</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> ua = <span class="variable language_">window</span>.<span class="property">navigator</span>.<span class="property">userAgent</span>.<span class="title function_">toLocaleLowerCase</span>();</span><br><span class="line">      <span class="keyword">const</span> isIOS = <span class="regexp">/iphone|ipad|ipod/</span>.<span class="title function_">test</span>(ua);</span><br><span class="line">      <span class="keyword">const</span> isAndroid = <span class="regexp">/android/</span>.<span class="title function_">test</span>(ua);</span><br><span class="line">      <span class="keyword">if</span> (isIOS) &#123;</span><br><span class="line">        <span class="comment">// 上面 IOS 处理</span></span><br><span class="line">        ...</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">if</span> (isAndroid) &#123;</span><br><span class="line">        <span class="comment">// 上面 Android 处理</span></span><br><span class="line">        ...</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="title function_">componentWillUnmount</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">focusinHandler</span> &amp;&amp; <span class="variable language_">this</span>.<span class="property">focusoutHandler</span>) &#123;</span><br><span class="line">        <span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">removeEventListener</span>(<span class="string">&#x27;focusin&#x27;</span>, <span class="variable language_">this</span>.<span class="property">focusinHandler</span>);</span><br><span class="line">        <span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">removeEventListener</span>(<span class="string">&#x27;focusout&#x27;</span>, <span class="variable language_">this</span>.<span class="property">focusoutHandler</span>);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">resizeHandler</span>) &#123;</span><br><span class="line">        <span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">removeEventListener</span>(<span class="string">&#x27;resize&#x27;</span>, <span class="variable language_">this</span>.<span class="property">resizeHandler</span>);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">WrappedComponent</span> &#123;<span class="attr">...this.props</span>&#125; /&gt;</span></span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> keyboard;</span><br></pre></td></tr></table></figure><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// PersonForm.tsx</span></span><br><span class="line">@<span class="title function_">keyboard</span>()</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">PersonForm</span> <span class="keyword">extends</span> <span class="title class_ inherited__">PureComponent</span>&lt;&#123;&#125;, &#123;&#125;&gt; &#123;</span><br><span class="line">  <span class="comment">// 业务逻辑</span></span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">PersonForm</span>;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;前言：前端时间也是应项目的需求开始了h5移动端的折腾之旅，在目前中台的基础上扩展了两个ToC移动端项目，下面就是在h5移动端表单页面键盘弹出收起兼容性的一些总结。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;问题&quot;&gt;&lt;a href=&quot;#问题</summary>
      
    
    
    
    
    <category term="随笔" scheme="http://yoursite.com/tags/%E9%9A%8F%E7%AC%94/"/>
    
    <category term="TypeScript" scheme="http://yoursite.com/tags/TypeScript/"/>
    
    <category term="js" scheme="http://yoursite.com/tags/js/"/>
    
    <category term="h5" scheme="http://yoursite.com/tags/h5/"/>
    
  </entry>
  
  <entry>
    <title>记npm login的一次异常解决</title>
    <link href="http://yoursite.com/2019/12/23/%E8%AE%B0npm%20login%E7%9A%84%E4%B8%80%E6%AC%A1%E5%BC%82%E5%B8%B8%E8%A7%A3%E5%86%B3/"/>
    <id>http://yoursite.com/2019/12/23/%E8%AE%B0npm%20login%E7%9A%84%E4%B8%80%E6%AC%A1%E5%BC%82%E5%B8%B8%E8%A7%A3%E5%86%B3/</id>
    <published>2019-12-23T05:40:08.000Z</published>
    <updated>2019-12-23T05:41:46.414Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>在工作中，我们常常会将一些通用性代码，比如一些工具类、公用业务逻辑代码以及团队定制化的UI库等，发布到私有npm仓库。需要的项目直接安装使用即可。</p><p>在一次组件开发完将要上传的私服的时候，通过 <code>npm login</code> 登录私服一直报错，出现了如下两种报错：</p><h2 id="登录报错npm-ERR-code-E401"><a href="#登录报错npm-ERR-code-E401" class="headerlink" title="登录报错npm ERR! code E401"></a>登录报错npm ERR! code E401</h2><p>npm登录时报错：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">$ npm login</span><br><span class="line">Username: 你的nexus用户名</span><br><span class="line">Password: 你的nexus用户密码</span><br><span class="line">Email: (this IS public) 你的邮箱</span><br><span class="line">npm ERR! code E401</span><br><span class="line">npm ERR! Unable to authenticate, need: BASIC realm=<span class="string">&quot;Sonatype Nexus Repository Manager&quot;</span></span><br><span class="line"></span><br><span class="line">npm ERR! A complete <span class="built_in">log</span> of this run can be found <span class="keyword">in</span>:</span><br><span class="line">npm ERR!     C:\Users\Administrator\AppData\Roaming\npm-cache\_logs\XXX-debug.log</span><br></pre></td></tr></table></figure><p>这时在我的mac上可以正常登录，说明是客户端的问题。</p><p>原因可能是本机使用过其他账号登录过，但是没有退出登录。果然在npm的配置文件 <code>.npmrc</code> 文件中找到了问题。</p><p>解决方案，检查 <code>~/.npmrc</code> 文件，删除 <code>xlab-npm-group</code> 和 <code>xlab-npm-private</code> 相关的token记录：</p><figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">registry</span>=<span class="string">http://**********:20001/repository/xlab-npm-group/</span></span><br><span class="line"><span class="attr">//**********</span>:<span class="string">20001/repository/xlab-npm-group/:_authToken=XXXX     // 删除这行</span></span><br><span class="line"><span class="attr">//**********</span>:<span class="string">20001/repository/xlab-npm-group/:_authToken=XXXX        // 删除这行</span></span><br><span class="line"><span class="attr">always-auth</span>=<span class="string">true</span></span><br><span class="line"><span class="attr">home</span>=<span class="string">https://www.npmjs.org</span></span><br><span class="line"><span class="attr">//**********</span>:<span class="string">20001/repository/xlab-npm-private/:_authToken=XXXX  // 删除这行</span></span><br></pre></td></tr></table></figure><p>修改后如下：</p><figure class="highlight properties"><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">registry</span>=<span class="string">http://**********:20001/repository/xlab-npm-group/</span></span><br><span class="line"><span class="attr">always-auth</span>=<span class="string">true</span></span><br><span class="line"><span class="attr">home</span>=<span class="string">https://www.npmjs.org</span></span><br></pre></td></tr></table></figure><p>保存后重新登录成功。</p><h2 id="登录报错npm-ERR-code-E500"><a href="#登录报错npm-ERR-code-E500" class="headerlink" title="登录报错npm ERR! code E500"></a>登录报错npm ERR! code E500</h2><p>npm登录时报错：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">$ npm login --registry=http://**********:20001/repository/xlab-npm-private/</span><br><span class="line">Username: 你的nexus用户名</span><br><span class="line">Password: 你的nexus用户密码</span><br><span class="line">Email: (this IS public) 你的邮箱</span><br><span class="line">npm ERR! code E500</span><br><span class="line">npm ERR! 500 Server Error -PUT =http://**********:20001/repository/xlab-npm-private/-/user/org.couchdb.user:XXX</span><br><span class="line"></span><br><span class="line">npm ERR! A complete <span class="built_in">log</span> of this run can be found <span class="keyword">in</span>:</span><br><span class="line">npm ERR!  /Users/XXX/.npm/_logs/XXX-debug.log</span><br></pre></td></tr></table></figure><p>这时在另外的机器登录也是同样错误，说明是可能是服务器问题。</p><p>解决方案，通知管理员检查服务器是否正常。管理员检查后发现服务器OOM了：</p><p><img src="https://hzzlyxx.oss-cn-beijing.aliyuncs.com/blog/npm/npm-error.png" alt="npm-error"></p><p>重启服务器容器后，重新登录成功。</p>]]></content>
    
    
      
      
    <summary type="html">&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;p&gt;在工作中，我们常常会将一些通用性代码，比如一些工具类、公用业务逻辑代码以及团队定制化的UI库等，发布到私有npm仓库。需要的项目直接安装使用</summary>
      
    
    
    
    
    <category term="随笔" scheme="http://yoursite.com/tags/%E9%9A%8F%E7%AC%94/"/>
    
    <category term="异常" scheme="http://yoursite.com/tags/%E5%BC%82%E5%B8%B8/"/>
    
  </entry>
  
  <entry>
    <title>flutter初探</title>
    <link href="http://yoursite.com/2019/12/19/flutter%E5%88%9D%E6%8E%A2/"/>
    <id>http://yoursite.com/2019/12/19/flutter%E5%88%9D%E6%8E%A2/</id>
    <published>2019-12-19T01:59:31.000Z</published>
    <updated>2019-12-19T02:11:07.369Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>忙完公司的工作之后，终于能腾出点时间来学习了，除了将基础和源码的学习补回来，还利用闲余的时间玩了一下flutter。刚开始我觉得Flutter的布局嵌套语法很恶心，内心是及其排斥的，但是在学了之后，我只想说：“真香！”</p><h2 id="移动开发的现状"><a href="#移动开发的现状" class="headerlink" title="移动开发的现状"></a>移动开发的现状</h2><p>随着移动互联网的高速发展，移动App的开发模式也在快速更迭中发展。<br>最初，为了能够在不同系统环境上运行，通常要求开发团队进行多平台并行开发。通常，开发Android和iOS App一共需要两个开发团队，维护两套源代码，分别进行测试。</p><p>后来，开发者们逐渐意识到，这样的开发效率并不高，成本却不低。因此诞生了一个接一个的跨平台解决方案。比如react-native、weex、cordova、ionic等等。但无一例外地，它们都无法摆脱低性能的JavaScript或者原生代码依赖，或多或少地存在不足。<br>所以急需一个真正能够打通多平台且高性能的框架来“救场”，Flutter则应运而生。</p><h2 id="认识-Flutter"><a href="#认识-Flutter" class="headerlink" title="认识 Flutter"></a>认识 Flutter</h2><p>Flutter是谷歌的移动UI框架，可以快速在iOS和Android上构建高质量的原生用户界面。作为一种全新的响应式，跨平台，高性能的移动开发框架。从开源以来，已经得到越来越多开发者的喜爱。其中闲鱼、腾讯、美团、字节跳动等大厂都有自己成熟的团队并有深度实践。赶快学起来！</p><h3 id="Flutter-特性"><a href="#Flutter-特性" class="headerlink" title="Flutter 特性"></a>Flutter 特性</h3><p>那么，Flutter 究竟有哪些特性吸引着这么多开发者的喜爱呢呢？</p><ol><li>热重载（Hot Reload）：有热重载真的太舒服了，可以帮助开发者更高效地进行开发和测试，更利于修复Bug，就这一点比原生安卓制作简直不知道高到哪里去了。</li><li>统一的应用开发体验：Flutter拥有丰富的库，帮助开发者快速实现项目需求。同时，大部分的工具和库同时支持Android和iOS；</li><li>界面生动：Flutter支持跨平台开发，同样支持Material Design（原生Android设计语言）和Cupertino（原生iOS设计语言）风格的控件。开发者可根据设计需要实现不同风格的UI界面；</li><li>原生性能：无论在Android还是iOS环境中，Flutter可以提供与原生应用一样的性能，甚至支持120 HZ的高刷新率；</li><li>响应式框架：Flutter支持响应式框架，在某些场景下，开发者无需付出任何代价，即可完成不同屏幕的适配，使UI的构建更加轻松；</li><li>混合开发：Flutter可以与平台原生代码相结合，支持较新的Kotlin和Swift开发语言。借助该特性，可以轻松访问Android或iOS上的原生系统功能和系统API。</li></ol><h3 id="Flutter-核心思想"><a href="#Flutter-核心思想" class="headerlink" title="Flutter 核心思想"></a>Flutter 核心思想</h3><p><strong>一切都是控件（Widget）(Everything’s a Widget)</strong></p><p>在Flutter的世界里，包括views,view controllers,layouts等在内的概念都建立在Widget之上。widget是flutter功能的抽象描述。</p><p>也就是说，在Flutter中，一个应用就是有许许多多的Widget组合而成的。</p><h3 id="Flutter-分层架构"><a href="#Flutter-分层架构" class="headerlink" title="Flutter 分层架构"></a>Flutter 分层架构</h3><p><img src="https://hzzlyxx.oss-cn-beijing.aliyuncs.com/blog/flutter/flutter_1.png" alt="flutter_1"></p><p>从flutter的架构图中不难看出widget是整个视图描述的基础。</p><h4 id="Flutter-Framework"><a href="#Flutter-Framework" class="headerlink" title="Flutter Framework"></a>Flutter Framework</h4><p>这是一个纯 Dart实现的 SDK，它实现了一套基础库，自底向上，我们来简单介绍一下：</p><ul><li><p>底下两层（Foundation和Animation、Painting、Gestures）在Google的一些视频中被合并为一个dart UI层，对应的是Flutter中的dart:ui包，它是Flutter引擎暴露的底层UI库，提供动画、手势及绘制能力。</p></li><li><p>Rendering层，这一层是一个抽象的布局层，它依赖于dart UI层，Rendering层会构建一个UI树，当UI树有变化时，会计算出有变化的部分，然后更新UI树，最终将UI树绘制到屏幕上，这个过程类似于React中的虚拟DOM。Rendering层可以说是Flutter UI框架最核心的部分，它除了确定每个UI元素的位置、大小之外还要进行坐标变换、绘制(调用底层dart:ui)。</p></li><li><p>Widgets层是Flutter提供的的一套基础组件库，在基础组件库之上，Flutter还提供了 Material 和Cupertino两种视觉风格的组件库。而我们Flutter开发的大多数场景，只是和这两层打交道。</p></li></ul><h4 id="Flutter-Engine"><a href="#Flutter-Engine" class="headerlink" title="Flutter Engine"></a>Flutter Engine</h4><p>这是一个纯 C++实现的 SDK，其中包括了 Skia引擎、Dart运行时、文字排版引擎等。在代码调用 dart:ui库时，调用最终会走到Engine层，然后实现真正的绘制逻辑。</p><p>聊完架构接下来我们聊一聊flutter的生命周期。</p><h3 id="Flutter-生命周期"><a href="#Flutter-生命周期" class="headerlink" title="Flutter 生命周期"></a>Flutter 生命周期</h3><p>Flutter的生命周期主要包括两大部分：state和App。</p><h4 id="state-生命周期"><a href="#state-生命周期" class="headerlink" title="state 生命周期"></a>state 生命周期</h4><p>widget是immutable的，发生变化的时候需要重建，所以谈不上状态。StatefulWidget 中的状态保持其实是通过State类来实现的。State拥有一套自己的生命周期：</p><table><thead><tr><th>名称</th><th align="left">状态</th></tr></thead><tbody><tr><td>initState</td><td align="left">插入渲染树时调用，只调用一次</td></tr><tr><td>didChangeDependencies</td><td align="left">state依赖的对象发生变化时调用</td></tr><tr><td>didUpdateWidget</td><td align="left">组件状态改变时候调用，可能会调用多次</td></tr><tr><td>build</td><td align="left">构建Widget时调用</td></tr><tr><td>deactivate</td><td align="left">当移除渲染树的时候调用</td></tr><tr><td>dispose</td><td align="left">组件即将销毁时调用</td></tr></tbody></table><p>生命周期状态图如下：</p><p><img src="https://hzzlyxx.oss-cn-beijing.aliyuncs.com/blog/flutter/flutter_2.png" alt="flutter_2"></p><p>注意：</p><blockquote><ul><li><p>didChangeDependencies有两种情况会被调用。</p><ul><li>创建时候在initState 之后被调用</li><li>在依赖的InheritedWidget发生变化的时候会被调用</li></ul></li><li><p>正常的退出流程中会执行deactivate然后执行dispose。但是也会出现deactivate以后不执行dispose，直接加入树中的另一个节点的情况。</p></li><li><p>这里的状态改变包括两种可能：1.通过setState内容改变 2.父节点的state状态改变，导致孩子节点的同步变化。</p></li></ul></blockquote><h4 id="App生命周期"><a href="#App生命周期" class="headerlink" title="App生命周期"></a>App生命周期</h4><p>如果想要知道App的生命周期,那么需要通过WidgetsBindingObserver的didChangeAppLifecycleState 来获取。通过该接口可以获取是生命周期在AppLifecycleState类中。常用状态包含如下几个：</p><table><thead><tr><th>名称</th><th align="left">状态</th></tr></thead><tbody><tr><td>resumed</td><td align="left">可见并能相应用户的输入</td></tr><tr><td>inactive</td><td align="left">处在并不活动状态，无法处理用户相应</td></tr><tr><td>paused</td><td align="left">不可见并不能相应用户的输入，但是在后台继续活动中</td></tr></tbody></table><p>一个实际场景中的例子：<br>在不考虑suspending的情况下：从后台切入前台生命周期变化如下:</p><blockquote><p>AppLifecycleState.inactive -&gt; AppLifecycleState.resumed;</p></blockquote><p>从前台压后台生命周期变化如下：</p><blockquote><p>AppLifecycleState.inactive -&gt; AppLifecycleState.paused;</p></blockquote><h2 id="Flutter-环境搭建"><a href="#Flutter-环境搭建" class="headerlink" title="Flutter 环境搭建"></a>Flutter 环境搭建</h2><p>工欲善其事必先利其器，环境搭建可以参考 <a href="https://book.flutterchina.club/chapter1/install_flutter.html">Flutter实战</a> 一步一步来搭建。</p><h2 id="Flutter-实践"><a href="#Flutter-实践" class="headerlink" title="Flutter 实践"></a>Flutter 实践</h2><p>经过一个多礼拜的折腾，自己也尝试写了几个小demo：</p><table><thead><tr><th align="center"><img src="https://hzzlyxx.oss-cn-beijing.aliyuncs.com/blog/flutter/flutter_3.png" alt="示范图片1"></th><th align="center"><img src="https://hzzlyxx.oss-cn-beijing.aliyuncs.com/blog/flutter/flutter_4.png" alt="示范图片1"></th><th align="center"><img src="https://hzzlyxx.oss-cn-beijing.aliyuncs.com/blog/flutter/flutter_5.png" alt="示范图片3"></th><th align="center"><img src="https://hzzlyxx.oss-cn-beijing.aliyuncs.com/blog/flutter/flutter_6.png" alt="示范图片4"></th></tr></thead></table><h2 id="优秀学习资料"><a href="#优秀学习资料" class="headerlink" title="优秀学习资料"></a>优秀学习资料</h2><ul><li><a href="https://flutterchina.club/">Flutter中文网</a></li><li><a href="https://flutterworld.site/cn/">Flutter World</a></li><li><a href="https://jspang.com/detailed?id=41">Flutter免费视频-技术胖</a></li><li><a href="https://book.flutterchina.club/">《Flutter 实战》</a></li><li><a href="https://juejin.im/user/5ac2db47f265da2393774122/posts">闲鱼技术团队</a></li></ul><h2 id="拓展"><a href="#拓展" class="headerlink" title="拓展"></a>拓展</h2><ul><li><a href="https://juejin.im/post/5b9606055188255c7c6541c3">Flutter和RN谁才是更好的跨端开发方案</a></li></ul><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://book.flutterchina.club/">《Flutter 实战》</a></li><li><a href="https://juejin.im/user/5ac2db47f265da2393774122/posts">闲鱼技术团队</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&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;p&gt;忙完公司的工作之后，终于能腾出点时间来学习了，除了将基础和源码的学习补回来，还利用闲余的时间玩了一下flutter。刚开始我觉得Flutte</summary>
      
    
    
    
    
    <category term="随笔" scheme="http://yoursite.com/tags/%E9%9A%8F%E7%AC%94/"/>
    
    <category term="flutter" scheme="http://yoursite.com/tags/flutter/"/>
    
  </entry>
  
  <entry>
    <title>flex: 1 详解</title>
    <link href="http://yoursite.com/2019/12/11/flex-1%E8%AF%A6%E8%A7%A3/"/>
    <id>http://yoursite.com/2019/12/11/flex-1%E8%AF%A6%E8%A7%A3/</id>
    <published>2019-12-11T09:17:34.000Z</published>
    <updated>2020-01-21T06:38:27.070Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>前段时间被头条hr从库里捞起来了(去年面到三面打入冷宫)，于是就接受了面试邀请，心想进不了也可以查缺补漏自己的不足。</p><p>其中就问了一道关于flex的问题：</p><figure class="highlight md"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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="code">`flex: 1`</span> 是哪些属性的缩写？</span><br><span class="line"> </span><br><span class="line">我：对应三个属性(flex-grow|flex-shrink|flex-basis)。</span><br><span class="line"> </span><br><span class="line">面试官：<span class="code">`1`</span> 对应哪个属性，另外两个属性的默认值是多少？</span><br><span class="line"> </span><br><span class="line">我：当 flex 取值为一个非负数字时，则该数字为 flex-grow 值，flex-shrink 默认为 1，flex-basis 默认 0%。心想幸好当时学的时候看到过。</span><br><span class="line"> </span><br><span class="line">面试官：这三个属性分别表示的什么意思，剩余空间和溢出空间的概念有了解过吗？</span><br><span class="line"> </span><br><span class="line">我：。。。不知道</span><br><span class="line"> </span><br><span class="line">面试官：没关系，我们下一个问题。</span><br><span class="line"> </span><br><span class="line">总结：果然很头条(一个问题问到你不知道为止)。</span><br></pre></td></tr></table></figure><p>接下来我就来对flex对应的三个属性做个详解。</p><p>首先明确一点是，flex 属性是 flex-grow, flex-shrink 和 flex-basis 的简写，默认值为 0 1 auto。</p><h2 id="flex-grow"><a href="#flex-grow" class="headerlink" title="flex-grow"></a>flex-grow</h2><p>传统的布局是子容器在父容器中从左到右进行布局，应用 flex 进行布局，那么父容器一定设置 <code>display: flex</code>，子容器要“占有”并且“瓜分”父容器的空间，如何占有、瓜分的策略就是弹性布局的策略。这里就要解释到“剩余空间”的概念：</p><blockquote><p>子容器在父容器的“主轴”上还有多少空间可以“瓜分”，这个可以被“瓜分”的空间就叫做剩余空间。</p></blockquote><p><code>flex-grow</code> 属性定义弹性盒子项（flex-item）的放大比例(定义子容器的瓜分剩余空间的比例)，默认为0，即如果存在剩余空间，也不放大(不会去瓜分)。</p><h2 id="flex-shrink"><a href="#flex-shrink" class="headerlink" title="flex-shrink"></a>flex-shrink</h2><p>如果子容器宽度超过父容器宽度，即使是设置了 flex-grow，但是由于没有剩余空间，就分配不到剩余空间了。这时候有两个办法：换行和压缩。由于 flex 默认不换行，那么压缩的话，怎么压缩呢，压缩多少？此时就需要用到 <code>flex-shrink</code> 属性了。</p><p><code>flex-shrink</code> 属性定义了弹性盒子项（flex-item）的缩小比例，默认为1，即如果空间不足，该子容器将缩小。</p><p>如果所有子项的 <code>flex-shrink</code> 属性都为1，当空间不足时，都将等比例缩小。如果一个子项的 <code>flex-shrink</code> 属性为0，其他子项都为1，则空间不足时，前者不缩小。</p><p>此时，剩余空间的概念就转化成了“溢出空间”。</p><p>注意：</p><ul><li>负值对该属性无效。</li><li>如果子容器没有超出父容器，设置 flex-shrink 无效</li></ul><h2 id="flex-basis"><a href="#flex-basis" class="headerlink" title="flex-basis"></a>flex-basis</h2><p><code>flex-basis</code> 属性定义了在分配多余空间之前，子项占据的主轴空间（main size）。浏览器根据这个属性，计算主轴是否有多余空间。它的默认值为auto，即项目的本来大小。</p><p>它可以设为跟 width 或 height 属性一样的值（比如350px），则子项将占据固定空间。既然是跟宽度相关，那么 max-width，min-width，width 和 flex-basis 的大小优先级是怎么样的。</p><blockquote><p>max-width&#x2F;min-width &gt; flex-basis &gt; width</p></blockquote><p>理解完了这三个属性接下来就看一些我们平时常写的一些简写表示的意义。</p><h2 id="flex-简写"><a href="#flex-简写" class="headerlink" title="flex 简写"></a>flex 简写</h2><p>flex 的默认值是以上三个属性值的组合。假设以上三个属性同样取默认值，则 flex 的默认值是 0 1 auto。</p><h3 id="flex-取三个值"><a href="#flex-取三个值" class="headerlink" title="flex 取三个值"></a>flex 取三个值</h3><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.item</span> &#123; <span class="attribute">flex</span>: <span class="number">2</span> <span class="number">1</span> <span class="number">200px</span>; &#125;</span><br><span class="line">// 等同于</span><br><span class="line"><span class="selector-class">.item</span> &#123;</span><br><span class="line">  <span class="attribute">flex-grow</span>: <span class="number">2</span>;</span><br><span class="line">  <span class="attribute">flex-shrink</span>: <span class="number">1</span>;</span><br><span class="line">  <span class="attribute">flex-basis</span>: <span class="number">200px</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="flex-取值为-none"><a href="#flex-取值为-none" class="headerlink" title="flex 取值为 none"></a>flex 取值为 none</h3><p>当 flex 取值为 none，则计算值为 0 0 auto。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.item</span> &#123; <span class="attribute">flex</span>: none; &#125;</span><br><span class="line">// 等同于</span><br><span class="line"><span class="selector-class">.item</span> &#123;</span><br><span class="line">  <span class="attribute">flex-grow</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">flex-shrink</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">flex-basis</span>: auto;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="flex-取值为-auto"><a href="#flex-取值为-auto" class="headerlink" title="flex 取值为 auto"></a>flex 取值为 auto</h3><p>当 flex 取值为 auto，则计算值为 1 1 auto。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.item</span> &#123; <span class="attribute">flex</span>: auto; &#125;</span><br><span class="line">// 等同于</span><br><span class="line"><span class="selector-class">.item</span> &#123;</span><br><span class="line">  <span class="attribute">flex-grow</span>: <span class="number">1</span>;</span><br><span class="line">  <span class="attribute">flex-shrink</span>: <span class="number">1</span>;</span><br><span class="line">  <span class="attribute">flex-basis</span>: auto;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="flex-取值为-一个非负数"><a href="#flex-取值为-一个非负数" class="headerlink" title="flex 取值为 一个非负数"></a>flex 取值为 一个非负数</h3><p>当 flex 取值为一个非负数字，则该数字为 flex-grow 值，flex-shrink 取 1，flex-basis 取 0%。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.item</span> &#123; <span class="attribute">flex</span>: <span class="number">1</span>; &#125;</span><br><span class="line">// 等同于</span><br><span class="line"><span class="selector-class">.item</span> &#123;</span><br><span class="line">  <span class="attribute">flex-grow</span>: <span class="number">1</span>;</span><br><span class="line">  <span class="attribute">flex-shrink</span>: <span class="number">1</span>;</span><br><span class="line">  <span class="attribute">flex-basis</span>: <span class="number">0%</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="flex-取值为-长度或百分比"><a href="#flex-取值为-长度或百分比" class="headerlink" title="flex 取值为 长度或百分比"></a>flex 取值为 长度或百分比</h3><p>当 flex 取值为一个长度或百分比，则视为 flex-basis 值，flex-grow 取 1，flex-shrink 取 1。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.item</span> &#123; <span class="attribute">flex</span>: <span class="number">0%</span>; &#125;</span><br><span class="line">// 等同于</span><br><span class="line"><span class="selector-class">.item</span> &#123;</span><br><span class="line">  <span class="attribute">flex-grow</span>: <span class="number">1</span>;</span><br><span class="line">  <span class="attribute">flex-shrink</span>: <span class="number">1</span>;</span><br><span class="line">  <span class="attribute">flex-basis</span>: <span class="number">0%</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.item</span> &#123; <span class="attribute">flex</span>: <span class="number">20px</span>; &#125;</span><br><span class="line">// 等同于</span><br><span class="line"><span class="selector-class">.item</span> &#123;</span><br><span class="line">  <span class="attribute">flex-grow</span>: <span class="number">1</span>;</span><br><span class="line">  <span class="attribute">flex-shrink</span>: <span class="number">1</span>;</span><br><span class="line">  <span class="attribute">flex-basis</span>: <span class="number">20px</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="flex-取值为-两个非负数字"><a href="#flex-取值为-两个非负数字" class="headerlink" title="flex 取值为 两个非负数字"></a>flex 取值为 两个非负数字</h3><p>当 flex 取值为两个非负数字，则分别视为 flex-grow 和 flex-shrink 的值，flex-basis 取 0%。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.item</span> &#123; <span class="attribute">flex</span>: <span class="number">1</span> <span class="number">2</span>; &#125;</span><br><span class="line">// 等同于</span><br><span class="line"><span class="selector-class">.item</span> &#123;</span><br><span class="line">  <span class="attribute">flex-grow</span>: <span class="number">1</span>;</span><br><span class="line">  <span class="attribute">flex-shrink</span>: <span class="number">2</span>;</span><br><span class="line">  <span class="attribute">flex-basis</span>: <span class="number">0%</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="flex-取值为-一个非负数字和一个长度或百分比"><a href="#flex-取值为-一个非负数字和一个长度或百分比" class="headerlink" title="flex 取值为 一个非负数字和一个长度或百分比"></a>flex 取值为 一个非负数字和一个长度或百分比</h3><p>当 flex 取值为一个非负数字和一个长度或百分比，则分别视为 flex-grow 和 flex-basis 的值，flex-shrink 取 1。</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.item</span> &#123; <span class="attribute">flex</span>: <span class="number">2</span> <span class="number">20px</span>; &#125;</span><br><span class="line">// 等同于</span><br><span class="line"><span class="selector-class">.item</span> &#123;</span><br><span class="line">  <span class="attribute">flex-grow</span>: <span class="number">2</span>;</span><br><span class="line">  <span class="attribute">flex-shrink</span>: <span class="number">1</span>;</span><br><span class="line">  <span class="attribute">flex-basis</span>: <span class="number">20px</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>最后我们来用一个例子来计算子项的宽度。</p><h2 id="栗子🌰"><a href="#栗子🌰" class="headerlink" title="栗子🌰"></a>栗子🌰</h2><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;parent&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;item1&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;item2&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;item3&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"> </span><br><span class="line"><span class="tag">&lt;<span class="name">style</span> <span class="attr">type</span>=<span class="string">&quot;text/css&quot;</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.parent</span> &#123;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">display</span>: flex;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">width</span>: <span class="number">600px</span>;</span></span><br><span class="line"><span class="language-css">  &#125;</span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.parent</span> &gt; <span class="selector-tag">div</span> &#123;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">height</span>: <span class="number">100px</span>;</span></span><br><span class="line"><span class="language-css">  &#125;</span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.item1</span> &#123;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">width</span>: <span class="number">140px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">flex</span>: <span class="number">2</span> <span class="number">1</span> <span class="number">0%</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">background</span>: blue;</span></span><br><span class="line"><span class="language-css">  &#125;</span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.item2</span> &#123;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">width</span>: <span class="number">100px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">flex</span>: <span class="number">2</span> <span class="number">1</span> auto;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">background</span>: darkblue;</span></span><br><span class="line"><span class="language-css">  &#125;</span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.item3</span> &#123;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">flex</span>: <span class="number">1</span> <span class="number">1</span> <span class="number">200px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">background</span>: lightblue;</span></span><br><span class="line"><span class="language-css">  &#125;</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br></pre></td></tr></table></figure><blockquote><p>当 item-1 的 flex-basis 取 0% 的时候，是把该项目视为零尺寸的，故即便声明其尺寸为 140px，也并没有什么用，形同虚设<br>而 item-2 的 flex-basis 取 auto 的时候，根据规则基准值使用值是主尺寸值即 100px，故这 100px 不会纳入剩余空间</p></blockquote><p>主轴上父容器总尺寸：600px</p><p>剩余空间：<br>600px - 100px(item2的flex-basis值) - 200px(item3的flex-basis值) &#x3D; 300px</p><p>伸缩放大系数之和(flex-grow值)：<br>2(item1) + 2(item2) + 1(item3) &#x3D; 5</p><p>每一份比例所占宽度：300 &#x2F; 5 &#x3D; 60px;</p><p>剩余空间分配如下：</p><ul><li>item1 和 item2 各分配 2&#x2F;5，各得 120px</li><li>item3 分配 1&#x2F;5，得 60px</li></ul><p>各子项最终宽度为：</p><ul><li>item1 &#x3D; 0%(0px) + 120px &#x3D; 120px</li><li>item2 &#x3D; auto(100px) + 120px &#x3D; 220px</li><li>item3 &#x3D; 200px + 60px &#x3D; 260px</li></ul><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://www.ruanyifeng.com/blog/2015/07/flex-grammar.html">Flex 布局教程：语法篇</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&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;p&gt;前段时间被头条hr从库里捞起来了(去年面到三面打入冷宫)，于是就接受了面试邀请，心想进不了也可以查缺补漏自己的不足。&lt;/p&gt;
&lt;p&gt;其中就问</summary>
      
    
    
    
    
    <category term="随笔" scheme="http://yoursite.com/tags/%E9%9A%8F%E7%AC%94/"/>
    
    <category term="flex" scheme="http://yoursite.com/tags/flex/"/>
    
  </entry>
  
  <entry>
    <title>React数据大屏的应用实践</title>
    <link href="http://yoursite.com/2019/09/24/React%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B1%8F%E7%9A%84%E5%BA%94%E7%94%A8%E5%AE%9E%E8%B7%B5/"/>
    <id>http://yoursite.com/2019/09/24/React%E6%95%B0%E6%8D%AE%E5%A4%A7%E5%B1%8F%E7%9A%84%E5%BA%94%E7%94%A8%E5%AE%9E%E8%B7%B5/</id>
    <published>2019-09-23T17:29:17.000Z</published>
    <updated>2019-11-06T06:08:07.653Z</updated>
    
    <content type="html"><![CDATA[<h2 id="数据大屏与数据可视化"><a href="#数据大屏与数据可视化" class="headerlink" title="数据大屏与数据可视化"></a>数据大屏与数据可视化</h2><p>现如今大数据已无所不在，并且正被越来越广泛的被应用到历史、政治、科学、经济、商业甚至渗透到我们生活的方方面面中，获取的渠道也越来越便利。</p><p>今天我们就来聊一聊“大屏应用”，说到大屏就一定要聊到数据可视化，现如今，数据可视化由于数据分析的火热也变得火热起来，不过数据可视化并不是一个新技术，可视化数据就是用可视化的方式展现的数据。而数据大屏作为大数据展示媒介的一种，广泛运用于各种展示厅、会展、发布会及各种狂欢节中，其中不乏一些通用的处理方案：阿里的DataV、百度的Suger、腾讯RayData等等。</p><p>随着物联网、5G等各种跟连接有关的技术的出现与发展，每个人手中掌握的数据量都呈指数级增长，光看这些数是看不过来也看不懂的，“数据可视化”就是一种简化，让艰难的数据理解过程，变成——看颜色，辨长短，分高低。从而大大缩短理解数据所需的时间。</p><p>因公司的自研产品涉及到BI模块，因此数据大屏展示的需求孕育而生（数据大屏需求已经完成）。</p><p>下面是本人针对这个数据大屏需求前期做的一些探索实践，数据也是mock的。</p><p><img src="https://hzzlyxx.oss-cn-beijing.aliyuncs.com/blog/bi/img2.gif" alt="bi"></p><h2 id="技术选型"><a href="#技术选型" class="headerlink" title="技术选型"></a>技术选型</h2><ul><li>React 全家桶（React-Router、React-Redux、React Hooks）</li><li>Webpack 编译打包</li><li>Echarts 图表组件</li><li>Socket.IO 即时通讯、通知与消息推送</li><li>Grid 网格布局</li></ul><h2 id="系统搭建"><a href="#系统搭建" class="headerlink" title="系统搭建"></a>系统搭建</h2><h3 id="图表选择"><a href="#图表选择" class="headerlink" title="图表选择"></a>图表选择</h3><p>六种基本图表涵盖了大部分图表使用场景，也是做数据可视化最常用的图表类型：</p><ul><li><strong>柱状图</strong> 用来反映分类项目之间的比较；</li><li><strong>饼图</strong> 用来反映构成，即部分占总体的比例；</li><li><strong>折线图</strong> 用来反映随时间变化的趋势；</li><li><strong>条形图</strong> 用来反映分类项目之间的比较；</li><li><strong>散点图</strong> 用来反映相关性或分布关系；</li><li><strong>地图</strong> 用来反映区域之间的分类比较。</li></ul><p>基本图表类型都有通用的样式，不过多的展开讲解。我们更多的考虑如何选择常用图表来呈现数据，达到数据可视化的目标。基本方法：<strong>明确目标</strong> —&gt; <strong>选择图形</strong> —&gt; <strong>梳理维度</strong> —&gt; <strong>突出关键信息</strong>。</p><h3 id="数据请求推送"><a href="#数据请求推送" class="headerlink" title="数据请求推送"></a>数据请求推送</h3><p>当信息一旦准备就绪，我们就需要从服务器获取它们。这里我们需要一种基于推送的方法，例如 WebSocket 协议、轮询、服务器推送事件（SSE）以及最近的 HTTP2 服务器推送。这里我们简单比较一下 WebSocket 与轮询。</p><p>轮询需要客户端定时向服务器发送ajax请求，服务器接到请求后返回响应信息。这就需要大量的占据服务器资源。同时在HTTP1.x协议中也存在一些比如线头阻塞、头部冗余等问题。所以这种方案直接pass了。</p><p>再来说说 WebSocket，建立在 TCP 协议之上，数据格式比较轻量，性能开销小，通信高效，可以发送文本，也可以发送二进制数据。同时它还没有同源限制，客户端可以与任意服务器通信。还有一点 WebSocket 通常不使用 XMLHttpRequest，因此，当我们每次需要从服务器获取更多的信息时，无需发送头部数据。反过来说，这又减少了数据发送到服务器时需要付出的高昂的数据负载代价。对于数据大屏需要实时获取数据，这无疑是最高效的。</p><h3 id="布局"><a href="#布局" class="headerlink" title="布局"></a>布局</h3><p>数据大屏的核心就是数据的拼接，具体到展示层可以归纳成数据块的拼接。这里我们采用通用的尺寸1920*108(16:9)。尺寸确立后，接下来要对展示层进行布局和页面的划分。这里的划分，主要根据我们之前定好的业务指标进行，核心业务指标安排在中间位置、占较大面积；其余的指标按优先级依次在核心指标周围展开。一般把有关联的指标让其相邻或靠近，把图表类型相近的指标放一起，这样能减少观者认知上的负担并提高信息传递的效率。</p><p>对于这种块状(网格)布局，我们就可以使用我们强大的 CSS 布局方案 – <strong>Grid</strong>。它将网页划分成一个个网格，可以任意组合不同的网格，做出各种各样的布局。</p><p>安利一个grid 布局可视化设计工具 – <a href="https://cssgrid-generator.netlify.com/">CSS Grid Generator</a>。可以使用它生成对应的代码，帮助咱们快速布局。</p><p><img src="https://hzzlyxx.oss-cn-beijing.aliyuncs.com/blog/bi/img2.png" alt="grid"></p><h3 id="项目结构"><a href="#项目结构" class="headerlink" title="项目结构"></a>项目结构</h3><p>聊完这些通用知识我们就可以上手开发了。</p><p>我这里使用了我自己开发的脚手架（hzzly-cli）来生成react项目环境。</p><blockquote><p>有兴趣了解脚手架开发的可以看我这篇文章<a href="http://hjingren.cn/2019/07/19/%E5%8A%A8%E6%89%8B%E5%BC%80%E5%8F%91%E4%B8%80%E4%B8%AA%E8%87%AA%E5%B7%B1%E7%9A%84%E9%A1%B9%E7%9B%AE%E8%84%9A%E6%89%8B%E6%9E%B6/">动手开发一个自己的项目脚手架</a></p></blockquote><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></pre></td><td class="code"><pre><span class="line">├── src</span><br><span class="line">│   ├── assets // 资源目录</span><br><span class="line">│   ├── components // 公共组件目录</span><br><span class="line">│   │   ├── Card // Card组件</span><br><span class="line">│   │   ├── Charts // 图表组件目录</span><br><span class="line">│   │   │   ├── Bar // 柱状图</span><br><span class="line">│   │   │   ├── ChinaMap // 中国地图</span><br><span class="line">│   │   │   ├── Funnel // 漏斗图</span><br><span class="line">│   │   │   ├── Line // 折线图</span><br><span class="line">│   │   │   ├── Pie // 饼图</span><br><span class="line">│   │   │   └── lib // 基础图表组件</span><br><span class="line">│   │   ├── ScrollNumber // 滚动数字组件</span><br><span class="line">│   │   └── SvgIcon // Icon组件</span><br><span class="line">│   ├── global.scss</span><br><span class="line">│   ├── index.js</span><br><span class="line">│   ├── pages // 分块结构目录</span><br><span class="line">│   ├── router // 路由</span><br><span class="line">│   ├── store</span><br><span class="line">│   │   ├── actions</span><br><span class="line">│   │   ├── index.js</span><br><span class="line">│   │   ├── reducers</span><br><span class="line">│   │   ├── sagas</span><br><span class="line">│   │   └── types.js</span><br><span class="line">│   └── utils</span><br><span class="line">│       ├── genChartData.js</span><br><span class="line">│       ├── genMapData.js</span><br><span class="line">│       ├── socket.js</span><br><span class="line">│       └── util.js</span><br></pre></td></tr></table></figure><h2 id="知识点"><a href="#知识点" class="headerlink" title="知识点"></a>知识点</h2><h3 id="Chart基础组件封装"><a href="#Chart基础组件封装" class="headerlink" title="Chart基础组件封装"></a>Chart基础组件封装</h3><p>这里对<code>echarts-for-react</code>进一步封装，其它图表组件可以直接继承使用。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Charts/lib/BaseChart.js</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">React</span>, &#123; <span class="title class_">PureComponent</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">PropTypes</span> <span class="keyword">from</span> <span class="string">&#x27;prop-types&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Echarts</span> <span class="keyword">from</span> <span class="string">&#x27;echarts-for-react&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">class</span> <span class="title class_">BaseChart</span> <span class="keyword">extends</span> <span class="title class_ inherited__">PureComponent</span> &#123;</span><br><span class="line">  <span class="keyword">static</span> propTypes = &#123;</span><br><span class="line">    <span class="attr">option</span>: <span class="title class_">PropTypes</span>.<span class="property">object</span>.<span class="property">isRequired</span>,</span><br><span class="line">    <span class="attr">data</span>: <span class="title class_">PropTypes</span>.<span class="property">object</span>.<span class="property">isRequired</span>,</span><br><span class="line">    <span class="attr">getOption</span>: <span class="title class_">PropTypes</span>.<span class="property">func</span>.<span class="property">isRequired</span>,</span><br><span class="line">    <span class="attr">style</span>: <span class="title class_">PropTypes</span>.<span class="property">object</span>,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">static</span> defaultProps = &#123;</span><br><span class="line">    <span class="attr">style</span>: &#123;&#125;,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">componentDidMount</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; runAction &#125; = <span class="variable language_">this</span>.<span class="property">props</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">chartRef</span> &amp;&amp; runAction) &#123;</span><br><span class="line">      <span class="keyword">const</span> chartIns = <span class="variable language_">this</span>.<span class="property">chartRef</span>.<span class="title function_">getEchartsInstance</span>();</span><br><span class="line">      <span class="variable language_">window</span>.<span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="title function_">runAction</span>(chartIns);</span><br><span class="line">      &#125;, <span class="number">300</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; option, data, getOption, style &#125; = <span class="variable language_">this</span>.<span class="property">props</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> finalOption = <span class="title function_">getOption</span>(option, data);</span><br><span class="line">    <span class="keyword">const</span> finalStyle = <span class="title function_">getStyle</span>(style);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;<span class="name">Echarts</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">ref</span>=<span class="string">&#123;ref</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-xml">          this.chartRef = ref;</span></span><br><span class="line"><span class="language-xml">        &#125;&#125;</span></span><br><span class="line"><span class="language-xml">        style=&#123;finalStyle&#125;</span></span><br><span class="line"><span class="language-xml">        option=&#123;finalOption&#125;</span></span><br><span class="line"><span class="language-xml">        notMerge</span></span><br><span class="line"><span class="language-xml">        lazyUpdate</span></span><br><span class="line"><span class="language-xml">      /&gt;</span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">getStyle</span>(<span class="params">style</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="title class_">Object</span>.<span class="title function_">assign</span>(&#123; <span class="attr">position</span>: <span class="string">&#x27;relative&#x27;</span> &#125;,</span><br><span class="line">    style</span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// line.js</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">BaseChart</span> <span class="keyword">from</span> <span class="string">&#x27;../lib/BaseChart&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> option <span class="keyword">from</span> <span class="string">&#x27;./option&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> getOption <span class="keyword">from</span> <span class="string">&#x27;./getOption&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">class</span> <span class="title class_">Line</span> <span class="keyword">extends</span> <span class="title class_ inherited__">BaseChart</span> &#123;</span><br><span class="line">  <span class="keyword">static</span> defaultProps = &#123;</span><br><span class="line">    option,</span><br><span class="line">    getOption,</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// option.js 基础配置</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// getOption.js 计算配置文件</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">seriesCreator</span>(<span class="params">series</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> series.<span class="title function_">map</span>(<span class="function"><span class="params">e</span> =&gt;</span> (&#123;</span><br><span class="line">    <span class="attr">type</span>: <span class="string">&#x27;line&#x27;</span>,</span><br><span class="line">    <span class="attr">symbol</span>: <span class="string">&#x27;circle&#x27;</span>,</span><br><span class="line">    <span class="attr">smooth</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">lineStyle</span>: &#123;</span><br><span class="line">      <span class="attr">normal</span>: &#123;</span><br><span class="line">        <span class="attr">width</span>: <span class="number">3</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">    ...e,</span><br><span class="line">  &#125;));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span>(<span class="params">option, data</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; tooltip, xAxis, yAxis, yCategory, series = [], ...rest &#125; = data;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    ...option,</span><br><span class="line">    <span class="attr">xAxis</span>: &#123;</span><br><span class="line">      ...option.<span class="property">xAxis</span>,</span><br><span class="line">      ...xAxis,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">tooltip</span>: &#123;</span><br><span class="line">      ...option.<span class="property">tooltip</span>,</span><br><span class="line">      ...tooltip,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">yAxis</span>: &#123;</span><br><span class="line">      ...option.<span class="property">yAxis</span>,</span><br><span class="line">      ...yAxis,</span><br><span class="line">      <span class="attr">data</span>: yCategory || [],</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">series</span>: <span class="title function_">seriesCreator</span>(series),</span><br><span class="line">    ...rest,</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Socket封装SDK"><a href="#Socket封装SDK" class="headerlink" title="Socket封装SDK"></a>Socket封装SDK</h3><p>这里对<code>socket.io-client</code>封装成SDK，方便使用。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> io <span class="keyword">from</span> <span class="string">&#x27;socket.io-client&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> socket = &#123;</span><br><span class="line">  <span class="attr">wsConn</span>: <span class="literal">null</span>,</span><br><span class="line">  <span class="attr">config</span>: &#123;</span><br><span class="line">    <span class="attr">wsHost</span>: <span class="string">&#x27;/&#x27;</span>, <span class="comment">// wesocket host</span></span><br><span class="line"></span><br><span class="line">    <span class="title function_">onConn</span>(<span class="params"></span>) &#123;&#125;,</span><br><span class="line">    <span class="title function_">onDisconn</span>(<span class="params"></span>) &#123;&#125;,</span><br><span class="line">    <span class="title function_">onError</span>(<span class="params"></span>) &#123;&#125;,</span><br><span class="line">    <span class="title function_">onReceiveMsg</span>(<span class="params"></span>) &#123;&#125;,</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="title function_">init</span>(<span class="params">opt</span>) &#123;</span><br><span class="line">    socket.<span class="property">config</span> = &#123; ...socket.<span class="property">config</span>, ...opt &#125;;</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="title function_">getWs</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (socket.<span class="property">wsConn</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> socket.<span class="property">wsConn</span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      socket.<span class="title function_">initWs</span>();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="title function_">getWsStatus</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> socket.<span class="property">wsConn</span> ? socket.<span class="property">wsConn</span>.<span class="property">connected</span> : <span class="literal">false</span>;</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="title function_">initWs</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (socket.<span class="title function_">getWsStatus</span>()) &#123;</span><br><span class="line">      <span class="keyword">return</span> socket.<span class="property">wsConn</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> wsUrl = socket.<span class="property">config</span>.<span class="property">wsHost</span>;</span><br><span class="line"></span><br><span class="line">    socket.<span class="property">wsConn</span> = io.<span class="title function_">connect</span>(wsUrl);</span><br><span class="line">    socket.<span class="property">wsConn</span>.<span class="title function_">on</span>(<span class="string">&#x27;connect&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      socket.<span class="property">config</span>.<span class="title function_">onConn</span>(socket.<span class="property">wsConn</span>);</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    socket.<span class="property">wsConn</span>.<span class="title function_">on</span>(<span class="string">&#x27;message&#x27;</span>, <span class="function">(<span class="params">...param</span>) =&gt;</span> &#123;</span><br><span class="line">      socket.<span class="property">config</span>.<span class="title function_">onReceiveMsg</span>(...param);</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    socket.<span class="property">wsConn</span>.<span class="title function_">on</span>(<span class="string">&#x27;disconnect&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      socket.<span class="property">config</span>.<span class="title function_">onDisconn</span>();</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">return</span> socket.<span class="property">wsConn</span>;</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="title function_">reconnect</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (socket.<span class="property">wsConn</span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (socket.<span class="property">wsConn</span>.<span class="property">disconnected</span>) &#123;</span><br><span class="line">        <span class="comment">// reconnect ws</span></span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// do nothing</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      socket.<span class="title function_">initWs</span>();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="title function_">disconnect</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (socket.<span class="property">wsConn</span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (socket.<span class="property">wsConn</span>.<span class="property">connected</span>) &#123;</span><br><span class="line">        socket.<span class="property">wsConn</span>.<span class="title function_">disconnect</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="title function_">wsEmit</span>(<span class="params">params</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (socket.<span class="property">wsConn</span>) &#123;</span><br><span class="line">      socket.<span class="property">wsConn</span>.<span class="title function_">emit</span>(params.<span class="property">name</span>, params.<span class="property">data</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">(<span class="keyword">function</span>(<span class="params"><span class="variable language_">global</span></span>) &#123;</span><br><span class="line">  <span class="variable language_">global</span>.<span class="property">socket</span> = socket;</span><br><span class="line">&#125;)(<span class="variable language_">window</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> &#123; socket &#125;;</span><br></pre></td></tr></table></figure><h3 id="动态数字展示"><a href="#动态数字展示" class="headerlink" title="动态数字展示"></a>动态数字展示</h3><p>该数据通过socket推送实时更新。</p><p>数字过渡的动态效果为对应数位的新数字从下至上替换旧数字，如果该位数的数字没有发生变化，则没有过渡效果。</p><p><code>1、对数据进行完善并格式化</code></p><p>针对数字少于9位数进行前位补零并进行千分位格式化</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">MAX_LEN</span> = <span class="number">9</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">toThousands</span>(<span class="params">val</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> num = (val || <span class="number">0</span>).<span class="title function_">toString</span>();</span><br><span class="line">  <span class="keyword">while</span> (num.<span class="property">length</span> &lt; <span class="variable constant_">MAX_LEN</span>) &#123;</span><br><span class="line">    num = <span class="string">`0<span class="subst">$&#123;num&#125;</span>`</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">let</span> result = <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">  <span class="keyword">while</span> (num.<span class="property">length</span> &gt; <span class="number">3</span>) &#123;</span><br><span class="line">    result = <span class="string">`,<span class="subst">$&#123;num.slice(-<span class="number">3</span>)&#125;</span><span class="subst">$&#123;result&#125;</span>`</span>;</span><br><span class="line">    num = num.<span class="title function_">slice</span>(<span class="number">0</span>, num.<span class="property">length</span> - <span class="number">3</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (num) &#123;</span><br><span class="line">    result = num + result;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> result.<span class="title function_">toString</span>().<span class="title function_">split</span>(<span class="string">&#x27;&#x27;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>2、过渡动画</code></p><p>利用样式控制过渡动画，在第一步中我们对数字进行了格式化，然后我们针对每一位数字进行比较，当数字不相等的时候添加<code>active</code>类，最后对<code>active</code>类添加动画。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 循环渲染每一位数字</span></span><br><span class="line">&lt;li className=&#123;<span class="string">`<span class="subst">$&#123;oldNumber[i] !== newNumber[i] ? <span class="string">&#x27;active&#x27;</span> : <span class="string">&#x27;&#x27;</span>&#125;</span>`</span>&#125;&gt;</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">span</span> <span class="attr">className</span>=<span class="string">&quot;num&quot;</span>&gt;</span>&#123;oldNumber[i]&#125;<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span></span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">span</span> <span class="attr">className</span>=<span class="string">&quot;num&quot;</span>&gt;</span>&#123;newNumber[i]&#125;<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span></span><br><span class="line">&lt;/li&gt;</span><br></pre></td></tr></table></figure><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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="selector-class">.active</span> &#123;</span><br><span class="line">  <span class="selector-class">.num</span> &#123;</span><br><span class="line">    <span class="attribute">animation</span>: move <span class="number">1.5s</span>;</span><br><span class="line">    <span class="attribute">animation-fill-mode</span>: forwards; // 让动画结束后保持最后一帧</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">@keyframes</span> move &#123;</span><br><span class="line">  <span class="selector-tag">from</span> &#123;</span><br><span class="line">    <span class="attribute">transform</span>: <span class="built_in">translateY</span>(<span class="number">0</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="selector-tag">to</span> &#123;</span><br><span class="line">    <span class="attribute">transform</span>: <span class="built_in">translateY</span>(-<span class="number">100%</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="背景线性粒子"><a href="#背景线性粒子" class="headerlink" title="背景线性粒子"></a>背景线性粒子</h3><p>这里我使用了我自己封装的组件，可以对应框架来安装引用：</p><ul><li><a href="https://github.com/hzzly/vue-particle-line">vue-particle-line</a></li><li><a href="https://github.com/hzzly/react-particle-line">react-particle-line</a></li></ul><h2 id="说明"><a href="#说明" class="headerlink" title="说明"></a>说明</h2><p>1、项目框架目录结构采用笔者自己搭建的webpack环境：<a href="https://github.com/hzzly/webpack-template">webpack-template</a></p><p>2、关于适配和兼容性暂时还未完善，如果后期有时间会慢慢去完善</p><p>3、此项目为笔者调研时的实践，因为时间有限，一些功能还不善，设计和布局都是自己的一些想象与参考</p><p>4、此项目作为开源学习使用，谢绝用于商业应用</p><h2 id="源码"><a href="#源码" class="headerlink" title="源码"></a>源码</h2><p>代码已上传至我的<a href="https://github.com/hzzly/credit-bi-react">GitHub</a>，欢迎 Star、Fork</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="http://www.woshipm.com/pd/1782868.html">超全面设计指南：如何做大屏数据可视化设计？</a></li><li><a href="http://www.woshipm.com/data-analysis/2279512.html">五个方面，聊聊大数据可视化的初体验</a></li><li><a href="https://juejin.im/post/5a20fe96f265da431120025b">一个炫酷大屏展示页的打造过程</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&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;p&gt;现如今大数据已无所不在，并且正被越来越广泛的被应用到历史、政治、科学、经济、</summary>
      
    
    
    
    
    <category term="随笔" scheme="http://yoursite.com/tags/%E9%9A%8F%E7%AC%94/"/>
    
    <category term="react" scheme="http://yoursite.com/tags/react/"/>
    
    <category term="大屏" scheme="http://yoursite.com/tags/%E5%A4%A7%E5%B1%8F/"/>
    
  </entry>
  
  <entry>
    <title>AST的实践</title>
    <link href="http://yoursite.com/2019/09/19/AST%E7%9A%84%E5%AE%9E%E8%B7%B5/"/>
    <id>http://yoursite.com/2019/09/19/AST%E7%9A%84%E5%AE%9E%E8%B7%B5/</id>
    <published>2019-09-19T02:04:36.000Z</published>
    <updated>2019-11-06T06:12:34.457Z</updated>
    
    <content type="html"><![CDATA[<h2 id="什么是AST（抽象语法树）"><a href="#什么是AST（抽象语法树）" class="headerlink" title="什么是AST（抽象语法树）?"></a>什么是AST（抽象语法树）?</h2><blockquote><p>It is a hierarchical program representation that presents source code structure according to the grammar of a programming language, each AST node corresponds to an item of a source code.</p><p>AST是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构，树上的每个节点都表示源代码中的一种结构。</p></blockquote><p>AST是一个非常基础但是同时非常重要的知识点，我们熟知的 TypeScript、babel、webpack、vue-cli 都是依赖 AST 进行开发的。</p><p>这里我们就以 babel 为例来实践一下 AST。</p><h2 id="Babel运行原理"><a href="#Babel运行原理" class="headerlink" title="Babel运行原理"></a>Babel运行原理</h2><p>Babel 作为当今最为常用的 JavaScript 编译器，在前端开发中扮演着极为重要的角色。大多数情况下，Babel 被用来转译 ECMAScript 2015+ 至可兼容浏览器的版本。</p><p>Babel 的三个主要处理步骤分别是：</p><ul><li>解析（parse）</li><li>转换（transform）</li><li>生成（generate）</li></ul><p><img src="https://hzzlyxx.oss-cn-beijing.aliyuncs.com/blog/ast/ast1.png" alt="Babel处理步骤"></p><p>整个过程中，parsing和generation是固定不变的，最关键的是transforming步骤，通过babel插件来支持，这是其扩展性的关键。</p><p>这三个阶段分别由 @babel&#x2F;parser、@babel&#x2F;core、@babel&#x2F;generator 执行。Babel 本质上只是一个代码的搬运工，如果不给 Babel 装上插件，它将会把输入的代码原封不动地输出。正是因为有插件的存在， Babel 才能将输入的代码进行转变，从而生成新的代码。</p><h3 id="解析"><a href="#解析" class="headerlink" title="解析"></a>解析</h3><p>输入JS源码，输出AST</p><p>parsing（解析），对应于编译器的词法分析，及语法分析阶段。输入的源码字符序列经过词法分析，生成具有词法意义的token序列（能够区分出关键字、数值、标点符号等），接着经过语法分析，生成具有语法意义的AST（能够区分出语句块、注释、变量声明、函数参数等）。</p><p>利用 @babel&#x2F;parser 对源代码进行解析 得到 AST。</p><p>栗如：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(info)</span><br></pre></td></tr></table></figure><p>经过parsing后，生成的AST如下：</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></pre></td><td class="code"><pre><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;CallExpression&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;callee&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;MemberExpression&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;object&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;Identifier&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;loc&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;identifierName&quot;</span><span class="punctuation">:</span> <span class="string">&quot;console&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;console&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;property&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;Identifier&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;loc&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;identifierName&quot;</span><span class="punctuation">:</span> <span class="string">&quot;log&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;log&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="attr">&quot;arguments&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="attr">&quot;Identifier&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;Identifier&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;loc&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;identifierName&quot;</span><span class="punctuation">:</span> <span class="string">&quot;log&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;info&quot;</span><span class="punctuation">,</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>🔥Tip: JS代码对应的AST结构可以通过<a href="https://astexplorer.net/">AST Explorer</a>工具查看</p><p>仔细的小伙伴可能就会发现从我们的源代码到AST的过程其实就是一个分词的过程，将我们的 console.log(info) 分成 console、log、info。</p><p>有了这个 AST 树结构，我们就能进行语义层面转换了。</p><h3 id="转换"><a href="#转换" class="headerlink" title="转换"></a>转换</h3><p>输入AST，输出修改过的AST</p><p>利用 @babel&#x2F;traverse 对 AST 进行遍历，并解析出整个树的 path，通过挂载的 metadataVisitor 读取对应的元信息，这一步叫 set AST 过程。</p><blockquote><p>@babel&#x2F;traverse 是一款用来自动遍历抽象语法树的工具，它会访问树中的所有节点，在进入每个节点时触发 enter 钩子函数，退出每个节点时触发 exit 钩子函数。开发者可在钩子函数中对 AST 进行修改。</p></blockquote><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><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> traverse <span class="keyword">from</span> <span class="string">&quot;@babel/traverse&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title function_">traverse</span>(ast, &#123;</span><br><span class="line">  <span class="title function_">enter</span>(<span class="params">path</span>) &#123;</span><br><span class="line">    <span class="comment">// 进入 path 后触发</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="title function_">exit</span>(<span class="params">path</span>) &#123;</span><br><span class="line">    <span class="comment">// 退出 path 前触发</span></span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>transforming（转换），对应于编译器的机器无关代码优化阶段（稍微有点牵强，但二者工作内容都是修改AST），对 AST 做一些修改，比如针对上面的 log 增加一些信息方便我们调试：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(info) =&gt; <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;[info]&#x27;</span>, info)</span><br></pre></td></tr></table></figure><p>修改过后的 AST 结构：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CallExpression&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;callee&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="comment">// ....</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;arguments&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="attr">&quot;StringLiteral&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;StringLiteral&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;value&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&#x27;[info]&#x27;&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;Identifier&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;Identifier&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;loc&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;identifierName&quot;</span><span class="punctuation">:</span> <span class="string">&quot;log&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;info&quot;</span><span class="punctuation">,</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>语义层面的转换具体而言就是对AST进行增、删、改操作，修改后的AST可能具有不同的语义，映射回代码字符串也不同</p><h3 id="生成"><a href="#生成" class="headerlink" title="生成"></a>生成</h3><p>输入AST，输出JS源码</p><p>generation（生成），对应于编译器的代码生成阶段，把AST映射回代码字符串。</p><p>利用 @babel&#x2F;generator 将 AST 树输出为转码后的代码字符串。</p><h2 id="实践"><a href="#实践" class="headerlink" title="实践"></a>实践</h2><p>说了这么多接下来我们就用代码实践一下上面的例子</p><h3 id="相关npm包"><a href="#相关npm包" class="headerlink" title="相关npm包"></a>相关npm包</h3><ul><li>@babel&#x2F;parser 解析输入源码，创建AST</li><li>@babel&#x2F;traverse 遍历操作AST</li><li>@babel&#x2F;generator 把AST转回JS代码</li><li>@babel&#x2F;types AST操作工具库</li></ul><h3 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> parser = <span class="built_in">require</span>(<span class="string">&#x27;@babel/parser&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> traverse = <span class="built_in">require</span>(<span class="string">&#x27;@babel/traverse&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> generate = <span class="built_in">require</span>(<span class="string">&#x27;@babel/generator&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> t = <span class="built_in">require</span>(<span class="string">&#x27;@babel/types&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">compile</span>(<span class="params">code</span>) &#123;</span><br><span class="line">  <span class="comment">// 1. parse</span></span><br><span class="line">  <span class="keyword">const</span> ast = parser.<span class="title function_">parse</span>(code);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 2. traverse</span></span><br><span class="line">  <span class="keyword">const</span> visitor = &#123;</span><br><span class="line">    <span class="title class_">CallExpression</span>(path) &#123;</span><br><span class="line">      <span class="keyword">const</span> &#123; callee, <span class="variable language_">arguments</span> &#125; = path.<span class="property">node</span>;</span><br><span class="line">      <span class="keyword">if</span> (</span><br><span class="line">        t.<span class="title function_">isMemberExpression</span>(callee)</span><br><span class="line">        &amp;&amp; callee.<span class="property">object</span>.<span class="property">name</span> === <span class="string">&#x27;console&#x27;</span></span><br><span class="line">        &amp;&amp; callee.<span class="property">property</span>.<span class="property">name</span> === <span class="string">&#x27;log&#x27;</span></span><br><span class="line">        &amp;&amp; <span class="variable language_">arguments</span>.<span class="property">length</span> &gt; <span class="number">0</span></span><br><span class="line">      ) &#123;</span><br><span class="line">        <span class="keyword">const</span> variableName = <span class="variable language_">arguments</span>[<span class="number">0</span>].<span class="property">name</span>;</span><br><span class="line">        path.<span class="property">node</span>.<span class="property">arguments</span>.<span class="title function_">unshift</span>(</span><br><span class="line">          t.<span class="title class_">StringLiteral</span>(<span class="string">`[<span class="subst">$&#123;variableName&#125;</span>]`</span>)</span><br><span class="line">        )</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;;</span><br><span class="line">  traverse.<span class="title function_">default</span>(ast, visitor);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 3. generate</span></span><br><span class="line">  <span class="keyword">return</span> generate.<span class="title function_">default</span>(ast, &#123;&#125;, code);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> code = <span class="string">`console.log(info)`</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> result = <span class="title function_">compile</span>(code);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(result.<span class="property">code</span>);</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>看到这，我们的 AST 实践也告一段落了。当然，文章所讲的只是一个简单的例子，但基本的原理思路八九不离十，更多的类型还得自己去探究。总之，掌握好 AST，你真的可以做很多事情。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;什么是AST（抽象语法树）&quot;&gt;&lt;a href=&quot;#什么是AST（抽象语法树）&quot; class=&quot;headerlink&quot; title=&quot;什么是AST（抽象语法树）?&quot;&gt;&lt;/a&gt;什么是AST（抽象语法树）?&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;It is a hie</summary>
      
    
    
    
    
    <category term="随笔" scheme="http://yoursite.com/tags/%E9%9A%8F%E7%AC%94/"/>
    
    <category term="AST" scheme="http://yoursite.com/tags/AST/"/>
    
  </entry>
  
  <entry>
    <title>JWT--JSON WEB TOKEN</title>
    <link href="http://yoursite.com/2019/09/04/JWT--JSON%20WEB%20TOKEN/"/>
    <id>http://yoursite.com/2019/09/04/JWT--JSON%20WEB%20TOKEN/</id>
    <published>2019-09-04T07:43:52.000Z</published>
    <updated>2019-09-04T07:44:59.614Z</updated>
    
    <content type="html"><![CDATA[<h2 id="什么是JWT"><a href="#什么是JWT" class="headerlink" title="什么是JWT"></a>什么是JWT</h2><p>JSON Web Token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准（<a href="https://tools.ietf.org/html/rfc7519">RFC 7519</a>)，该token被设计为紧凑且安全的，特别适用于分布式站点的单点登录（SSO）场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息，以便于从资源服务器获取资源，也可以增加一些额外的其它业务逻辑所必须的声明信息，该token也可直接被用于认证，也可被加密。</p><p>JWT是目前最流行的跨域认证解决方案之一。</p><h2 id="为什么使用JWT"><a href="#为什么使用JWT" class="headerlink" title="为什么使用JWT"></a>为什么使用JWT</h2><p>在看为什么使用之前，我们必须要先了解之前我们是如何进行验证请求的。</p><h3 id="Session认证"><a href="#Session认证" class="headerlink" title="Session认证"></a>Session认证</h3><p>在 Session 认证方式中，用户登录后发给服务器，服务器在接收并验证发送过来的账号密码请求之后，就会把这个用户信息放入 Session 中，然后把 Session 存在服务器上，这样服务器就知道了这个用户的存在，当下一次用户访问的时候，就能认证了。</p><p>但因为我们知道<strong>http协议是一种无状态的协议</strong>，也就是说当下一次用户发送请求的时候，请求中没有任何信息能表明用户身份！也就是说不知道请求是谁发出来了，这样也就不能认证了。</p><p>所以就需要利用 Cookie 来管理 Session，即把 SessionID 放入 HTTP 响应中发给客户端，并保存在客户端，当客户端发送下一次请求的时候，就把这个 SessionID 一起发送回来，这样就能这次的请求是谁发出来的了。</p><blockquote><p>扩展：Cookie 是由客户端（通常是浏览器）保存的小型文本信息，其内容是一系列的键值对，是由 HTTP 服务器设置并保存在浏览器上的信息。</p></blockquote><h3 id="Session认证的问题"><a href="#Session认证的问题" class="headerlink" title="Session认证的问题"></a>Session认证的问题</h3><ol><li><p><code>内存开销大</code>: 我们知道 Session 是存在服务器上的，实际上为了加快认证的速度，我们一般都会放在内存中，这样当用户基数大的时候，内存的开销就会很大。当然也可以将 Session 存入到 Session 表或者是缓存（redis等）中，但是依旧会有这样的问题。</p></li><li><p><code>安全性（CSRF）</code>: 因为是基于 Cookie 进行用户识别，如果 Cookie 被截获，用户就会很容易收到跨站请求伪造的攻击。</p></li><li><p><code>分布式负载均衡</code>: 因为 Session 信息是被单个服务器所保存的，所以在分布式系统中就不能适用了。比如 Session 一开始是保存在 A 服务器上，但是下一次请求的时候，这个请求被服务器负载均衡转发到了 B 服务器，而 B 服务器则没有这个 Session 信息，所以就不能用过认证了。</p></li></ol><h3 id="JWT的优点"><a href="#JWT的优点" class="headerlink" title="JWT的优点"></a>JWT的优点</h3><p>因为 JWT 是由服务端生成的，通过请求传给客户端（客户端可以以任意方式存放）。所以服务器不需要存储任何 JWT 信息。这样就能避免了上述 Session 的几个问题了。当然 JWT 还有其自身的一些优点。</p><ol><li><p><code>轻量级</code>：JWT是非常轻量级的，传输的方式多样化，可以通过URL&#x2F;POST参数&#x2F;HTTP头部等方式传输。</p></li><li><p><code>无状态/跨域认证</code>：token包含所有用于标识用户的信息，这消除了对会话状态的需要。如果我们使用负载均衡，我们依然可以将token传递给任何服务器，而不是存储在我们登录的同一台服务器上。</p></li><li><p><code>安全性</code>：无需担心跨站请求伪造（CSRF）攻击。</p></li></ol><h2 id="JWT-组成"><a href="#JWT-组成" class="headerlink" title="JWT 组成"></a>JWT 组成</h2><p>由三个部分组成：header.payload.signature</p><h3 id="header"><a href="#header" class="headerlink" title="header"></a>header</h3><p><code>header</code>：包含了两个部分 typ 和 alg，分别是声明类型和JWT的加密算法。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;alg&quot;</span>: <span class="string">&quot;HS256&quot;</span>,</span><br><span class="line">  <span class="string">&quot;typ&quot;</span>: <span class="string">&quot;JWT&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>经过 base64URL 加密之后得到 JWT 的第一部分信息：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9</span><br></pre></td></tr></table></figure><h3 id="payload"><a href="#payload" class="headerlink" title="payload"></a>payload</h3><p><code>payload</code>：负载，存放有效信息的地方。这些有效信息包含三个部分：标准中注册的声明、公共的声明 和 私有的声明。</p><ul><li>iss：JWT 的签发者</li><li>sub：JWT 所面向的用户</li><li>aud：接收 JWT 的一方</li><li>exp：JWT 的过期时间这个过期时间必须大于签发时间</li><li>nbf：JWT 起作用的开始时间，即定义在什么时间之前，该JWT都是不可用的</li><li>iat：JWT 的签发时间</li><li>jti：JWT 的唯一身份标识，主要用来作为一次性 token，从而回避重放攻击。</li></ul><p>除了官方字段，你还可以在这个部分定义私有字段：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;sub&quot;</span>: <span class="string">&quot;1234567890&quot;</span>, </span><br><span class="line">  <span class="string">&quot;nickname&quot;</span>: <span class="string">&quot;hzzly&quot;</span>, </span><br><span class="line">  <span class="string">&quot;username&quot;</span>: <span class="string">&quot;hzzly&quot;</span>, </span><br><span class="line">  <span class="string">&quot;scopes&quot;</span>: [ <span class="string">&quot;admin&quot;</span>, <span class="string">&quot;user&quot;</span> ] </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>经过 base64URL 加密之后得到 JWT 的第二部分信息：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">eyJzdWIiOiIxMjM0NTY3ODkwIiwibmlja25hbWUiOiJoenpseSIsInVzZXJuYW1lIjoiaHp6bHkiLCJzY29wZXMiOlsiYWRtaW4iLCJ1c2VyIl19</span><br></pre></td></tr></table></figure><h3 id="signature"><a href="#signature" class="headerlink" title="signature"></a>signature</h3><p><code>signature</code>：是对前两部分的签名，防止数据篡改。由三个部分组成：header、payload 和 secret。其中 header 和 payload 都是加密后的字符串，secret就是一个字符串（密钥）。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> signature = <span class="title class_">HMACSHA256</span>(<span class="title function_">base64UrlEncode</span>(header) + <span class="string">&quot;.&quot;</span> + <span class="title function_">base64UrlEncode</span>(payload), secret);</span><br></pre></td></tr></table></figure><p>算出签名以后，把 header、payload、signature 三个部分拼成一个字符串，每个部分之间用”点”（.）分隔，就可以返回给客户端。</p><p>最终的jwt：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.<span class="property">eyJzdWIiOiIxMjM0NTY3ODkwIiwibmlja25hbWUiOiJoenpseSIsInVzZXJuYW1lIjoiaHp6bHkiLCJzY29wZXMiOlsiYWRtaW4iLCJ1c2VyIl19</span>.<span class="property">sXaHGg9SWyRpl</span>-rhiSBFuD01G4yE3Gmi5m-<span class="title class_">JD7</span>u6YyI</span><br></pre></td></tr></table></figure><h3 id="base64URL"><a href="#base64URL" class="headerlink" title="base64URL"></a>base64URL</h3><p>面提到，header 和 payload 串型化的算法是 base64URL。这个算法跟 base64 算法基本类似，但有一些小的不同。</p><p>JWT 作为一个令牌（token），有些场合可能会放到 URL（比如 api.example.com&#x2F;?token&#x3D;xxx）。base64 有三个字符+、&#x2F;和&#x3D;，在 URL 里面有特殊含义，所以要被替换掉：&#x3D;被省略、+替换成-，&#x2F;替换成_ 。这就是 Base64URL 算法。</p><h2 id="JWT的使用方式"><a href="#JWT的使用方式" class="headerlink" title="JWT的使用方式"></a>JWT的使用方式</h2><p>客户端收到服务器返回的 JWT，可以储存在 sessionStorage 或 localStorage 里面。</p><p>此后，客户端每次与服务器通信，都要带上这个 JWT。需要把它放在 HTTP 请求的头信息 Authorization 字段里面。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Authorization</span>: <span class="title class_">Bearer</span> &lt;token&gt;</span><br></pre></td></tr></table></figure><p>另一种做法是，跨域的时候，JWT 就放在 POST 请求的数据体里面。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;什么是JWT&quot;&gt;&lt;a href=&quot;#什么是JWT&quot; class=&quot;headerlink&quot; title=&quot;什么是JWT&quot;&gt;&lt;/a&gt;什么是JWT&lt;/h2&gt;&lt;p&gt;JSON Web Token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准</summary>
      
    
    
    
    
    <category term="随笔" scheme="http://yoursite.com/tags/%E9%9A%8F%E7%AC%94/"/>
    
    <category term="jwt" scheme="http://yoursite.com/tags/jwt/"/>
    
  </entry>
  
  <entry>
    <title>一本正经的来了解一下HTTP2</title>
    <link href="http://yoursite.com/2019/09/01/%E4%B8%80%E6%9C%AC%E6%AD%A3%E7%BB%8F%E7%9A%84%E6%9D%A5%E4%BA%86%E8%A7%A3%E4%B8%80%E4%B8%8BHTTP2/"/>
    <id>http://yoursite.com/2019/09/01/%E4%B8%80%E6%9C%AC%E6%AD%A3%E7%BB%8F%E7%9A%84%E6%9D%A5%E4%BA%86%E8%A7%A3%E4%B8%80%E4%B8%8BHTTP2/</id>
    <published>2019-09-01T15:44:30.000Z</published>
    <updated>2019-09-04T07:40:13.570Z</updated>
    
    <content type="html"><![CDATA[<h2 id="HTTP-x2F-2介绍"><a href="#HTTP-x2F-2介绍" class="headerlink" title="HTTP&#x2F;2介绍"></a>HTTP&#x2F;2介绍</h2><p>维基百科：HTTP&#x2F;2（超文本传输协议第2版，最初命名为HTTP 2.0），简称为h2（基于TLS&#x2F;1.2或以上版本的加密连接）或h2c（非加密连接），是HTTP协议的的第二个主要版本，使用于万维网。</p><p>HTTP&#x2F;2是HTTP协议自1999年HTTP 1.1发布后的首个更新，主要基于SPDY协议。它由互联网工程任务组（IETF）的Hypertext Transfer Protocol Bis（httpbis）工作小组进行开发。该组织于2014年12月将HTTP&#x2F;2标准提议递交至IESG进行讨论，于2015年2月17日被批准。HTTP&#x2F;2标准于2015年5月以RFC 7540正式发表。</p><h2 id="HTTP-x2F-1-x存在的问题"><a href="#HTTP-x2F-1-x存在的问题" class="headerlink" title="HTTP&#x2F;1.x存在的问题"></a>HTTP&#x2F;1.x存在的问题</h2><p>在看为什么要使用HTTP&#x2F;2之前，我们先来了解之前的HTTP&#x2F;1.x存在的问题。</p><ol><li><code>线头阻塞</code>：TCP连接上只能发送一个请求，前面的请求未完成前，后续的请求都在排队等待。</li><li><code>多个TCP连接</code>：虽然HTTP&#x2F;1.1管线化可以支持请求并发，但是浏览器很难实现，chrome、firefox等都禁用了管线化。所以1.1版本请求并发依赖于多个TCP连接，建立TCP连接成本很高，还会存在慢启动的问题。</li><li><code>头部冗余，采用文本格式</code>：HTTP&#x2F;1.X版本是采用文本格式，首部未压缩，而且每一个请求都会带上cookie、user-agent等完全相同的首部。</li><li><code>客户端需要主动请求</code></li></ol><h2 id="HTTP-x2F-2的具体变化"><a href="#HTTP-x2F-2的具体变化" class="headerlink" title="HTTP&#x2F;2的具体变化"></a>HTTP&#x2F;2的具体变化</h2><h3 id="二进制分帧层"><a href="#二进制分帧层" class="headerlink" title="二进制分帧层"></a>二进制分帧层</h3><p>先来理解几个概念：</p><p><code>帧（Frame）</code>：HTTP&#x2F;2数据通信的最小单位消息：指 HTTP&#x2F;2 中逻辑上的 HTTP 消息。例如请求和响应等，消息由一个或多个帧组成。</p><p><code>流（Stream）</code>：存在于连接中的一个虚拟通道。流可以承载双向消息，每个流都有一个唯一的整数ID。</p><p><code>消息（Message）</code>：一个完整的HTTP请求或响应，由一个或多个帧组成。特定消息的帧在同一个流上发送，这意味着一个HTTP请求或响应只能在一个流上发送。</p><p>HTTP&#x2F;2 性能提升的核心就在于二进制分帧层。HTTP2是二进制协议，他采用二进制格式传输数据而不是1.x的文本格式，二进制协议解析起来更高效。 HTTP &#x2F; 1 的请求和响应报文，都是由起始行，首部和实体正文（可选）组成，各部分之间以文本换行符分隔。HTTP&#x2F;2 将请求和响应数据分割为更小的帧，并且它们采用二进制编码。</p><p>HTTP&#x2F;2 中，同域名下所有通信都在单个连接上完成，该连接可以承载任意数量的双向数据流。每个数据流都以消息的形式发送，而消息又由一个或多个帧组成。多个帧之间可以乱序发送，根据帧首部的流标识可以重新组装。</p><h3 id="多路复用"><a href="#多路复用" class="headerlink" title="多路复用"></a>多路复用</h3><p>上面提到HTTP&#x2F;1.x的线头阻塞和多个TCP连接的问题，HTTP2的多路复用完美解决。HTTP&#x2F;2让所有的通信都在一个TCP连接上完成，真正实现了请求的并发。</p><p>在一个 TCP 连接上，HTTP&#x2F;2可以向服务器不断发送帧，每帧的 stream identifier 的标明这一帧属于哪个流，然后在接收时，根据 stream identifier 拼接每个流的所有帧组成一整块数据。把 HTTP&#x2F;1.x 每个请求都当作一个流，那么多个请求变成多个流，请求响应数据分成多个帧，不同流中的帧交错地发送给对方，这就是 HTTP&#x2F;2 中的多路复用。</p><p>流的概念实现了单连接上多请求 - 响应并行，解决了线头阻塞的问题，减少了 TCP 连接数量和 TCP 连接慢启动造成的问题</p><p>所以 HTTP&#x2F;2 对于同一域名只需要创建一个连接，而不是像 HTTP&#x2F;1.x 那样创建 6~8 个连接。</p><h3 id="头部压缩"><a href="#头部压缩" class="headerlink" title="头部压缩"></a>头部压缩</h3><p>在HTTP&#x2F;1.x版本中，首部用文本格式传输，通常会给每个传输增加500-800字节的开销。当一个网站请求非常多时，而每个请求带的一些首部字段都是相同的，例如cookie、user-agent等，浪费了很多带宽资源。HTTP&#x2F;2为此对消息头采用HPACK（专为HTTP&#x2F;2头部设计的压缩格式）进行压缩传输，能够节省消息头占用的网络的流量。头部压缩需要在浏览器和服务器端之间：</p><ul><li>维护一份相同的静态字典，包含常见的头部名称，以及常见的头部名称和值的组合</li><li>维护一份相同的动态字典，可以动态的添加内容</li><li>通过静态Huffman编码对传输的首部字段进行编码</li></ul><p>HTTP&#x2F;2的静态字典可以查看<a href="https://httpwg.org/specs/rfc7541.html#static.table.definition">这里</a></p><p>所以我们在传输首部字段的时候，例如要传输method:GET,那我们只需要传输静态字典里面method:GET对应的索引值就可以了，一个字节搞定。像user-agent、cookie这种静态字典里面只有首部名称而没有值的首部，第一次传输需要user-agent在静态字典中的索引以及他的值，值会采用静态Huffman编码来减小体积。</p><p>第一次传输过user-agent 之后呢，浏览器和服务器端就会把它添加到自己的动态字典中。后续传输就可以传输索引了，一个字节搞定。</p><h3 id="服务器推送"><a href="#服务器推送" class="headerlink" title="服务器推送"></a>服务器推送</h3><p>浏览器发送一个请求，服务器主动向浏览器推送与这个请求相关的资源，这样浏览器就不用发起后续请求。<br>Server-Push 主要是针对资源内联做出的优化，相较于 HTTP&#x2F;1.x 资源内联的优势:</p><ul><li>客户端可以缓存推送的资源</li><li>客户端可以拒收推送过来的资源</li><li>推送资源可以由不同页面共享</li><li>服务器可以按照优先级推送资源</li></ul><h3 id="重置"><a href="#重置" class="headerlink" title="重置"></a>重置</h3><p>HTTP&#x2F;1.1的有一个缺点是：当一个含有确切值的Content-Length的HTTP消息被送出之后，你就很难中断它了。当然，通常你可以断开整个TCP链接（但也不总是可以这样），但这样导致的代价就是需要通过三次握手来重新建立一个新的TCP连接。</p><p>一个更好的方案是只终止当前传输的消息并重新发送一个新的。在HTTP&#x2F;2里面，我们可以通过发送RST_STREAM帧来实现这种需求，从而避免浪费带宽和中断已有的连接。</p><h2 id="扩展"><a href="#扩展" class="headerlink" title="扩展"></a>扩展</h2><p>HTTP&#x2F;2升级并不完全是没有副作用的，先说结论，HTTP&#x2F;1.x全升HTTP&#x2F;2性能不一定能提升，还是需要做一些特殊的优化。</p><ol><li><p>需要把针对HTTP&#x2F;1.x的优化点摘出来改成对h2友好的，不然会影响性能，比如雪碧图，css，js行内引入，域名打散这些都是针对h1的优化，如果不针对h2做修改，收益可能是负的。</p></li><li><p>保证你的页面没有那种古老的合并资源请求的优化，比如通过xhr请求多个图片js，html片段再在客户端解析的骚操作。</p></li><li><p>h2特性在h1上不支持，所以你需要在不支持的h1浏览器里访问站点，来做性能测试，需要成本。</p></li><li><p>h2对单请求的优化有限，如果做流服务器，可能收益也不大，视频，大图片下载，多路复用也体现不出什么优势。</p></li><li><p>开启h2之后对ssl的配置可能会更复杂一些，如果不是nginx层代理开启，而是在前端机上比如nodejs服务上开启h2，服务端的改造也比较麻烦，不像静态资源那么开关方便。</p></li></ol><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://zh.wikipedia.org/wiki/HTTP/2">维基百科：HTTP&#x2F;2</a></li><li><a href="https://ye11ow.gitbooks.io/http2-explained/content/">《http2讲解》</a></li><li><a href="https://www.zhihu.com/question/310263956/answer/582342502">把所有 HTTPS 项目无脑升级成 HTTP&#x2F;2 会有什么坑吗?[知乎]</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;HTTP-x2F-2介绍&quot;&gt;&lt;a href=&quot;#HTTP-x2F-2介绍&quot; class=&quot;headerlink&quot; title=&quot;HTTP&amp;#x2F;2介绍&quot;&gt;&lt;/a&gt;HTTP&amp;#x2F;2介绍&lt;/h2&gt;&lt;p&gt;维基百科：HTTP&amp;#x2F;2（超文本传输协议第2版，</summary>
      
    
    
    
    
    <category term="随笔" scheme="http://yoursite.com/tags/%E9%9A%8F%E7%AC%94/"/>
    
    <category term="http" scheme="http://yoursite.com/tags/http/"/>
    
  </entry>
  
  <entry>
    <title>深究HTTPS</title>
    <link href="http://yoursite.com/2019/08/13/%E6%B7%B1%E7%A9%B6HTTPS/"/>
    <id>http://yoursite.com/2019/08/13/%E6%B7%B1%E7%A9%B6HTTPS/</id>
    <published>2019-08-13T07:44:42.000Z</published>
    <updated>2019-11-06T06:11:27.306Z</updated>
    
    <content type="html"><![CDATA[<p>前言：还没有用上https的可以看一下我之前写的<a href="http://hjingren.cn/2019/06/11/Nginx%E4%B8%8B%E5%8D%87%E7%BA%A7https/">Nginx下升级https</a>，把你的http升级到https。</p><h2 id="HTTP和HTTPS基本概念"><a href="#HTTP和HTTPS基本概念" class="headerlink" title="HTTP和HTTPS基本概念"></a>HTTP和HTTPS基本概念</h2><p><code>HTTP</code>：超文本传输协议（英文：HyperText Transfer Protocol，缩写：HTTP）是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP是万维网的数据通信的基础。<br><code>HTTPS</code>：超文本传输安全协议（英语：Hypertext Transfer Protocol Secure，缩写：HTTPS，常称为HTTP over TLS，HTTP over SSL或HTTP Secure）是一种通过计算机网络进行安全通信的传输协议。HTTPS经由HTTP进行通信，但利用SSL&#x2F;TLS来加密数据包。HTTPS开发的主要目的，是提供对网站服务器的身份认证，保护交换数据的隐私与完整性。</p><h2 id="HTTP和HTTPS的差异"><a href="#HTTP和HTTPS的差异" class="headerlink" title="HTTP和HTTPS的差异"></a>HTTP和HTTPS的差异</h2><ol><li><p>从上面概念也可以看出来HTTP是不安全的，且攻击者通过监听和中间人攻击等手段，可以获取网站帐户和敏感信息等。HTTPS被设计为可防止前述攻击，并在正确配置时被认为是安全的。</p></li><li><p>HTTP的URL由”http:&#x2F;&#x2F;“起始且默认使用端口80，而HTTPS的URL由”https:&#x2F;&#x2F;“起始且默认使用端口443。</p></li><li><p>HTTP协议运行在TCP之上，所有传输的内容都是明文，客户端和服务器端都无法验证对方的身份。HTTPS是运行在SSL&#x2F;TLS之上的HTTP协议，SSL&#x2F;TLS运行在TCP之上。所有传输的内容都经过加密，加密采用对称加密，但对称加密的密钥用服务器方的证书进行了非对称加密。</p></li></ol><ul><li>1）对称加密：密钥只有一个，加密解密为同一个密码，且加解密速度快，典型的对称加密算法有DES、AES等；</li><li>2）非对称加密：密钥成对出现（且根据公钥无法推知私钥，根据私钥也无法推知公钥），加密解密使用不同密钥（公钥加密需要私钥解密，私钥加密需要公钥解密），相对对称加密速度较慢，典型的非对称加密算法有RSA、DSA等。</li></ul><h2 id="简述HTTPS访问过程"><a href="#简述HTTPS访问过程" class="headerlink" title="简述HTTPS访问过程"></a>简述HTTPS访问过程</h2><p>HTTPS在进行数据传输之前会与服务器和浏览器进行一次握手，在握手时确定双方的加密密码信息。</p><p>具体过程如下：</p><ol><li><p>浏览器将支持的加密信息发送给网站服务器；</p></li><li><p>服务器会选择出一套加密算法和哈希算法，将验证身份的信息以证书（证书发布CA机构、证书有效期、公钥、证书所有者、签名等）的形式发送给浏览器；</p></li><li><p>当浏览器收到证书之后首先需要验证证书的合法性，如果证书受到浏览器信任则在浏览器地址栏会有标志显示，否则就会显示不受信的标识。当证书受信之后，浏览器会随机生成一串密码，并使用证书中的公钥加密。之后就是使用约定好的哈希算法握手消息，并生成随机数对消息进行加密，再将生成的信息发送给服务器；</p></li><li><p>当服务器接收到浏览器发送过来的数据后，会使用服务器本身的私钥将信息解密确定密码，然后通过密码解密浏览器发送过来的握手信息，并验证哈希是否与浏览器一致。然后服务器会使用密码加密新的握手信息，发送给浏览器；</p></li><li><p>最后浏览器解密并计算经过哈希算法加密的握手消息，如果与服务发送过来的哈希一致，则此握手过程结束后，服务器与浏览器会使用之前浏览器生成的随机密码和对称加密算法进行加密交换数据。</p></li></ol><h2 id="HTTPS的加密原理"><a href="#HTTPS的加密原理" class="headerlink" title="HTTPS的加密原理"></a>HTTPS的加密原理</h2><p>HTTPS在加密过程中使用了非对称加密技术和对称加密技术。</p><h3 id="对称加密算法"><a href="#对称加密算法" class="headerlink" title="对称加密算法"></a>对称加密算法</h3><p>采用单钥密码系统的加密方式，同一个密钥可以同时做信息的加密和解密，这种加密的方法称为对称加密。</p><p>SSL在通信过程中，使用了对称加密算法，也就是说客户端和服务器同时共享一个密钥。</p><p>于是，以共享密钥的方式加密，必须将密钥发给对方。这个时候，假如通信过程被监听，密钥被攻击者获取了，那么这个时候也就失去了加密的意义了。</p><p><img src="https://hzzlyxx.oss-cn-beijing.aliyuncs.com/blog/https/https2.png" alt="https://hzzlyxx.oss-cn-beijing.aliyuncs.com/blog/https/https2.png"></p><p>于是就需要使用两把密钥的非对称加密算法。</p><h3 id="非对称加密算法"><a href="#非对称加密算法" class="headerlink" title="非对称加密算法"></a>非对称加密算法</h3><p>与对称加密算法相反，非对称加密算法需要两个密钥来进行加密和解密，这两个密钥是配对的，分别是公开密钥（公钥）和私有密钥（私钥）。</p><p>于是现在，假设现在由服务器来生成一对公钥和密钥。</p><p>当客户端第一次发请求和服务器协商的时候，服务器就生成了一对公钥和私钥。</p><p>紧接着，服务器把公钥发给客户端（明文，不需要做任何加密），客户端接收后，使用服务器发过来的公钥进行数据加密。</p><p>再接着，服务器接收到了以后，用配对的私钥进行解密，就得到了客户端发送的数据。</p><p>非对称加密算法的特点：算法强度复杂、安全性依赖于算法与密钥但是由于其算法复杂，而使得加解密速度性能上比较差，没有对称加密算法加解密的速度快。</p><p>但是我们还存在一个问题，如果公钥被中间人拿到篡改呢？</p><p><img src="https://hzzlyxx.oss-cn-beijing.aliyuncs.com/blog/https/https3.jpeg" alt="https://hzzlyxx.oss-cn-beijing.aliyuncs.com/blog/https/https3.jpeg"></p><p>这时我们就需要使用证书保证公钥的正确性</p><h3 id="公钥证书"><a href="#公钥证书" class="headerlink" title="公钥证书"></a>公钥证书</h3><p>首先，服务器的运营人员向数字证书机构（CA）提出公开密钥的申请。数字证书认证机构在验证申请者的身份之后，会对申请的公开密钥做数字签名，然后分配这个已签名的公开密钥，并将该公开密钥放入公钥证书后绑定在一起。</p><p>简单点理解就是：<br>CA会向申请者颁发一个证书文件和证书的私钥文件，这个证书文件里面的内容有：签发者、证书用途、服务器申请的时候附带的公钥、服务器的加密算法、使用的HASH算法、证书到期的时间等等，私钥文件就是与证书中公钥对应的私钥。</p><p>紧接着，把上面所提到的证书文件里的内容，做一次HASH求值，得到一个HASH值。</p><p>再接着，用CA的私钥进行加密，这样就完成了数字签名。而用CA的私钥加密后，就生成了类似人体指纹的签名，任何篡改证书的尝试，都会被数字签名发现。</p><p>最后，把数字签名，附在数字证书的末尾，传输回来给服务器。</p><p>接下来，服务器会把这份由数字证书认证机构颁发的公钥证书发给客户端。<br>客户端拿到这个数字证书以后，会去校验证书签名的合法性，先对证书进行SHA256（浏览器的加密算法）得到一个哈希值，然后用证书的公钥对证书的签名进行解密从中取得另一个哈希值，如果这两个哈希值相等，说明证书没有被篡改过，确实是权威机构颁发。</p><p>如果认证通过，就可以取得服务器的公开密钥。</p><p>接下来就可以使用对称加密算法通信了。</p><h3 id="扩展：客户端证书"><a href="#扩展：客户端证书" class="headerlink" title="扩展：客户端证书"></a>扩展：客户端证书</h3><p>HTTPS中不仅可以使用服务器证书，还可以使用客户端证书。以客户端证书进行客户端认证，它的作用与服务器证书是相同的。</p><p>例如，银行的网上银行就采用了客户端证书（网银盾）。在登录网银时不仅要求用户确认输入ID和密码，还会要求用户的客户端证书（网银盾），以确认用户是否从特定的终端访问网银。</p><h2 id="HTTPS的安全通信机制"><a href="#HTTPS的安全通信机制" class="headerlink" title="HTTPS的安全通信机制"></a>HTTPS的安全通信机制</h2><p><img src="https://hzzlyxx.oss-cn-beijing.aliyuncs.com/blog/https/https1.png" alt="https://hzzlyxx.oss-cn-beijing.aliyuncs.com/blog/https/https1.png"></p><h3 id="client-hello"><a href="#client-hello" class="headerlink" title="client_hello"></a>client_hello</h3><p>这一步中，客户端通过发送<code>client_hello</code>报文开始SSL通信。在请求中，浏览器会带上一些建立连接的必要信息（注意：这一步的信息全都是明文的），包括：</p><ol><li><code>版本</code>: 客户端支持的最高的 TLS 协议版本。从高到低依次为：TLS v1.2, TLS v1.1, TLS v1.0, SSL v3, SSL v2。其中低于 TLS v1.0 的版本基本不再使用，因为 SSL v3 和 SSL v2 都存在漏洞，Google 和 Mozilla 已明确禁用 SSL 协议。</li><li><code>密码套件</code>: 按优先级降序排列的、客户端支持的加密套件列表。每个加密套件会各包含一个认证算法（用于身份验证）、密钥交换算法（用于协商密钥）、对称加密算法（用于消息加密）和信息摘要算法（用于完整性校验）。</li><li><code>压缩方法</code>: 客户端支持的用于压缩消息、降低传输体积的压缩算法列表。</li><li><code>随机数</code>: 一个由客户端生成的随机数，使用 32 位时间戳和一个安全随机数生成器生成的 28 字节随机数组成。这个随机数用于后续<code>Master Key</code>的生成，并防止重放攻击。</li><li><code>会话标识</code>: 一个变长的会话标志。非 0 值意味着客户端希望更新当前已存在的连接的参数或者为此连接创建一个新的连接。0 值表示客户端想在新会话上创建一个新连接。</li><li><code>扩展字段</code>: 包含一些其他的相关参数（比如 SNI）。</li></ol><h3 id="server-hello"><a href="#server-hello" class="headerlink" title="server_hello"></a>server_hello</h3><p>客户端在发出<code>client_hello</code>消息之后，会等待服务器返回<code>server_hello</code>消息，包含和 <code>client_hello</code>相同的参数。一般来说，参数结构如下：</p><ol><li><code>版本</code>: 包含客户端支持的最低版本和服务器支持的最高版本。</li><li><code>密码套件</code>: 包含了服务器从客户端发来的密码套件列表中选择出的将要使用的密码套件。</li><li><code>压缩方法</code>: 包含了服务器从客户端发来的压缩方法列表中选择出的将要使用的压缩方法。</li><li><code>随机数</code>: 由服务器生成的不同于客户端在<code>client_hello</code>中发来的随机数的另一个独立的随机数。</li><li><code>会话标识</code>: 如果客户端发送的会话标识不为 0，服务器会使用与客户端发送的一致的会话标识，否则返回的是服务器生成的一个新的会话标识。</li></ol><h3 id="certificate-server-hello-done"><a href="#certificate-server-hello-done" class="headerlink" title="certificate + server_hello _done"></a>certificate + server_hello _done</h3><p>通常来说，服务器会在<code>certificate</code>消息中发送其自身的公开密钥证书供客户端进行验证。</p><p>最后，服务器发送<code>server_hello_done</code>消息，表明服务器的 hello 相关的消息结束。在发送此消息之后，服务器会等待客户端应答，该消息没有参数。</p><p>在这一步结束之后表明最初阶段的SSL握手协商部分结束。</p><h3 id="client-key-change"><a href="#client-key-change" class="headerlink" title="client_key_change"></a>client_key_change</h3><p>客户端在收到服务器发来的<code>server_hello_done</code>消息之后，会验证服务器提供的证书是否合法，并检查<code>server_hello</code>的各项参数。如果验证通过，则客户端会向服务器发送一条或多条消息。</p><p>然后客户端会发送<code>client_key_exchange</code>报文消息，报文中包含通信加密中使用的一种被称为<code>PreMaster Key</code>的随机密码串，并使用服务器证书中的公钥或者服务器密钥交换消息中的临时 RSA 密钥加密。这个密钥会被用于之后的<code>Master Key</code>的计算。</p><h3 id="change-cipher-spec-finished"><a href="#change-cipher-spec-finished" class="headerlink" title="change_cipher_spec + finished"></a>change_cipher_spec + finished</h3><p>经过以上步骤，客户端和服务器已经可以通过得到的消息计算出<code>Master Key</code>了。从现在开始，客户端和服务器都将开始使用协商好的加密算法、密钥进行通信，在正式传递消息之前会计算<code>Master Key</code>和之前握手过程中收到的所有信息的<code>hash</code>，并通过协商好的加密算法使用 <code>Master Key</code>加密，作为<code>change_cipher_spec</code>消息的内容，接着发送<code>finished</code>消息。服务器在收到客户端发来的<code>change_cipher_spec</code>和<code>finished</code>消息之后，也会计算<code>Master Key</code>并使用协商好的加密算法和之前握手过程中收到的所有信息的<code>hash</code>，发回给客户端用以验证。至此，握手阶段结束，之后就可以交换应用层的内容了。</p><p>服务器和客户端的<code>finished</code>报文交换完毕之后，SSL连接就算建立完成，当然，通信会受到SSL的保护。从此处开始进行应用层协议的通信，即发送HTTP请求。</p><p>至此，整个过程介绍完毕。</p><h2 id="实践HTTPS连接的握手过程"><a href="#实践HTTPS连接的握手过程" class="headerlink" title="实践HTTPS连接的握手过程"></a>实践HTTPS连接的握手过程</h2><p>我们可以使用 curl 命令来简略查看建立 HTTPS 时的握手过程，在命令行中执行：curl -v -I -L <a href="https://hzzly.cn/">https://hzzly.cn</a></p><p>能得到如下的输出：</p><p><img src="https://hzzlyxx.oss-cn-beijing.aliyuncs.com/blog/https/http4.png" alt="https://hzzlyxx.oss-cn-beijing.aliyuncs.com/blog/https/http4.png"></p><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><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 表示建立了和 hzzly.cn 服务器 443 端口的连接。</span></span><br><span class="line">Connected to hzzly.cn (39.108.182.125) port 443 (<span class="comment">#0) </span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 客户端发出 client_hello 消息。</span></span><br><span class="line">TLSv1.2 (OUT), TLS handshake, Client hello (1): </span><br><span class="line"></span><br><span class="line"><span class="comment"># 服务器发出 server_hello 消息。</span></span><br><span class="line">TLSv1.2 (IN), TLS handshake, Server hello (2): </span><br><span class="line"></span><br><span class="line"><span class="comment"># 服务器发出 certificate 消息。</span></span><br><span class="line">TLSv1.2 (IN), TLS handshake, Certificate (11): </span><br><span class="line"></span><br><span class="line"><span class="comment"># 服务器发出 server_key_exchange 消息。</span></span><br><span class="line">TLSv1.2 (IN), TLS handshake, Server key exchange (12): </span><br><span class="line"></span><br><span class="line"><span class="comment"># 服务器发出 server_done 消息。</span></span><br><span class="line">TLSv1.2 (IN), TLS handshake, Server finished (14): </span><br><span class="line"></span><br><span class="line"><span class="comment"># 客户端发出 client_key_exchange 消息。</span></span><br><span class="line">TLSv1.2 (OUT), TLS handshake, Client key exchange (16): </span><br><span class="line"></span><br><span class="line"><span class="comment"># 客户端发出加密后的 client_hello 消息。</span></span><br><span class="line">TLSv1.2 (OUT), TLS change cipher, Client hello (1): </span><br><span class="line"></span><br><span class="line"><span class="comment"># 客户端发出 hello_done 消息。</span></span><br><span class="line">TLSv1.2 (OUT), TLS handshake, Finished (20): </span><br><span class="line"></span><br><span class="line"><span class="comment"># 服务器将加密后的 client_hello 消息发回。</span></span><br><span class="line">TLSv1.2 (IN), TLS change cipher, Client hello (1): </span><br><span class="line"></span><br><span class="line"><span class="comment"># 握手结束。</span></span><br><span class="line">TLSv1.2 (IN), TLS handshake, Finished (20): </span><br><span class="line"></span><br><span class="line"><span class="comment"># SSL 连接采用 ECDHE-RSA-AES256-GCM-SHA384 密码套件。</span></span><br><span class="line"><span class="comment"># ECDHE 表示密钥交换方法采用椭圆曲线迪菲-赫尔曼交换方法</span></span><br><span class="line"><span class="comment"># RSA 表示密钥交换中使用的签名方式</span></span><br><span class="line"><span class="comment"># AES-256-GCM 表示的是对称加密算法</span></span><br><span class="line"><span class="comment"># SHA-384 表示的是内容完整性校验使用的哈希算法</span></span><br><span class="line">SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384 </span><br><span class="line"></span><br><span class="line"><span class="comment"># 之后的几行包含了证书的内容，包括有效时间、常用名、证书签发机构等。</span></span><br><span class="line">Server certificate: </span><br><span class="line">      <span class="comment"># Common Name 为 hzzly.cn</span></span><br><span class="line">    subject: CN=hzzly.cn</span><br><span class="line">      <span class="comment"># 在此时间之前无效</span></span><br><span class="line">    start <span class="built_in">date</span>: Aug  2 00:00:00 2019 GMT</span><br><span class="line">      <span class="comment"># 在此时间之后无效</span></span><br><span class="line">    expire <span class="built_in">date</span>: Aug  1 12:00:00 2020 GMT</span><br><span class="line">      <span class="comment"># 域名和证书的域名匹配</span></span><br><span class="line">    subjectAltName: host <span class="string">&quot;hzzly.cn&quot;</span> matched certs <span class="string">&quot;hzzly.cn&quot;</span></span><br><span class="line">      <span class="comment"># 签发者是 Encryption</span></span><br><span class="line">    issuer: C=US; O=DigiCert Inc; OU=www.digicert.com; CN=Encryption Everywhere DV TLS CA - G1</span><br></pre></td></tr></table></figure><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li>《图解http》</li><li><a href="https://juejin.im/post/5a9400fcf265da4e976eb4b9">从Chrome源码看HTTPS</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;前言：还没有用上https的可以看一下我之前写的&lt;a href=&quot;http://hjingren.cn/2019/06/11/Nginx%E4%B8%8B%E5%8D%87%E7%BA%A7https/&quot;&gt;Nginx下升级https&lt;/a&gt;，把你的http升级到https。</summary>
      
    
    
    
    
    <category term="随笔" scheme="http://yoursite.com/tags/%E9%9A%8F%E7%AC%94/"/>
    
    <category term="https" scheme="http://yoursite.com/tags/https/"/>
    
  </entry>
  
</feed>
