-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlocal-search.xml
148 lines (71 loc) · 102 KB
/
local-search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>How I Saved My Work Accidentally Discarded Using VSCode</title>
<link href="/2024/08/08/How%20I%20Saved%20My%20Work%20Accidentally%20Discarded%20Using%20VSCode/"/>
<url>/2024/08/08/How%20I%20Saved%20My%20Work%20Accidentally%20Discarded%20Using%20VSCode/</url>
<content type="html"><![CDATA[<p>Today, when I was dealing with my work progress, I accidentally discard all the files I wanted to commit, because part of the code was just compiled locally and didn’t need to submit.</p><p>At that moment, I really wanted to die, because this is the result of a day’s work.</p><p><img src="https://cdn.danni.cool/file/f96e6d89526f6f62ed0d70898a273e47.png" alt="Untitled.png"></p><h1 id="Life-still-goes-on"><a href="#Life-still-goes-on" class="headerlink" title="Life still goes on"></a>Life still goes on</h1><p>Because all the files did not commit to git, git is certainly unable to help, so I’m checking the IDE to see if there‘s any black magic, I’ve discovered that VSCode has a powerful file history function. Interesting, this looks promising.</p><p>Firstly, I’m relieved to confirm that I already turned on the Local History in the settings, I remember this being the default setting, which I haven’t changed.</p><p><img src="https://cdn.danni.cool/file/842718c052d9041fe6415e0340f027df.jpg" alt="Jietu20240808-225408.jpg"></p><p>The corresponding file path is in <code>/Users/yourname/Library/Application\ Support/Code/User/History</code> , after opening it includes a bunch of md5 named folders.</p><p><img src="https://cdn.danni.cool/file/7e951b4d3d3e94e5af001b396a90f49e.png" alt="Untitled.png"></p><p>For easier searching, it is recommended to use the list view and descending order by modification date,Open the first <code>entries.json</code> , I realized it records the relationship between the source file and all the historical files named by their hashes., I tried to recover the latest one.</p><p><img src="https://cdn.danni.cool/file/43f79dba73638cc3ec7771ef0fe6d65b.png" alt="Untitled.png"></p><p>After saving and git diff,the file content is exactly what I discorded before,just based on this point alone, VSCode deserves the highest praise😎! It truly lives up to its reputation as the universal IDE.</p><h1 id="Ending"><a href="#Ending" class="headerlink" title="Ending"></a>Ending</h1><p>Valuing my life, I choose to use 👑 <a href="https://code.visualstudio.com/"><strong>vscode</strong></a> <strong>👑</strong></p>]]></content>
<categories>
<category>Tech</category>
</categories>
<tags>
<tag>vscode</tag>
<tag>IDE</tag>
<tag>Code Recovery</tag>
</tags>
</entry>
<entry>
<title>Fixed the broken search in nextjs-notion-starter-kit</title>
<link href="/2023/10/17/Fixed%20the%20broken%20search%20in%20nextjs-notion-starter-kit/"/>
<url>/2023/10/17/Fixed%20the%20broken%20search%20in%20nextjs-notion-starter-kit/</url>
<content type="html"><![CDATA[<blockquote><p>Last Update: Finally, I’ve decided to abandon the Next.js Notion starter kit for my website. Instead, I recommend trying out the solution of Notion + Elog + Hexo. It allows for easy DIY and offers better page speed. I’ll write an article to introduce it next time!</p></blockquote><h2 id="Reputation"><a href="#Reputation" class="headerlink" title="Reputation"></a>Reputation</h2><p>Thanks to @<strong>Travis Fischer</strong> for bringing us this awesome project that combines Next.js and Notion. It makes building a custom website quite simple.</p><h2 id="Reason"><a href="#Reason" class="headerlink" title="Reason"></a>Reason</h2><p>If your <a href="https://github.com/transitive-bullshit/nextjs-notion-starter-kit"><code>nextjs-notion-starter-kit</code></a> version is <strong>v2.0.0</strong> or below, like my project is <strong>v2.0.0</strong>, you’ll find your search function is broken. This is because of <strong>the search API params was changed by Notion</strong>.</p><blockquote><p>💡 As the pic shows, param <strong>sort</strong> turned <code>String</code> into an <code>Object</code>.</p></blockquote><p><img src="https://cdn.danni.cool/file/8b232e71595cf51a7fb21bfca5717fa2.png" alt="Untitled.png"></p><p>Although the guy @Armster15 fixed this problem in the Project’s dependency <a href="https://github.com/NotionX/react-notion-x/compare/v6.16.0...v6.16.1"><code>react-notion-x@6.16.1</code></a> , but the <a href="https://www.npmjs.com/package/notion-client">npm version</a> is still v6.16.0 until 17/10/2023.</p><h2 id="Solve"><a href="#Solve" class="headerlink" title="Solve"></a>Solve</h2><p>We can not simply update the dependency version to solve this problem, I believe the author will fixed the issue in the future. but currently we need a hotfix. The way is quite simple:</p><p>1.find a package called <code>patch-package</code> in npm, and install it.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs bash">//npm <br>npm i patch-package --legacy-peer-deps<br><br>//yarn<br>yarn add patch-package<br></code></pre></td></tr></table></figure><p>2.add a post-install hook in <code>package.json</code>, like this</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></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"name"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"nextjs-notion-starter-kit"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"version"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"2.0.0"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"private"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"description"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"The perfect starter kit for building beautiful websites with Next.js and Notion."</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"author"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Travis Fischer <travis@transitivebullsh.it>"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"repository"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"transitive-bullshit/nextjs-notion-starter-kit"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"license"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"MIT"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"engines"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"node"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">">=16"</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"scripts"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"postinstall"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"patch-package"</span><span class="hljs-punctuation">,</span> <span class="hljs-comment">// <== look here, add this one only.</span><br> <span class="hljs-attr">"dev"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"next dev"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"build"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"next build"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"start"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"next start"</span><span class="hljs-punctuation">,</span><br></code></pre></td></tr></table></figure><p>3.download the patch file and put it into your project dir <code>/patches</code> , as the pic shows.</p><h3 id="Download-The-Patch-File"><a href="#Download-The-Patch-File" class="headerlink" title="Download The Patch File"></a>Download The Patch File</h3><blockquote><p>File on Google Drive<br><a href="https://drive.google.com/file/d/17AQFkbqJVH53DGIKYGZBHGTH1w6Rgpzu/view">file</a></p></blockquote><pre><code class="hljs">[file](https://drive.google.com/file/d/17AQFkbqJVH53DGIKYGZBHGTH1w6Rgpzu/view)</code></pre><p><img src="https://cdn.danni.cool/file/f4223b235e88ff84868b2d931cdd71bc.png" alt="Untitled.png"></p><p>4.Run <code>npm i --legacy-peer-deps</code> again**,** the patches will apply to the <code>node_modules</code>.</p><p>5.Test the your project in development, it works.</p><p><img src="https://cdn.danni.cool/file/f49eb72349e81f6e06d1a21fb02c1db5.png" alt="Untitled.png"></p>]]></content>
<categories>
<category>Tech</category>
</categories>
<tags>
<tag>Next.js</tag>
<tag>Notion</tag>
</tags>
</entry>
<entry>
<title>A Simple Install Test Between Npm, Yarn and Pnpm</title>
<link href="/2023/10/15/A%20Simple%20Install%20Test%20Between%20Npm,%20Yarn%20and%20Pnpm/"/>
<url>/2023/10/15/A%20Simple%20Install%20Test%20Between%20Npm,%20Yarn%20and%20Pnpm/</url>
<content type="html"><![CDATA[<p>I’m working on my new open-source project <a href="https://github.com/danni-cool/docker-wechatbot-webhook"><strong>docker-wechatbot-webhook</strong></a> recently, it’s a Node.js app that allows you to easily send messages to <a href="https://www.wechat.com/en/">Wechat</a> through http requests. </p><h1 id="A-Discussion-Of-Dockerfile"><a href="#A-Discussion-Of-Dockerfile" class="headerlink" title="A Discussion Of Dockerfile"></a>A Discussion Of Dockerfile</h1><p>Three months ago, my friend J were discussed with me about this project’s dockerfile. He thought the <code>npm install</code> layer may slowed down the docker’s build speed in Github Actions.</p><figure class="highlight docker"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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><code class="hljs docker"><span class="hljs-keyword">FROM</span> node:<span class="hljs-number">18</span>-alpine<br><br><span class="hljs-keyword">WORKDIR</span><span class="language-bash"> /app</span><br><br><span class="hljs-keyword">COPY</span><span class="language-bash"> package*.json ./</span><br><br><span class="hljs-keyword">RUN</span><span class="language-bash"> npm install</span><br></code></pre></td></tr></table></figure><p><strong>J</strong>:<em>“Daniel, It’s 2023, why are you still using the broken slow npm to install the node_modules, it’s the time of pnpm — faster and smaller”.</em> </p><p><strong>Me:</strong> <em>“well, I’ve heard this before, so let’s try it”.</em></p><p>And for the principle of “<strong>talk is cheap, show me the PR</strong>”, he helped to optimize the dockerfile finally.😂</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></pre></td><td class="code"><pre><code class="hljs bash">FROM node:18-alpine<br><br>WORKDIR /app<br><br>COPY package.json pnpm-lock.yaml ./<br><br>RUN npm install -g pnpm && pnpm install && pnpm store prune && npm uninstall pnpm -g<br></code></pre></td></tr></table></figure><h1 id="Pnpm-Improves-Nothing"><a href="#Pnpm-Improves-Nothing" class="headerlink" title="Pnpm Improves Nothing?"></a>Pnpm Improves Nothing?</h1><p><img src="https://cdn.danni.cool/file/7b31bc2ab1890888ba0a53199fafe44b.png" alt="Untitled.png"></p><p>We know, pnpm <strong>uses symbol links point to a global space to resolve the node_modules in every project</strong>, <strong>so that each project installs modules only once in specific version, that’s one of the advantages why pnpm takes little space and install node_modules so quick.</strong></p><p>With the high expectation of pnpm’s magic, I found the docker build speed is almost unchanged in Github Actions. 🤨</p><p><img src="https://cdn.danni.cool/file/21ce41b0678696e994551a72f63a4409.jpg" alt="Jietu20231206-154642.jpg"></p><p>Why was that? In Github Runner, each building task owns a new environment. it means whether to choose npm , yarn or pnpm , they all need to reinstall again. </p><h1 id="Github-Actions-Cache"><a href="#Github-Actions-Cache" class="headerlink" title="Github Actions Cache"></a>Github Actions Cache</h1><p>If you install node_modules in Github Actions workflow, there comes a way to cache dependencies and build outputs 👉 <a href="https://github.com/actions/cache">https://github.com/actions/cache</a>.</p><p>What my workflow does is to build dockerfile, upload to dockerHub, and then push a release PR. The <code>npm install</code> step is only running in docker building progress. So this cache method is helpless.</p><p>Docker actually support DLC (docker layer caching) , but Github Actions don’t support it in native. </p><p>You can still rely on some third party service to cache layer, however, it makes this node app more complex. it’s not necessary right now.</p><p>At least, it claims that I don’t need to alternate it from npm to pnpm at current stage, I know it’s not pnpm ’s fault. one of the reasons is my app is not too much complex, I believed it can speed up my works in local dev.</p><h1 id="Performance-In-Local-Dev"><a href="#Performance-In-Local-Dev" class="headerlink" title="Performance In Local Dev"></a>Performance In Local Dev</h1><p>I had tested the performance between these 3 package managers in development. built them in 3 different conditions separately for comparing the installation size and time.</p><ol><li>install modules with no cache and no lock file.</li><li>install modules with no cache but with lock file.</li><li>clear cache and remove package manager after building finished.</li></ol><table><thead><tr><th>pkg manager</th><th>no cache and no lock file</th><th>no cache but with lock File</th><th>clear cache and remove pkg manager</th><th>clear cache command</th><th>problem after remove cache</th></tr></thead><tbody><tr><td><strong>npm</strong></td><td>669 MB / 74.3s</td><td>655.2MB / 36.3s</td><td>568.41MB</td><td><em>npm</em> <em>cache</em> <em>clean</em> <em>–force</em></td><td></td></tr><tr><td><strong>yarn</strong></td><td>616MB / 59.7s</td><td>616MB / 36s</td><td>467.75MB</td><td>yarn cache clean</td><td></td></tr><tr><td><strong>pnpm</strong></td><td>732 MB / 42.5s</td><td>701.67MB / 36.5s</td><td>566.63MB</td><td><em>pnpm</em> <em>store</em> <em>prune</em></td><td>Error: Cannot find module ‘file-box’</td></tr></tbody></table><h3 id="No-cache-and-no-lock-file"><a href="#No-cache-and-no-lock-file" class="headerlink" title="No cache and no lock file"></a>N<strong>o cache and no lock file</strong></h3><p>just like the scene that we init a new project, pnpm is far ahead in installation time than yarn about <strong>40.47%,</strong> npm about <strong>74.82%,</strong> it’s a incredible score! But yarn has the minimal installation size, pnpm is at the bottom.</p><h3 id="No-cache-but-with-lock-file"><a href="#No-cache-but-with-lock-file" class="headerlink" title="No cache but with lock file"></a>No cache but with lock file</h3><p>like the most common build scene, the installation time is roughly similar, but the size is still minimal via yarn installed, pnpm is at the bottom</p><h3 id="Clear-cache-and-remove-package-manage"><a href="#Clear-cache-and-remove-package-manage" class="headerlink" title="Clear cache and remove package manage"></a>Clear cache and remove package manage</h3><p><strong>yarn</strong> is ahead over others in installation size, however, the node_modules that installed via pnpm comes an error when I try to launch my project. but I found it was my fault after I carefully debug the reason. </p><p>I was tried to import the module called “<strong>file-box”</strong> which is not declared in the package.json**.** Actually, it’s a dependency of <a href="https://github.com/wechaty/wechaty"><strong>Wechaty</strong></a>. </p><p><img src="https://cdn.danni.cool/file/18a602bd544140a79ea214530bb70ce9.png" alt="Untitled.png"></p><p><strong>Pnpm helped me find an potential issues, it called “</strong><a href="https://broadcrunch.com/technology/computing/phantom-dependencies-in-nodejs-and-how-pnpm-prevents-them/"><strong>phantom dependencies</strong></a><strong>”.</strong></p><p>Phantom dependencies is dangerous because it will cause error or crash if “Wechaty” don’t depends “file-box” anymore. Although it runs well in local dev.</p><h1 id="Final-Thoughts"><a href="#Final-Thoughts" class="headerlink" title="Final Thoughts"></a><strong>Final Thoughts</strong></h1><ol><li>Use <code>pnpm</code> in most scenarios because it offers faster installation speeds, uses less disk space cross multiple projects, it helps prevent potential issues.</li><li>Use <code>yarn</code> if you wanna have a minimal app size, like Docker image or other one-time build package.</li></ol>]]></content>
<categories>
<category>Tech</category>
</categories>
<tags>
<tag>Yarn</tag>
<tag>Pnpm</tag>
<tag>Github Actions</tag>
</tags>
</entry>
<entry>
<title>基于多页项目的微前端改造</title>
<link href="/2019/09/30/%E5%9F%BA%E4%BA%8E%E5%A4%9A%E9%A1%B5%E9%A1%B9%E7%9B%AE%E7%9A%84%E5%BE%AE%E5%89%8D%E7%AB%AF%E6%94%B9%E9%80%A0/"/>
<url>/2019/09/30/%E5%9F%BA%E4%BA%8E%E5%A4%9A%E9%A1%B5%E9%A1%B9%E7%9B%AE%E7%9A%84%E5%BE%AE%E5%89%8D%E7%AB%AF%E6%94%B9%E9%80%A0/</url>
<content type="html"><![CDATA[<p>Techniques, strategies and recipes for building a modern web app with multiple teams using different JavaScript frameworks.—— Micro Frontends</p><p>一种可以让多个团队使用不同JavaScript框架的技术、策略和方法 —— 微前端</p><h1 id="一、前言"><a href="#一、前言" class="headerlink" title="一、前言"></a>一、前言</h1><p>丁香人才运营管理后台是一个面向运营使用的后台管理系统,方便运营在后台去配置一些运营活动从而减少开发手动写代码去配置功能的问题。管理后台的前端技术架构是一个基于vue全家桶的多页应用,重构前有33 个独立入口页面,并未做相关整合。</p><p>由于页面之间的互相跳转,应用之间切换造成了浏览器重刷,运营在流程操作上存在断点。同时产品也希望想将要原先分散在不同入口之间的应用整合到一个页面,目前还存在多种不同组合的情况。</p><p>如果每个入口都增加一个侧边栏组件,开发成本就是N * repeat。而基于原先的多页应用,使用微前端的架构可以平滑过渡,同时也不会产生侧边栏代码一式多份的问题。借着这个机会我在微前端做出了一些探索。</p><h1 id="二、当前的痛点"><a href="#二、当前的痛点" class="headerlink" title="二、当前的痛点"></a>二、当前的痛点</h1><ol><li><strong>客户 or 产品需求(定制化)</strong></li><li>由于产品之间相互跳转,应用之间切换会造成浏览器重刷,流程体验上会存在断点,这也是运营和产品上的痛点 ✅</li><li>每个功能模块比较独立、有特色。如果说将来需要拆分组成一个大单页的话,拆分+组合工作量大,后期tab菜单再整合的时候还需要继续拆分+组合。 ✅</li><li><strong>开发&运维成本</strong></li><li>每次开发一个新模块都需要向后端提供一个新入口文件,后端告知前端路由地址,新模块部署成本。 ✅</li><li>(未来)<del>多人合作开发同一种产品,但现有发布的耦合性降低了交付效率</del> ✅</li><li><strong>多页应用(multi-page application)的问题</strong></li><li>使用多页应用的方式去搭建后台产品,随着应用的复杂度的提升,代码的构建变得越来越慢 ✅</li><li>公共资源无法复用,页面刷新时,静态资源需要反复加载,降低了交互体验 ✅</li><li>多个页面状态共享变得很困难 ✅</li></ol><p><strong>相对比SPA:</strong></p><p>SPA 则天生具备体验上的优势,应用直接无刷新切换,能极大的保证多产品之间流程操作串联时的流程性。缺点则在于各应用技术栈之间是强耦合的。</p><p>所以有没有一种办法既可以保证拥有单页应用的体验,又可以兼容多个技术战的兼容呢?</p><p>答案是微前端。</p><h1 id="三、微前端是什么?"><a href="#三、微前端是什么?" class="headerlink" title="三、微前端是什么?"></a>三、微前端是什么?</h1><p>微前端是一种新架构风格,最早由 ThoughtWorks 于2016年提出,将后端微服务的理念应用于浏览器端,即将 Web 应用<strong>由单一的单体应用转变为多个小型前端应用聚合为一</strong>的应用。 其中众多独立交付的前端应用组合成一个大型整体。</p><p><img src="https://cdn.danni.cool/file/80cdb43d9e6255002a0d5341af1b730c.png" alt="1572839045880-4d255d03-31b2-4f5a-9d68-c501954c8217.png"></p><p>在 ThoughtWorks 正式发布的最新一期(2019年4月)<a href="https://www.thoughtworks.com/cn/radar">技术雷达</a>中,微前端已经进入到<strong>采纳</strong>(adopt)阶段,而且它<strong>强烈推荐</strong>我们在合适的项目中应该使用这个方案。</p><p><img src="https://cdn.danni.cool/file/b77d9bd2eba157bf88929d9acdf83350.png" alt="1572759229607-09c5e615-1f16-4b1a-ba68-e9eea382669c.png"></p><h1 id="四、微前端的价值"><a href="#四、微前端的价值" class="headerlink" title="四、微前端的价值"></a>四、微前端的价值</h1><p>微前端架构旨在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个<strong>巨石应用</strong>( Frontend Monolith )后,随之而来的应用不可维护的问题。这类问题在企业级 Web 应用中尤其常见。</p><ul><li><strong>技术选型灵活</strong>:主框架不限制接入应用的技术栈,可以使用市面上所有的前端技术栈子应用具备完全自主权</li><li><strong>独立开发、独立部署</strong>:子应用仓库独立,每个前端应用都可独立开发,部署完成后主框架自动完成同步更新</li><li><strong>独立运行</strong>:每个子应用之间状态隔离,运行时状态不共享,只需要遵循统一的接口规范或者框架,相互之间是不存在依赖关系的。</li><li><strong>容错</strong>:单个应用模块挂了不会影响其他模块</li><li><strong>可扩展性</strong>:单每一个服务可以独立横向扩展满足业务伸缩性</li></ul><h1 id="五、透视微前端架构"><a href="#五、透视微前端架构" class="headerlink" title="五、透视微前端架构"></a>五、透视微前端架构</h1><p>微前端有多种实现方式,这次主要讲现在社区里用的比较多的一种实现方式——使用模块加载器聚合多个单页应用这种方式。</p><p><img src="https://cdn.danni.cool/file/9a46bfd47807ceaf28040e08adbf3080.png" alt="1572953521033-2144d2cc-7365-4a50-87d5-1d85ae3ebec2.png"></p><p>这种实现方式下,核心的模块加载器在路由规则匹配的条件下负责<strong>注册</strong>、<strong>装载</strong>和<strong>卸载</strong>子应用。</p><p>相比较于单页应用是<strong>应用分发路由,</strong>微前端的实现方式则是<strong>路由分发应用。</strong></p><h1 id="六、微前端两种集成方式"><a href="#六、微前端两种集成方式" class="headerlink" title="六、微前端两种集成方式"></a>六、微前端两种集成方式</h1><p>讲完架构上的特点,我们聊一聊微前端在开发到构建流程上的一个变化。</p><h2 id="1-构建时集成"><a href="#1-构建时集成" class="headerlink" title="1.构建时集成"></a>1.构建时集成</h2><p>在开发时,应用都是以单一、微小应用的形式存在,而在运行时,则通过构建系统合并这些应用,组合成一个新的应用。</p><p><img src="https://cdn.danni.cool/file/7bdd10b5e2fec5487dad257fbcbc2dd6.png" alt="1572951849540-b1582413-5f17-4ba8-bd2a-cb26751d5152.png"></p><p><strong>这种开发模式有以下特点:</strong></p><ol><li>代码由统一仓库管理,只有一个git项目托管</li><li>具体不同应用开发通过git 功能分支去约束</li><li>使用构建系统实现依赖的打包和分离</li><li>比较适合项目初期代码量不是很大,模块少,编译时间短的情况下</li></ol><p><strong>优点:</strong></p><p>✅ 依赖版本很容易管理,也能享受构建工具在依赖和code split上带来的好处</p><p>✅ 一次最多只需要构建一个项目</p><p><strong>缺点:</strong></p><p>⚠️在开发模块时改动portal的顶层路由等会产生冲突</p><p>⚠️代码构建时间跟着模块增加而增加</p><h2 id="2-运行时集成"><a href="#2-运行时集成" class="headerlink" title="2.运行时集成"></a>2.运行时集成</h2><p>子应用自己打包,并将各个代码自己上传到服务器上,由在运行时,我们只需要加载相应的业务模块即可。对应的,在更新代码的时候,我们只需要更新对应的模块即可。</p><p><img src="https://cdn.danni.cool/file/f5bd437c5e8c2dc1b6ea69a376d3012b.png" alt="1573058236756-a2efc7e0-27c1-4440-8b0d-03c544101ca5.png"></p><h2 id="3-两者对比"><a href="#3-两者对比" class="headerlink" title="3.两者对比"></a>3.两者对比</h2><p><img src="https://cdn.danni.cool/file/15642b73aebb33cc93e974a2a1c753bd.png" alt="1573058351895-29ff8a63-e549-40a9-8b00-270c3a50498f.png"></p><h1 id="七、技术方案"><a href="#七、技术方案" class="headerlink" title="七、技术方案"></a>七、技术方案</h1><p>对于我来说,这些东西都是可选择的,比如独立部署和运行是适合项目扩展到多团队,大代码库的方案,那现在还是可以使用内置构建集成的方式,享受一下webpack tree-shaking 和 code Split 的便利,只不过说在代码层面要做好子应用的概念,这样在抽离成为可以独立部署的模块时的再次改造成本不会很大。</p><p>目前我选择了</p><ul><li>子应用使用统一的技术栈vue</li><li>大仓库模式(构建时集成)</li><li>vuex 作为状态管理(原先是redux)</li><li>singleSPA.js 作为模块加载器</li></ul><p>微前端有许多实现方式,今天我们只讲基于 <a href="https://single-spa.js.org/">singleSPA</a> 框架做的「构建时集成」的整合方案。</p><p>singleSPA 是一个模块加载器,在portal 页面的管理其他框架的生命周期,加载或者卸载他们。</p><h2 id="1-建立门户(portal)应用"><a href="#1-建立门户(portal)应用" class="headerlink" title="1.建立门户(portal)应用"></a>1.建立门户(portal)应用</h2><p><img src="https://cdn.danni.cool/file/25a43c84ac6d4ed69272f46992dde750.jpg" alt="https___cdn_%E5%89%AF%E6%9C%AC.jpg"></p><p>“Portal项目”提供注册的接口,“子项目”进行注册,最终聚合成一个类单页应用。在整套机制中,比较核心的部分是路由注册机制,“子项目”的路由应该由自己控制,而整个系统的导航是“Portal项目”提供的。</p><p>同时,我们还要</p><ul><li>呈现公用的页面元素,比如侧边栏、头部栏目</li><li>路由功能,告诉每个微应用何时启动</li><li>封装共全局调用的底层方法和服务</li><li>解决鉴权问题</li></ul><h3 id="代码目录的重新规划"><a href="#代码目录的重新规划" class="headerlink" title="代码目录的重新规划"></a>代码目录的重新规划</h3><p>以类似单页应用的结构规划portal应用</p><p><img src="https://cdn.danni.cool/file/0b6ac004db67e96717a7740116ee3109.png" alt="1572948379567-60b7ae85-176b-442b-a1db-a58bc0e74b51.png"></p><h3 id="安装依赖"><a href="#安装依赖" class="headerlink" title="安装依赖"></a>安装依赖</h3><p>项目中引入 singleSPA 和其提供的vue实例注册插件</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs text">npm i single-spa single-spa-vue -d<br></code></pre></td></tr></table></figure><h3 id="启动文件-app-js"><a href="#启动文件-app-js" class="headerlink" title="启动文件 app.js"></a>启动文件 app.js</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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">import</span> { registerApplication, start } <span class="hljs-keyword">from</span> <span class="hljs-string">'single-spa'</span>;<br><br><span class="hljs-title function_">registerApplication</span>(<br> <span class="hljs-string">"applicationName"</span>, <span class="hljs-comment">// 应用唯一名字</span><br> <span class="hljs-function">()=></span><span class="hljs-keyword">import</span>(<span class="hljs-string">"view/app1/main.js"</span>), <span class="hljs-comment">//以异步组件的方式引进来</span><br> location.<span class="hljs-property">pathname</span>.<span class="hljs-title function_">indexOf</span>(<span class="hljs-string">"/app1/"</span>) === <span class="hljs-number">0</span> <span class="hljs-comment">// 应用在路由匹配时展示,</span><br>);<br><br><span class="hljs-comment">//启动应用</span><br><span class="hljs-title function_">start</span>();<br></code></pre></td></tr></table></figure><h3 id="模板文件-index-html"><a href="#模板文件-index-html" class="headerlink" title="模板文件 index.html"></a>模板文件 index.html</h3><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs html"><span class="hljs-meta"><!DOCTYPE <span class="hljs-keyword">html</span>></span><br><span class="hljs-tag"><<span class="hljs-name">html</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">head</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width,initial-scale=1.0"</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">title</span>></span>丁香人才管理后台 - jobmd_admin<span class="hljs-tag"></<span class="hljs-name">title</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">head</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">body</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"app"</span>></span><br><br> <span class="hljs-comment"><!--此部分动态插入,减少对后端依赖</span><br><span class="hljs-comment"> <div id="sideBar"></div></span><br><span class="hljs-comment"> <div class="container"></span><br><span class="hljs-comment"> <div id="container"></div></span><br><span class="hljs-comment"> </div></span><br><span class="hljs-comment"></div></span><br><span class="hljs-comment"> --></span><br><br> <span class="hljs-comment"><!-- webpack script files will be auto injected --></span><br> <span class="hljs-tag"></<span class="hljs-name">body</span>></span><br><span class="hljs-tag"></<span class="hljs-name">html</span>></span><br><br></code></pre></td></tr></table></figure><h2 id="2-包装现有vue应用"><a href="#2-包装现有vue应用" class="headerlink" title="2.包装现有vue应用"></a>2.包装现有vue应用</h2><h3 id="vue模块"><a href="#vue模块" class="headerlink" title="vue模块"></a>vue模块</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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-comment">//page1/page1.app.js</span><br><span class="hljs-keyword">import</span> <span class="hljs-title class_">Vue</span> <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span>;<br><span class="hljs-keyword">import</span> <span class="hljs-title class_">App</span> <span class="hljs-keyword">from</span> <span class="hljs-string">'./App.vue'</span>;<br><span class="hljs-keyword">import</span> router <span class="hljs-keyword">from</span> <span class="hljs-string">'./router'</span>;<br><span class="hljs-keyword">import</span> store <span class="hljs-keyword">from</span> <span class="hljs-string">'store'</span>;<br><span class="hljs-keyword">import</span> singleSpaVue <span class="hljs-keyword">from</span> <span class="hljs-string">'single-spa-vue'</span>;<br><br><span class="hljs-keyword">const</span> vueLifecycles = <span class="hljs-title function_">singleSpaVue</span>({<br> <span class="hljs-title class_">Vue</span>,<br> <span class="hljs-attr">appOptions</span>: {<br> <span class="hljs-attr">render</span>: <span class="hljs-function"><span class="hljs-params">h</span> =></span> <span class="hljs-title function_">h</span>(<span class="hljs-title class_">App</span>),<br> <span class="hljs-attr">el</span>: <span class="hljs-string">'#container'</span><br> router,<br> },<br>});<br><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> bootstrap = vueLifecycles.<span class="hljs-property">bootstrap</span>;<br><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> mount = vueLifecycles.<span class="hljs-property">mount</span>;<br><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> unmount = vueLifecycles.<span class="hljs-property">unmount</span>;<br><br></code></pre></td></tr></table></figure><p>到这里,最简单的只有一个微应用的项目已经完成了,有木有很简单?但多个应用之间切换其实还有很多东西要做,路由的切换得自己写。</p><h2 id="3-公共应用-侧边栏"><a href="#3-公共应用-侧边栏" class="headerlink" title="3.公共应用-侧边栏"></a>3.公共应用-侧边栏</h2><p><img src="https://cdn.danni.cool/file/757c5ef20bcd6a4b5af8874112e7794a.png" alt="image.png"></p><ol><li>侧边栏是portal常显的子应用,所以在 <code>app.js</code> 使用 <code>registerApplication</code> 注册的时候,第三参数需要始终为 <code>true</code> 这样即使路由变化,侧边栏页永远不会消失。</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-comment">// side模块</span><br><span class="hljs-title function_">registerApplication</span>(<span class="hljs-string">"sideBar"</span>, <span class="hljs-function">()=></span><span class="hljs-keyword">import</span>(<span class="hljs-string">"view/app1/sideBar.app.js"</span>),<br> <span class="hljs-literal">true</span> <span class="hljs-comment">// 始终展示则返回true</span><br>);<br><br><span class="hljs-comment">// 其他模块</span><br><span class="hljs-title function_">registerApplication</span>(<span class="hljs-string">"otherApplication"</span>, <span class="hljs-function">()=></span><span class="hljs-keyword">import</span>(<span class="hljs-string">"view/app1/otherApplication.app.js"</span>),<br> <span class="hljs-function">() =></span> location.<span class="hljs-property">pathname</span>.<span class="hljs-title function_">indexOf</span>(<span class="hljs-string">"/otherApplication/"</span>) === <span class="hljs-number">0</span><br>);<br><br></code></pre></td></tr></table></figure><ol><li>侧边栏有多种组合方式,所以需要使用配置项来维护。</li></ol><p>运营C端</p><p><img src="https://cdn.danni.cool/file/2e0f7c2799557f47a96cc70b2d4d99e4.png" alt="Untitled.png"></p><p>任务中心</p><p><img src="https://cdn.danni.cool/file/4c3aaa660a53872573f924251d12dadc.png" alt="Untitled.png"></p><p>小程序</p><p><img src="https://cdn.danni.cool/file/b927a785bc194941a83d1613a34de2af.png" alt="Untitled.png"></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {<br> <span class="hljs-comment">// 根路由重定向</span><br> <span class="hljs-attr">homeRedirect</span>: <span class="hljs-string">'#/miniProgramMHR/userManage'</span>,<br><br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 以下配置menu</span><br><span class="hljs-comment"> * 注意:key值不能相同,不然这里注册的高亮和展开二级菜单必定显示不正常</span><br><span class="hljs-comment"> * */</span><br> <span class="hljs-attr">menuList</span>: [<br> {<br> <span class="hljs-attr">key</span>: <span class="hljs-string">'home'</span>,<br> <span class="hljs-attr">menuName</span>: <span class="hljs-string">'管理后台首页'</span>,<br> <span class="hljs-attr">path</span>: <span class="hljs-string">'/admin/guessoneguess.do'</span> <span class="hljs-comment">// 外链不以#开头</span><br> },<br> {<br> <span class="hljs-attr">key</span>: <span class="hljs-string">'miniProgramMHR'</span>,<br> <span class="hljs-attr">menuName</span>: <span class="hljs-string">'MHR小程序管理'</span>,<br> <span class="hljs-attr">children</span>: [<br> {<br> <span class="hljs-attr">menuName</span>: <span class="hljs-string">'用户管理'</span>,<br> <span class="hljs-attr">key</span>: <span class="hljs-string">'userManage'</span>,<br> <span class="hljs-attr">path</span>: <span class="hljs-string">'#/miniProgramMHR/userManage'</span>,<br> <span class="hljs-attr">component</span>: <span class="hljs-function">() =></span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'../../view/miniProgramMHR/userManage/app.js'</span>)<br> }<br> ]<br> }<br> ]<br>}<br></code></pre></td></tr></table></figure><h2 id="4-路由和组件注册"><a href="#4-路由和组件注册" class="headerlink" title="4.路由和组件注册"></a>4.路由和组件注册</h2><p>因为考虑到url的history模式对服务器有改造成本,所以下面都是以hash模式去做路由的管理。</p><p>应用的路由在singleSPA里注册,“应用的子项目”的路由应该由自己控制</p><p><img src="https://cdn.danni.cool/file/e77f04ffd859a6d46f126a2b799afc8b.jpg" alt="123123.jpg"></p><h3 id="query部分"><a href="#query部分" class="headerlink" title="query部分"></a>query部分</h3><p>区分不同的侧边栏,从而加载不同的侧边栏配置</p><p><img src="https://cdn.danni.cool/file/8db07f9b0faf054389b3597e3407e773.png" alt="1573051069905-cbcb8650-46ed-45f3-ae20-389d8e17929b.png"></p><h3 id="hash部分"><a href="#hash部分" class="headerlink" title="hash部分"></a>hash部分</h3><p>用来定义父子路由</p><p><strong>父路由 router.mhr.js</strong></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><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><code class="hljs javascript"><span class="hljs-attr">menuList</span>: [<br> {<br> <span class="hljs-attr">key</span>: <span class="hljs-string">'home'</span>,<br> <span class="hljs-attr">menuName</span>: <span class="hljs-string">'管理后台首页'</span>,<br> <span class="hljs-attr">path</span>: <span class="hljs-string">'/admin/guessoneguess.do'</span> <span class="hljs-comment">// 外链不以#开头</span><br> },<br> {<br> <span class="hljs-attr">key</span>: <span class="hljs-string">'miniProgramMHR'</span>,<br> <span class="hljs-attr">menuName</span>: <span class="hljs-string">'MHR小程序管理'</span>,<br> <span class="hljs-attr">children</span>: [<br> {<br> <span class="hljs-attr">menuName</span>: <span class="hljs-string">'用户管理'</span>,<br> <span class="hljs-attr">key</span>: <span class="hljs-string">'userManage'</span>,<br> <span class="hljs-attr">path</span>: <span class="hljs-string">'#/miniProgramMHR/userManage'</span>,<br> <span class="hljs-attr">component</span>: <span class="hljs-function">() =></span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'../../view/miniProgramMHR/userManage/app.js'</span>)<br> }<br> ]<br> }<br>]<br></code></pre></td></tr></table></figure><p><strong>子路由 page1/page1.router.js</strong></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">import</span> <span class="hljs-title class_">Router</span> <span class="hljs-keyword">from</span> <span class="hljs-string">'vue-router'</span><br><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Router</span>({<br> <span class="hljs-attr">routes</span>: [<br> {<br> <span class="hljs-attr">path</span>: <span class="hljs-string">'/miniProgramMHR/userManage'</span>,<br> <span class="hljs-attr">name</span>: <span class="hljs-string">'list'</span>,<br> <span class="hljs-attr">component</span>: <span class="hljs-title class_">List</span><br> },<br> {<br> <span class="hljs-attr">path</span>: <span class="hljs-string">'/miniProgramMHR/userManage/detail'</span>,<br> <span class="hljs-attr">name</span>: <span class="hljs-string">'detail'</span>,<br> <span class="hljs-attr">component</span>: <span class="hljs-title class_">Detail</span><br> },<br> {<br> <span class="hljs-attr">path</span>: <span class="hljs-string">'/miniProgramMHR/userManage/audit'</span>,<br> <span class="hljs-attr">name</span>: <span class="hljs-string">'audit'</span>,<br> <span class="hljs-attr">component</span>: <span class="hljs-title class_">Detail</span><br> },<br> {<br> <span class="hljs-attr">path</span>: <span class="hljs-string">'/miniProgramMHR/userManage/edit'</span>,<br> <span class="hljs-attr">name</span>: <span class="hljs-string">'edit'</span>,<br> <span class="hljs-attr">component</span>: <span class="hljs-title class_">Detail</span><br> }<br> ]<br>})<br></code></pre></td></tr></table></figure><h3 id="全局状态管理方案"><a href="#全局状态管理方案" class="headerlink" title="全局状态管理方案"></a>全局状态管理方案</h3><p>侧边栏应用需要tab名称、父子之间的级联关系和对应的路由,而portal应用则需要知道当前注册哪些组件,对应的路由关系如何,这部分放在portal应用层面去做会比较好,所以他们之间的共享状态我们需要通过一个状态管理去共享,路由变化后,由portal 告诉侧边栏,展示哪些配置项,当前哪个是高亮选项。</p><p><img src="https://cdn.danni.cool/file/db1a18eb3187af85cd6a49258cf42504.png" alt="image.png"></p><p>因为使用了vue全家桶,vuex用起来就很得心应手,但如果是多种技术栈的话,redux也可以选择</p><p>需要的子应用注入store</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">import</span> store <span class="hljs-keyword">from</span> <span class="hljs-string">'@/store/index'</span><br><br><span class="hljs-keyword">const</span> vueLifeCycles = <span class="hljs-title function_">singleSpaVue</span>({<br> <span class="hljs-title class_">Vue</span>,<br> <span class="hljs-attr">appOptions</span>: {<br> <span class="hljs-attr">render</span>: <span class="hljs-function"><span class="hljs-params">h</span> =></span> <span class="hljs-title function_">h</span>(<span class="hljs-title class_">App</span>),<br> <span class="hljs-attr">el</span>: <span class="hljs-string">'#sideBar'</span>,<br> store<br> }<br>})<br></code></pre></td></tr></table></figure><p>portal 应用 app.js在路由变化时commit事件</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">import</span> store <span class="hljs-keyword">from</span> <span class="hljs-string">'./store/index'</span><br><br><span class="hljs-comment">// 设置高亮模块</span><br><span class="hljs-variable language_">window</span>.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'single-spa:routing-event'</span>, <span class="hljs-function">() =></span> {<br> <span class="hljs-comment">// 根路由的话跳转</span><br> <span class="hljs-keyword">if</span> (location.<span class="hljs-property">hash</span> === <span class="hljs-string">'#/'</span> || location.<span class="hljs-property">hash</span> === <span class="hljs-string">''</span>) {<br> <span class="hljs-title function_">navigateToUrl</span>(homeRedirect)<br> <span class="hljs-keyword">return</span><br> }<br> store.<span class="hljs-title function_">commit</span>(<span class="hljs-string">'SET_SIDEBAR'</span>, {<span class="hljs-attr">menu</span>: menuList, <span class="hljs-attr">highlightTab</span>: <span class="hljs-title function_">getCurrentMenuName</span>(menuList)})<br>})<br><br></code></pre></td></tr></table></figure><h2 id="6-公共服务"><a href="#6-公共服务" class="headerlink" title="6.公共服务"></a>6.公共服务</h2><p>现在每个子应用都是独立的,会存在共享一些底层服务的情况,比如我们封装一个axios的 request.js 去拦截一些异常情况统一处理,一些公共服务比如请求loading,toast就比较适合放在portal这层</p><p>通过将实例挂载在window上实现方法上的共享</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> showLoading = <span class="hljs-variable language_">window</span>.<span class="hljs-property">$service</span>.<span class="hljs-property">showLoading</span><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> hideLoading = <span class="hljs-variable language_">window</span>.<span class="hljs-property">$service</span>.<span class="hljs-property">hideLoading</span><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> <span class="hljs-title class_">Message</span> = <span class="hljs-variable language_">window</span>.<span class="hljs-property">$service</span>.<span class="hljs-property">Message</span><br></code></pre></td></tr></table></figure><h2 id="7-样式隔离"><a href="#7-样式隔离" class="headerlink" title="7.样式隔离"></a>7.样式隔离</h2><p>多个子应用之间开发,最不能出现的就是样式之间的相互引用和覆盖,在这一层面上,需要极力去避免,我们要防止这种情况的发生,目前还是通过vue scoped style 去避免,后期应该使用babel在打包阶段就加上命名空间。</p><p><img src="https://cdn.danni.cool/file/e4f1024b4a0a4de1537af45d45433dd9.png" alt="1573058034037-304f9182-da46-4d4f-a4f7-82eeac5b06c0.png"></p><h2 id="8-公共依赖抽离"><a href="#8-公共依赖抽离" class="headerlink" title="8.公共依赖抽离"></a>8.公共依赖抽离</h2><p><img src="https://cdn.danni.cool/file/0a16e249f298b75422cb8cee9135d222.png" alt="1573059190834-fefe4fb7-72c7-4e29-92ea-9105185904e2.png"></p><h2 id="9-完整版-app-js"><a href="#9-完整版-app-js" class="headerlink" title="9.完整版 app.js"></a>9.完整版 app.js</h2><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">import</span> { start, registerApplication, navigateToUrl } <span class="hljs-keyword">from</span> <span class="hljs-string">'single-spa'</span><br><span class="hljs-keyword">import</span> { matchRoute, getRouteMap, getCurrentMenuName } <span class="hljs-keyword">from</span> <span class="hljs-string">'./router/utils'</span><br><span class="hljs-keyword">import</span> store <span class="hljs-keyword">from</span> <span class="hljs-string">'./store/index'</span><br><span class="hljs-keyword">import</span> getUrlQuery <span class="hljs-keyword">from</span> <span class="hljs-string">'@/single-spa/utils/getUrlQuery'</span><br><span class="hljs-keyword">import</span> {sideBarComponent, sideBarMenu} <span class="hljs-keyword">from</span> <span class="hljs-string">'./router/index'</span><br><br><span class="hljs-keyword">import</span> <span class="hljs-string">'./utils/service'</span> <span class="hljs-comment">// 全局提示组件</span><br><span class="hljs-keyword">import</span> <span class="hljs-string">'iview/dist/styles/iview.css'</span><br><span class="hljs-keyword">import</span> <span class="hljs-string">'./style/index.less'</span><br><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 动态插入dom挂载节点,减少对后端依赖</span><br><span class="hljs-comment"> * <div id="app"></span><br><span class="hljs-comment"> * <div id="sideBar"></div></span><br><span class="hljs-comment"> * <div class="container"></span><br><span class="hljs-comment"> * <div id="container"></div></span><br><span class="hljs-comment"> * </div></span><br><span class="hljs-comment"> * </div></span><br><span class="hljs-comment"> * */</span><br><span class="hljs-keyword">function</span> <span class="hljs-title function_">initDomHook</span>(<span class="hljs-params"></span>) {<br> <span class="hljs-keyword">const</span> frag = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">createDocumentFragment</span>()<br> <span class="hljs-keyword">const</span> sideBarEl = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">createElement</span>(<span class="hljs-string">'DIV'</span>)<br> <span class="hljs-keyword">const</span> containerEl = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">createElement</span>(<span class="hljs-string">'DIV'</span>)<br> <span class="hljs-keyword">const</span> appEl = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">createElement</span>(<span class="hljs-string">'DIV'</span>)<br><br> sideBarEl.<span class="hljs-property">id</span> = <span class="hljs-string">'sideBar'</span><br> appEl.<span class="hljs-property">id</span> = <span class="hljs-string">'container'</span><br> containerEl.<span class="hljs-property">className</span> = <span class="hljs-string">'container'</span><br><br> frag.<span class="hljs-title function_">appendChild</span>(sideBarEl)<br> frag.<span class="hljs-title function_">appendChild</span>(containerEl)<br> frag.<span class="hljs-title function_">querySelector</span>(<span class="hljs-string">'.container'</span>).<span class="hljs-title function_">append</span>(appEl)<br><br> <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelector</span>(<span class="hljs-string">'#app'</span>).<span class="hljs-title function_">append</span>(frag)<br>}<br><br><span class="hljs-comment">// 绑定路由事件</span><br><span class="hljs-keyword">function</span> <span class="hljs-title function_">bindRouterEvent</span>(<span class="hljs-params">menuList, homeRedirect</span>) {<br> <span class="hljs-comment">// 设置高亮模块</span><br> <span class="hljs-variable language_">window</span>.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'single-spa:routing-event'</span>, <span class="hljs-function">() =></span> {<br> <span class="hljs-comment">// 根路由的话跳转</span><br> <span class="hljs-keyword">if</span> (location.<span class="hljs-property">hash</span> === <span class="hljs-string">'#/'</span> || location.<span class="hljs-property">hash</span> === <span class="hljs-string">''</span>) {<br> <span class="hljs-title function_">navigateToUrl</span>(homeRedirect)<br> <span class="hljs-keyword">return</span><br> }<br> store.<span class="hljs-title function_">commit</span>(<span class="hljs-string">'SET_SIDEBAR'</span>, {<span class="hljs-attr">menu</span>: menuList, <span class="hljs-attr">highlightTab</span>: <span class="hljs-title function_">getCurrentMenuName</span>(menuList)})<br> })<br>}<br><br><span class="hljs-comment">// 注册路由和侧边栏</span><br><span class="hljs-keyword">function</span> <span class="hljs-title function_">registerRouter</span>(<span class="hljs-params"></span>) {<br> <span class="hljs-comment">// 根据当前路由注册app</span><br> <span class="hljs-title function_">registerApplication</span>(sideBarComponent.<span class="hljs-property">key</span>, sideBarComponent.<span class="hljs-property">component</span>, <span class="hljs-function">() =></span> <span class="hljs-literal">true</span>)<br><br> <span class="hljs-comment">// 注册当前的menu菜单</span><br> <span class="hljs-keyword">const</span> currentApp = <span class="hljs-title function_">getUrlQuery</span>(<span class="hljs-string">'app'</span>) <span class="hljs-comment">// exp: ?app="2c"</span><br> <span class="hljs-keyword">const</span> currentAppCfg = sideBarMenu[currentApp]<br><br> <span class="hljs-keyword">if</span> (!currentApp) {<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">'未发现?app=参数,应用启动失败'</span>)}<br> <span class="hljs-keyword">if</span> (!currentAppCfg) {<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">`未找到?app=<span class="hljs-subst">${currentApp}</span> 匹配的路由,应用启动失败`</span>)}<br><br> <span class="hljs-keyword">const</span> { menuList, homeRedirect } = currentAppCfg<br><br> <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">values</span>(<span class="hljs-title function_">getRouteMap</span>(menuList)).<span class="hljs-title function_">forEach</span>(<span class="hljs-function"><span class="hljs-params">item</span> =></span> {<br> item.<span class="hljs-property">component</span> && <span class="hljs-title function_">registerApplication</span>(item.<span class="hljs-property">key</span>, item.<span class="hljs-property">component</span>, <span class="hljs-function">() =></span> <span class="hljs-title function_">matchRoute</span>(item.<span class="hljs-property">path</span>))<br> })<br><br> <span class="hljs-title function_">bindRouterEvent</span>(menuList, homeRedirect)<br><br> <span class="hljs-variable language_">window</span>.<span class="hljs-property">store</span> = store<br>}<br><br><br>;(<span class="hljs-keyword">function</span> <span class="hljs-title function_">init</span>(<span class="hljs-params"></span>) {<br> <span class="hljs-title function_">registerRouter</span>()<br> <span class="hljs-title function_">initDomHook</span>()<br> <span class="hljs-title function_">start</span>()<br>})()<br><br></code></pre></td></tr></table></figure><h1 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a>八、总结</h1><ol><li>我们如何实现在一个页面里渲染多种技术栈? singleSPA</li><li>不同独立模块之间如何通讯? 现成的状态管理使用全局状态管理注入各个子应用</li><li>如何通过路由渲染到正确的模块? 通过自己定义的路由规则,通过hash路由的方式管理子模块</li><li>在不同子应用之间的路由该如何正确触发? 统一位置管理,防止命名冲突</li><li>项目代码别切割之后,通过何种方式合并到一起? 构建时集成,分包通过异步组件加载过来</li><li>前端微服务化后我们该如何编写我们的代码? 去router定义顶层路由,通过hash子路由管理自己路由</li><li>独立团队之间该如何协作? git分支功能分支</li></ol><h1 id="九、TODO"><a href="#九、TODO" class="headerlink" title="九、TODO"></a>九、TODO</h1><ul><li>动态增减应用依赖</li><li>vendor打包只打包公共依赖,各自依赖自己管理</li><li>cli 使开发新模块和新项目更简单</li><li>css 作用域的严格隔离</li><li>contentHash</li><li>运行时依赖</li></ul><h1 id="参考:"><a href="#参考:" class="headerlink" title="参考:"></a>参考:</h1><ul><li><a href="https://www.yuque.com/chentianyu/ykdmaz/ela3k3#8JBgz">infoQ - 大前端时代下的微前端架构</a></li><li><a href="https://juejin.im/post/5cadd7835188251b2f3a4bb0">掘金 - 微前端实践</a></li><li><a href="http://insights.thoughtworkers.org/micro-frontends-1/">ThoughtWorks洞见 -「微前端」- 将微服务理念扩展到前端开发(理论篇)</a></li><li><a href="http://insights.thoughtworks.cn/micro-frontends-2/">ThoughtWorks洞见 -「微前端」- 将微服务理念扩展到前端开发(实战篇)</a></li><li><a href="https://yq.aliyun.com/articles/715922">云栖社区 - 可能是你见过最完善的微前端解决方案</a><a href="https://juejin.im/post/5cadd7835188251b2f3a4bb0"></a></li></ul>]]></content>
<categories>
<category>Tech</category>
</categories>
<tags>
<tag>single-spa</tag>
<tag>Micro-Frontends</tag>
</tags>
</entry>
<entry>
<title>G2实现大数据展示模块</title>
<link href="/2019/09/05/G2%E5%AE%9E%E7%8E%B0%E5%A4%A7%E6%95%B0%E6%8D%AE%E5%B1%95%E7%A4%BA%E6%A8%A1%E5%9D%97/"/>
<url>/2019/09/05/G2%E5%AE%9E%E7%8E%B0%E5%A4%A7%E6%95%B0%E6%8D%AE%E5%B1%95%E7%A4%BA%E6%A8%A1%E5%9D%97/</url>
<content type="html"><![CDATA[<h1 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h1><p>本次回顾了业务需求里大数据模块的实现过程。该项目发布以后之前有投屏在公司大屏上,某天偶然经过以后看了很自豪啊哈哈。回顾心路历程,从接到需求后恶补正态分布函数,到技术选型中途从D3转向G2,再到G2图表实现到80%以后,发现其动效方面API能力不足之后硬核做完以后可谓一波三折,该需求一共做了6天,今天刚好回顾一下,正好也可以和之前开发使用过的eChart、D3做一个简单的对比。</p><p><img src="https://cdn.danni.cool/file/bc2b40e50b4f83e0c1f47520034c507e.jpeg" alt="1567610715274-0d2e6f41-d7c3-4b21-a165-4b4d525423a1.jpeg"></p><h1 id="一、需求分析"><a href="#一、需求分析" class="headerlink" title="一、需求分析"></a>一、需求分析</h1><p>需求很简单,大数据模块需要前端画两个图,一个是面积曲线图、一个是环形进度条。</p><h2 id="面积曲线图"><a href="#面积曲线图" class="headerlink" title="面积曲线图"></a>面积曲线图</h2><ul><li>面积曲线图需要使用曲线绘制出固定的走势</li><li>线条和面积的阴影颜色不同</li><li>初次渲染时需要有自左向右的进入动画</li><li>相应数值处标注起求职者的薪水位置</li></ul><h2 id="环形进度条"><a href="#环形进度条" class="headerlink" title="环形进度条"></a>环形进度条</h2><ul><li>数据大小使用环形进度条百分比呈现</li><li>初次进入时有个数据增长的动效。</li><li>环形进度条有个白色圆跟着动</li></ul><p>emmmm….需求看起来好像不过分</p><h2 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h2><p><img src="https://cdn.danni.cool/file/69a80c41d02875c5c4cc747fb59af25f.gif" alt="Untitled.gif"></p><h1 id="二、画图届的PK"><a href="#二、画图届的PK" class="headerlink" title="二、画图届的PK"></a>二、画图届的PK</h1><p>听到数据可视化需求,马上脑袋里出现三大框架,<del>vue,react、angular</del>,是 echart、G2、D3。</p><h2 id="多而全的EChart"><a href="#多而全的EChart" class="headerlink" title="多而全的EChart"></a>多而全的EChart</h2><p>以前用echart做过移动端的饼图和折线图,而echart作为一款开箱即用的图表库,确实很方便,但是echart的定制能力比较弱,对于这么简单的图估计光查api和删减配置项就要弄很久,而且最后是否能还原到设计稿这样样式应该是个问号?不过之所以用惯了echart的一步到位,也想探究下画图更加偏底层的内容。</p><h2 id="陡峭的D3"><a href="#陡峭的D3" class="headerlink" title="陡峭的D3"></a>陡峭的D3</h2><p>G2是画图届的面团,可定制化能力比echart相对高,用它来实现应该没问题。好,那我还是用D3吧,这样我就可以给大家写一篇D3的分享了,不过大家也发现了,最终文章的标题是讲 G2而不是D3,因为在D3上碰壁了。</p><p>我们简单说一下D3和G2的区别,D3简称“画图届的jquery”,因为D3是基于SVG的,而G2则是基于Canvas(当然3.2.7版本也开始支持以SVG渲染),两者其实各有优缺点,「数据可视化小组」最近搬运的这篇文章</p><p><a href="https://www.yuque.com/docs/share/eda67c40-b202-48d3-97b7-fb7a59a2cc26">《选择Canvas还是SVG》</a>简单介绍了Canvas 和 SVG的适用场景和性能差异。</p><p><img src="https://cdn.danni.cool/file/5ceb9b790534c2c3fc05d2384a90d915.png" alt="1567615980097-741ce71b-ec50-47fe-9608-5e78bf2e2c72.png"></p><p><strong>简单摘录一下:</strong></p><p>如果单就图表库的视角来看,选择 Canvas 和 SVG 各有千秋。小画布、大数据量的场景适合用 Canvas,譬如热力图、大数据量的散点图等。如果画布非常大,有缩放、平移等高频的交互,或者移动端对内存占用量非常敏感等场景,可以使用 SVG 的方案。</p><h3 id="为什么初期使用D3不是一个好的选择?"><a href="#为什么初期使用D3不是一个好的选择?" class="headerlink" title="为什么初期使用D3不是一个好的选择?"></a>为什么初期使用D3不是一个好的选择?</h3><p>好了,接下来我们说一说为什么刚上来就使用D3不是一个好的选择?</p><p>首先你得储备大量SVG相关的知识,但如果你之前没接触过SVG绘图的话,就会花大量的时间成本下去,</p><p>D3主要是和DOM交互,比如SVG有六大预设图形:矩形rect、圆形circle、椭圆ellipse、线条line、折线polyline、多边形polygon,另外再加一个万能的路径path</p><p><img src="https://cdn.danni.cool/file/5eaa7db45657c134c696ba6d3d98aab6.png" alt="1567698476394-79dbdf0e-a87d-43e5-865c-740fe8d40518.png"></p><p>当你决定使用D3画图时,你就会不能避免的接触这些标签的绘图属性,特别是当你要使用这么一堆属性来绘图的时候,你还得去学每个图形的属性,那真的是会增加很多时间成本。当然,D3已经帮你做了很多封装,即使这样我还是觉得要了解太多SVG的知识,才能把一个图画好。比如我们看一个简单的示例</p><ul><li><a href="https://segmentfault.com/a/1190000012303584">《D3简单入门》</a></li><li><a href="https://www.w3schools.com/graphics/svg_path.asp">《w3cSchool - svg path》</a></li></ul><p>这个其实就有点类似于用CSS3动画去实现复杂的H5动画,这一点其实我还是建议使用可视化的方式去调整,之前项目里我做过一个带路径的体验地图(红框部分),其路径就是直接通过设计稿sketch生成的,路劲的行径动效则是根据数值的百分比的位置计算出来的,光这个动效从预研到实现用了2天。</p><p><img src="https://cdn.danni.cool/file/d33cbdad757b1a51ed0c6765c6afacbf.png" alt="1567738830152-8a0fd8be-f933-462d-9859-2012cae4c04a.png"></p><p>其次,还需要掌握一些算法和数学函数,比如这次画这个像抛物线(后面会讲),画圆弧,这些SVG固然有很多预设可以选,但是你要让他们动起来,就必须使用 path,而path几乎是一个点一个点绘制的,这才造就了SVG的无限可能。</p><p><strong>这是我中途放弃D3的原因。</strong></p><p>当然,使用D3比纯用JS去操作SVG还是有其优势的,这个后面再单独开一章节讲。</p><h1 id="三、用G2把图画好"><a href="#三、用G2把图画好" class="headerlink" title="三、用G2把图画好"></a>三、用G2把图画好</h1><h2 id="面积图"><a href="#面积图" class="headerlink" title="面积图"></a>面积图</h2><p>你使用G2的话,其实有一种找回echart的感觉, 通过官网去找示例,<a href="https://antv.alipay.com/zh-cn/g2/3.x/demo/area/basic.html">面积图</a> 比较符合我们的诉求,通过简单的chart.line() 就能绘制出平滑的曲线。</p><p><img src="https://cdn.danni.cool/file/0b154b66ca3f9bbbe813876de5c7ed04.png" alt="1567747687152-fd8043b4-0e81-4e57-9a94-d22307064f5f.png"></p><p>这个图的难点在于以下三个方面:</p><ul><li>曲线部分,因为要画出类似设计稿的平滑曲线,所以就不能只使用几个点的标记,我们需要一连串的坐标,这次得益于算法组的帮助,告诉我这是一条正态分布曲线图。</li><li>数据部分,因为事先定义好了使用正态分布曲线,那么具体标注的地方就是相应的坐标,后端传入数据的区间为 -2.5 ~ 2.5,需要将坐标映射到 0 ~100 的图中。</li></ul><h3 id="正态分布函数"><a href="#正态分布函数" class="headerlink" title="正态分布函数"></a>正态分布函数</h3><p>我们先来回顾看下一般正态分布函数的定义,搜了全网,恶补了高数的知识,它的定义是这样的:</p><blockquote><p>若<a href="https://baike.baidu.com/item/%E9%9A%8F%E6%9C%BA%E5%8F%98%E9%87%8F/828980">随机变量</a>X服从一个<a href="https://baike.baidu.com/item/%E6%95%B0%E5%AD%A6%E6%9C%9F%E6%9C%9B/5362790">数学期望</a>为μ、<a href="https://baike.baidu.com/item/%E6%96%B9%E5%B7%AE/3108412">方差</a>(σ^2)的正态分布,记为N(μ,σ^2)。其<a href="https://baike.baidu.com/item/%E6%A6%82%E7%8E%87%E5%AF%86%E5%BA%A6%E5%87%BD%E6%95%B0/5021996">概率密度函数</a>为正态分布的<a href="https://baike.baidu.com/item/%E6%9C%9F%E6%9C%9B%E5%80%BC/8664642">期望值</a>μ决定了其位置,其<a href="https://baike.baidu.com/item/%E6%A0%87%E5%87%86%E5%B7%AE/1415772">标准差</a>σ决定了分布的幅度。当μ = 0,σ = 1时的正态分布是<a href="https://baike.baidu.com/item/%E6%A0%87%E5%87%86%E6%AD%A3%E6%80%81%E5%88%86%E5%B8%83">标准正态分布</a>。</p></blockquote><p>它的函数大概长这样</p><p><img src="https://cdn.danni.cool/file/1682c3895374d065c038a4cb23ec30e4.png" alt="1567749607066-84314abe-dd4e-4999-ae52-8138a75ebe2d.png"></p><p>画到图形上</p><p><strong>一般正态分布函数</strong></p><p><img src="https://cdn.danni.cool/file/aab56c82bd2177262817055b34e2ab33.png" alt="Untitled.png"></p><p><strong>μ = 0,σ = 1,为标准正态分布</strong></p><p><img src="https://cdn.danni.cool/file/1643c8b622dd00aee4f87baec1d8b87a.png" alt="Untitled.png"></p><p>这里我们要关注几个参数:</p><ol><li>1μ(平均值)决定了最高点的位置x轴上的偏移量。</li><li>2σ(标准差)决定了这个函数的陡峭还是平缓, σ越小,函数越陡峭</li><li>μ - n*σ 可以划定函数的边界</li></ol><p><strong>正态分布函数使用JS表达:</strong></p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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><code class="hljs typescript"> <span class="hljs-comment">// x: x的值</span><br> <span class="hljs-comment">// mean: 平均数</span><br> <span class="hljs-comment">// std: 标准差</span><br><span class="hljs-keyword">function</span> <span class="hljs-title function_">lineGenerate</span>(<span class="hljs-params">x, mean, std</span>) {<br> <span class="hljs-keyword">return</span> (<br> (<span class="hljs-number">1</span> / (std * <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">sqrt</span>(<span class="hljs-title class_">Math</span>.<span class="hljs-property">PI</span> * <span class="hljs-number">2</span>))) * <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">exp</span>(-(<span class="hljs-title class_">Math</span>.<span class="hljs-title function_">pow</span>(x - mean, <span class="hljs-number">2</span>) / (<span class="hljs-number">2</span> * <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">pow</span>(std, <span class="hljs-number">2</span>))))<br> )<br>}<br></code></pre></td></tr></table></figure><p><strong>数据映射</strong></p><p><img src="https://cdn.danni.cool/file/9b82b55711d867cc1bd4dfcd991365fa.png" alt="1567748640065-fc0616af-306f-4808-887f-e3d8faa8bcd3.png"></p><p>通过将 -2.5 ~ 2.5 的接口数据,映射到 0~100 我们通过方程计算得知</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><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><code class="hljs typescript"><span class="hljs-comment">// x 为输入值 </span><br><span class="hljs-keyword">function</span> <span class="hljs-title function_">scaleMap</span>(<span class="hljs-params">x</span>) {<br> <span class="hljs-keyword">return</span> <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">round</span>((<span class="hljs-number">20</span> * x) + <span class="hljs-number">50</span>)<br> }<br></code></pre></td></tr></table></figure><p><strong>图例动态高度</strong></p><p><img src="https://cdn.danni.cool/file/07e3f0e90161bb66d9623e72d92c0136.png" alt="1567751903041-fff1eb54-a6df-4bd3-9243-6c14fedc208c.png"></p><p>图例部分是使用dom实现的,这部分由G2的 chart.guide() API 提供,它的宽高都是像素px,这边我们没定义相应的数据到尺寸的比例尺,因为基本没用上,所以这部分稍微偷了下懒,毕竟在pc端也没有响应式缩放,所以就给对应的坐标乘以了一个系数去实现类似的根据不同的数据展示不同的高度。</p><p>html片段:</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs typescript">**<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"region"</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"width:23.8px;height:${lineGenerate(drawX) * adjustValue}px;"</span>></span>**</span><br></code></pre></td></tr></table></figure><p>javascript片段:</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><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><code class="hljs typescript">**<span class="hljs-comment">//计算和中心点的偏移量 ,距离中心点约远,系数越小</span><br> <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Math</span>.<span class="hljs-title function_">abs</span>(drawX - <span class="hljs-number">50</span>) < <span class="hljs-number">10</span>) {<br> adjustValue = <span class="hljs-number">16200</span><br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Math</span>.<span class="hljs-title function_">abs</span>(drawX - <span class="hljs-number">50</span>) < <span class="hljs-number">20</span>) {<br> adjustValue = <span class="hljs-number">16000</span><br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Math</span>.<span class="hljs-title function_">abs</span>(drawX - <span class="hljs-number">50</span>) < <span class="hljs-number">30</span>) {<br> adjustValue = <span class="hljs-number">15000</span><br> } <span class="hljs-keyword">else</span> {<br> adjustValue = <span class="hljs-number">14500</span><br> }**<br></code></pre></td></tr></table></figure><p>完整示例:<a href="https://codepen.io/danielchan27/pen/YzKYENw?editors=1011">codePen - areaChart</a></p><h3 id="环形进度条-1"><a href="#环形进度条-1" class="headerlink" title="环形进度条"></a>环形进度条</h3><p>而绘制环形进度条我参考了<a href="https://www.yuque.com/chentianyu/ykdmaz/tt8afr/edit">G2-仪表盘</a>结果半天没写出来,主要是 这个仪表盘使用了取巧的办法,其实质上还是使用了chart去做,对坐标轴采用了极坐标的变换,所以看上去像环形进度条,最难实现的就是首末两段的圆角部分,切差距比较大,遂弃。</p><p><img src="https://cdn.danni.cool/file/07af33a0ef37e482bfd6a5bc412f30b4.png" alt="1567754481251-27cb8214-8470-4b6e-87a7-7881ea3318a0.png"></p><h3 id="注册自定义图形"><a href="#注册自定义图形" class="headerlink" title="注册自定义图形"></a>注册自定义图形</h3><p>但是G2除了能绘制图表以为,还通过了Shape.shape实现了<a href="https://www.yuque.com/antv/g2-docs/api-shape">自定义图形</a>的绘制,具体有以下几种</p><ul><li><strong>point:</strong>点的绘制很简单,只要获取它的坐标以及大小即可,其中的 size 属性代表的是点的半径。</li></ul><p><img src="https://cdn.danni.cool/file/aa081a50c441a0e8e054e96577defe40.png" alt="Untitled.png"></p><ul><li><strong>line:</strong>线其实是由无数个点组成,在 G2 中我们将参与绘制的各个数据转换成坐标上的点然后通过线将逐个点连接而成形成线图,其中的 size 属性代表的是线的粗细。</li></ul><p><img src="https://cdn.danni.cool/file/a1f4c92c27d1f371cd181d106c52ac30.png" alt="Untitled.png"></p><ul><li><strong>area:</strong>area 面其实是在 line 线的基础之上形成的, 它将折线图中折线与自变量坐标轴之间的区域使用颜色或者纹理填充。</li></ul><p><img src="https://cdn.danni.cool/file/405fe7f32022b39b8e880529ec88150c.png" alt="Untitled.png"></p><ul><li><strong>interval:</strong>interval 默认的图形形状是矩形,而矩形实际是由四个点组成的,在 G2 中我们根据 pointInfo 中的 x、y、size 以及 y0 这四个值来计算出这四个点,然后顺时针连接而成。</li></ul><p><img src="https://cdn.danni.cool/file/278787656dd26accc462362be0420743.png" alt="Untitled.png"></p><ul><li><strong>polygon:</strong>polygon 多边形其实也是由多个点连接而成,在 pointInfo 中 x 和 y 都是数组结构。</li></ul><p><img src="https://cdn.danni.cool/file/713883cf91082ff07ec92319eb997dfc.png" alt="Untitled.png"></p><ul><li><strong>schema:</strong>schema 作为一种自定义的几何图形,在 G2 中默认提供了 box 和 candle 两种 shape,分别用于绘制箱型图和股票图,注意这两种形状的矩形部分四个点的连接顺序都是顺时针,并且起始点均为左下角,这样就可以无缝转换至极坐标。</li></ul><p><img src="https://cdn.danni.cool/file/d163a1c014adf0df93415af5f116708c.png" alt="Untitled.png"></p><ul><li><strong>edge:</strong>edge 边同 line 线一致,区别就是 edge 是一个线段,连接边的两个端点即可。</li></ul><p>于是查了找遍全网,找了半天文档,终于发现了原来 G2.Shape.registerShape 是可以实现的,但是文档和相关demo少的可怜,接下来的画图真的是硬核写完的。</p><ol><li><strong>定义带有圆角的5px的线的预设</strong></li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><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><code class="hljs javascript"><span class="hljs-keyword">const</span> <span class="hljs-title class_">Shape</span> = <span class="hljs-variable constant_">G2</span>.<span class="hljs-property">Shape</span> <span class="hljs-comment">// 环形图自定义形状</span><br><span class="hljs-keyword">const</span> <span class="hljs-title class_">Animate</span> = <span class="hljs-variable constant_">G2</span>.<span class="hljs-property">Animate</span><br><br> <span class="hljs-title class_">Shape</span>.<span class="hljs-title function_">registerShape</span>(<span class="hljs-string">'edge'</span>, <span class="hljs-string">'arc2'</span>, {<br> <span class="hljs-attr">draw</span>: <span class="hljs-keyword">function</span> <span class="hljs-title function_">draw</span>(<span class="hljs-params">cfg, group</span>) {<br><br><span class="hljs-comment">// 将0-1空间的坐标转换为画布坐标,this.parsePoint 真的太宝贵了,一般人找不到他</span><br> <span class="hljs-keyword">const</span> center = <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">parsePoint</span>({ <span class="hljs-attr">x</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">y</span>: <span class="hljs-number">0</span> })<br><br><span class="hljs-comment">// 算出圆的最大半径</span><br> <span class="hljs-keyword">const</span> R = <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">parsePoint</span>({ <span class="hljs-attr">x</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">y</span>: <span class="hljs-number">1</span> }).<span class="hljs-property">y</span> - center.<span class="hljs-property">y</span><br> <span class="hljs-keyword">const</span> lineWidth = <span class="hljs-number">5</span><br><br> <span class="hljs-comment">//这边定义预设</span><br> <span class="hljs-keyword">return</span> group.<span class="hljs-title function_">addShape</span>(<span class="hljs-string">'arc'</span>, {<br> <span class="hljs-attr">attrs</span>: {<br> <span class="hljs-attr">x</span>: center.<span class="hljs-property">x</span>,<br> <span class="hljs-attr">y</span>: center.<span class="hljs-property">y</span>,<br> <span class="hljs-attr">r</span>: (R + lineWidth),<br> <span class="hljs-attr">startAngle</span>: (<span class="hljs-number">3</span> / <span class="hljs-number">4</span>) * <span class="hljs-title class_">Math</span>.<span class="hljs-property">PI</span>,<br> <span class="hljs-attr">endAngle</span>: (<span class="hljs-number">1</span> / <span class="hljs-number">4</span>) * <span class="hljs-title class_">Math</span>.<span class="hljs-property">PI</span>,<br> <span class="hljs-attr">stroke</span>: <span class="hljs-string">'#8B5EFF'</span>,<br> lineWidth,<br> <span class="hljs-attr">lineCap</span>: <span class="hljs-string">'round'</span><br> }<br> })<br> }<br> })<br></code></pre></td></tr></table></figure><ol><li><strong>新起一个chart,使用极坐标变换</strong></li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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><code class="hljs javascript"><span class="hljs-keyword">const</span> chart = <span class="hljs-keyword">new</span> <span class="hljs-variable constant_">G2</span>.<span class="hljs-title class_">Chart</span>({<br> <span class="hljs-attr">container</span>: wrapEl,<br> <span class="hljs-attr">forceFit</span>: <span class="hljs-literal">true</span>,<br> <span class="hljs-attr">height</span>: <span class="hljs-number">60</span>,<br> <span class="hljs-attr">padding</span>: [<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>]<br>})<br><br>chart.<span class="hljs-title function_">source</span>(data)<br><br>chart.<span class="hljs-title function_">coord</span>(<span class="hljs-string">'polar'</span>, {<br> <span class="hljs-attr">startAngle</span>: (<span class="hljs-number">3</span> / <span class="hljs-number">4</span>) * <span class="hljs-title class_">Math</span>.<span class="hljs-property">PI</span>,<br> <span class="hljs-attr">endAngle</span>: (<span class="hljs-number">1</span> / <span class="hljs-number">4</span>) * <span class="hljs-title class_">Math</span>.<span class="hljs-property">PI</span>,<br> <span class="hljs-attr">radius</span>: <span class="hljs-number">0.85</span><br>})<br></code></pre></td></tr></table></figure><ol><li><strong>绘制灰色圆弧背景</strong></li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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><code class="hljs javascript">chart.<span class="hljs-title function_">guide</span>().<span class="hljs-title function_">arc</span>({<br> <span class="hljs-attr">zIndex</span>: <span class="hljs-number">0</span>,<br> <span class="hljs-attr">top</span>: <span class="hljs-literal">false</span>,<br> <span class="hljs-attr">start</span>: [<span class="hljs-number">0</span>, <span class="hljs-number">0.945</span>],<br> <span class="hljs-attr">end</span>: [<span class="hljs-number">100</span>, <span class="hljs-number">0.945</span>],<br> <span class="hljs-attr">style</span>: {<br> <span class="hljs-comment">// 底灰色</span><br> <span class="hljs-attr">stroke</span>: <span class="hljs-string">'#E5E5E5'</span>,<br> <span class="hljs-attr">lineWidth</span>: <span class="hljs-title class_">Piechart</span>.<span class="hljs-property">lineWidth</span>,<br> <span class="hljs-attr">lineCap</span>: <span class="hljs-string">'round'</span><br> }<br>})<br></code></pre></td></tr></table></figure><ol><li><strong>绘制显示的进度条并指定动画名称</strong></li></ol><p>这边有点坑,我们发现动画执行过程中只有动画完成时的callback,而antV同款可视化组件,<a href="https://antv.alipay.com/zh-cn/f2/3.x/demo/gallery/simple-gauge.html">F2</a>则提供了update()的回调,这意味着增长的动画效果需要我们自己做。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs javascript">chart<br> .<span class="hljs-title function_">edge</span>()<br> .<span class="hljs-title function_">shape</span>(<span class="hljs-string">'arc2'</span>)<br> .<span class="hljs-title function_">position</span>(<span class="hljs-string">'value*1'</span>)<br> .<span class="hljs-title function_">animate</span>({<br> <span class="hljs-attr">appear</span>: {<br> <span class="hljs-attr">animation</span>: <span class="hljs-string">'stepPercent'</span>, <span class="hljs-comment">// 动画名称</span><br> <span class="hljs-attr">easing</span>: <span class="hljs-string">'easeQuadInOut'</span>, <span class="hljs-comment">// 动画缓动效果</span><br> <span class="hljs-attr">delay</span>: <span class="hljs-number">100</span>, <span class="hljs-comment">// 动画延迟执行时间</span><br> <span class="hljs-attr">duration</span>: <span class="hljs-number">1000</span>, <span class="hljs-comment">// 动画执行时间</span><br> <span class="hljs-title function_">callback</span>(<span class="hljs-params"></span>) {<br> <span class="hljs-comment">// console.log('动画结束')</span><br> }<br> }<br> })<br> .<span class="hljs-title function_">active</span>(<span class="hljs-literal">false</span>)<br> .<span class="hljs-title function_">select</span>(<span class="hljs-literal">false</span>)<br></code></pre></td></tr></table></figure><ol><li><strong>注册一个增长动画效果的开始状态和结束状态</strong></li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><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><code class="hljs javascript"><span class="hljs-comment">// 注册百分比动画</span><br><span class="hljs-title class_">Animate</span>.<span class="hljs-title function_">registerAnimation</span>(<span class="hljs-string">'appear'</span>, <span class="hljs-string">'stepPercent'</span>, <span class="hljs-function">(<span class="hljs-params">shape, animateCfg</span>) =></span> {<br> <span class="hljs-keyword">const</span> startAngle = shape.<span class="hljs-title function_">attr</span>(<span class="hljs-string">'startAngle'</span>)<br><br> <span class="hljs-comment">// 设置初始状态</span><br> shape.<span class="hljs-title function_">attr</span>(<span class="hljs-string">'endAngle'</span>, (-<span class="hljs-number">2</span> * <span class="hljs-title class_">Math</span>.<span class="hljs-property">PI</span>) + startAngle)<br><br> <span class="hljs-keyword">const</span> endX = shape.<span class="hljs-title function_">get</span>(<span class="hljs-string">'origin'</span>).<span class="hljs-property">points</span>[<span class="hljs-number">0</span>].<span class="hljs-property">x</span><br> shape.<span class="hljs-title function_">animate</span>(<br> {<br> <span class="hljs-attr">endAngle</span>: ((<span class="hljs-number">3</span> / <span class="hljs-number">2</span>) * <span class="hljs-title class_">Math</span>.<span class="hljs-property">PI</span> * endX) - ((<span class="hljs-number">5</span> / <span class="hljs-number">4</span>) * <span class="hljs-title class_">Math</span>.<span class="hljs-property">PI</span>)<br> },<br> animateCfg.<span class="hljs-property">duration</span>,<br> animateCfg.<span class="hljs-property">easing</span>,<br> animateCfg.<span class="hljs-property">callback</span>,<br> animateCfg.<span class="hljs-property">delay</span><br> )<br><br> <span class="hljs-comment">// console.log('开始做动画')</span><br> $number = $(<span class="hljs-string">`#number-<span class="hljs-subst">${wrapEl}</span>`</span>)<br> <span class="hljs-title function_">gainPercent</span>()<br><br>})<br></code></pre></td></tr></table></figure><ol><li><strong>requestAnimationFrame注册的步进动画</strong></li></ol><p>因为电脑缘故,性能低下的CPU可能在1秒内执行不完60帧,所以使用<strong>requestAnimationFrame</strong>,另外因为 data[0].value / 60 是个不精准的数值,所以会出现最后累加出现不等于传入值的情况,在最后一个次累加等于正常值</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">let</span> $number<br><span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span><br><span class="hljs-keyword">const</span> gainPercent = <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) {<br> i++<br> <span class="hljs-comment">// 执行到最后一步强制拉到传入的最终值</span><br> <span class="hljs-keyword">if</span> (i === <span class="hljs-number">60</span>) {<br> $number.<span class="hljs-title function_">text</span>(<span class="hljs-string">`<span class="hljs-subst">${data[<span class="hljs-number">0</span>].value}</span>%`</span>)<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-keyword">const</span> step = data[<span class="hljs-number">0</span>].<span class="hljs-property">value</span> / <span class="hljs-number">60</span><br> <span class="hljs-keyword">const</span> value = $number.<span class="hljs-title function_">attr</span>(<span class="hljs-string">'data-val'</span>)<br> $number.<span class="hljs-title function_">attr</span>(<span class="hljs-string">'data-val'</span>, +value + step)<br> $number.<span class="hljs-title function_">text</span>(<span class="hljs-string">`<span class="hljs-subst">${(+value + step).toString().split(<span class="hljs-string">'.'</span>)[<span class="hljs-number">0</span>]}</span>%`</span>)<br> }<br><br> <span class="hljs-comment">// console.log(`动画执行${i}`, $number.attr('data-val'))</span><br><br> <span class="hljs-keyword">if</span> (i < <span class="hljs-number">60</span>) {<br> <span class="hljs-title function_">requestAnimationFrame</span>(gainPercent)<br> }<br>}<br></code></pre></td></tr></table></figure><p>完整示例:<a href="https://codepen.io/danielchan27/pen/zYOppMZ">G2-Circle</a></p><h1 id="四、后记"><a href="#四、后记" class="headerlink" title="四、后记"></a>四、后记</h1><ol><li>选择可视化框架时按实现的简单程度:echart > G2 > D3 > 原生</li><li>echart大而全,但是定制能力比较弱</li><li>G2适合实现一些本身就有的图表库,单独去定义自定义图形API和文档较少</li><li>G2的动画和联动能力较弱,推荐F2</li></ol>]]></content>
<categories>
<category>Tech</category>
</categories>
<tags>
<tag>G2</tag>
<tag>Canvas</tag>
<tag>SVG</tag>
</tags>
</entry>
</search>