Million.js 是一个非常快速和轻量级的 ( <4kb) 虚拟 DOM。框架可以通过包装 React 组件来提升性能(该框架目前版本只兼容 React 18 及以上版本)。
先说结论:Million.js 适应的场景极其有限,但在特定场景下也大放异彩。
如何使用
Million.js 集成 React 中使用非常简单。先进行安装和编译器配置。
安装与配置
npm install million
当前是 webpack 的配置文件。如果有使用其他的构建工具,可以自行参考 安装 Million.js。
module.exports = {
plugins: [
million.webpack(),
],
}" aria-label="复制" data-bs-original-title="复制"></button>
</div>
const million = require('million/compiler');
module.exports = {
plugins: [
million.webpack(),
],
}
使用 block 和 For 组件
// million block 是一个 HOC
const LionQuickBlock = quickBlock(function Lion() {
return ;
});
// 直接使用
export default function App() {
return (
<div>
<h1>mil + LION = million</h1>
<LionQuickBlock />
</div>
);
}" aria-label="复制" data-bs-original-title="复制"></button>
</div>
import { block as quickBlock } from "million/react";
// million block 是一个 HOC
const LionQuickBlock = quickBlock(function Lion() {
return <img src="million.dev/lion.svg" />;
});
// 直接使用
export default function App() {
return (
<div>
<h1>mil + LION = million</h1>
<LionQuickBlock />
</div>
);
}
当前是数组的情况下
const RowBlock = quickBlock(function Row({ name, age, phone }) {
return (
<tr>
<td>{name}</td>
<td>{age}</td>
<td>{phone}</td>
</tr>
);
});
// 使用 For 组件优化
export default function App() {
return (
<div>
<For each={data}>
{({ adjective, color, noun }) => (
<RowBlock
adjective={adjective}
color={color}
noun={noun}
/>
)}
</For>
</div>
);
}" aria-label="复制" data-bs-original-title="复制"></button>
</div>
import { block as quickBlock, For } from "million/react";
const RowBlock = quickBlock(function Row({ name, age, phone }) {
return (
<tr>
<td>{name}</td>
<td>{age}</td>
<td>{phone}</td>
</tr>
);
});
<span class="hljs-comment">// 使用 For 组件优化</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">App</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">return</span> (
<span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">For</span> <span class="hljs-attr">each</span>=<span class="hljs-string">{data}</span>></span>
{({ adjective, color, noun }) => (
<span class="hljs-tag"><<span class="hljs-name">RowBlock</span>
<span class="hljs-attr">adjective</span>=<span class="hljs-string">{adjective}</span>
<span class="hljs-attr">color</span>=<span class="hljs-string">{color}</span>
<span class="hljs-attr">noun</span>=<span class="hljs-string">{noun}</span>
/></span>
)}
<span class="hljs-tag"></<span class="hljs-name">For</span>></span>
<span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>
);
}</pre><h3 id="item-0-2">Block Virtual DOM</h3><p>Million.js 引入了 Block Virtual DOM 来进行优化。</p><p>Block Virtual DOM 采用不同的方法进行比较,可以分为两部分:</p><ul><li>静态分析(分析虚拟 DOM 以将 DOM 动态部分搜集起来,放入 Edit Map 或者 edits(列表) 中去)</li><li>脏检查(比较状态(不是虚拟 DOM 树)来确定发生了什么变化。如果状态发生变化,DOM 将直接通过 Edit Map 进行更新)</li></ul><p>这种方式大部分情况下要比 React 的虚拟 DOM 要快,因为它比较数据而并非 DOM,将树遍历从 O(tree) 变为 Edit Map O(1)。同时我们也可以看出 Million.js 也会通过编译器对原本的 React 组件进行修改。</p><h3 id="item-0-3">适用场景</h3><p>但所有的事情都不是绝对的,Block Virtual DOM 在某些情况下甚至要比虚拟 DOM 要慢。</p><h4>静态内容多,动态内容少</h4><p>block virtual DOM 会跳过 virtual DOM 的静态部分。</p><div class="widget-codetool" style="display: none;">
<div class="widget-codetool--inner">
<button type="button" class="btn btn-dark far fa-copy rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="// ✅ Good
<div>
<div>{dynamic}</div>
Lots and lots of static content...
</div>
// ❌ Bad
<div>
<div>{dynamic}</div>
<div>{dynamic}</div>
<div>{dynamic}</div>
<div>{dynamic}</div>
<div>{dynamic}</div>
</div>" aria-label="复制" data-bs-original-title="复制"></button>
</div>
</div><pre class="hljs language-dust"><span class="language-xml">// ✅ Good
<span class="hljs-tag"><<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span>></span></span><span class="hljs-template-variable">{dynamic}</span><span class="language-xml"><span class="hljs-tag"></<span class="hljs-name">div</span>></span>
Lots and lots of static content...
<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
// ❌ Bad
<span class="hljs-tag"><<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span>></span></span><span class="hljs-template-variable">{dynamic}</span><span class="language-xml"><span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span>></span></span><span class="hljs-template-variable">{dynamic}</span><span class="language-xml"><span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span>></span></span><span class="hljs-template-variable">{dynamic}</span><span class="language-xml"><span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span>></span></span><span class="hljs-template-variable">{dynamic}</span><span class="language-xml"><span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span>></span></span><span class="hljs-template-variable">{dynamic}</span><span class="language-xml"><span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"></<span class="hljs-name">div</span>></span></span></pre><h4>稳定的 UI 树</h4><p>因为 Edit Map 只创建一次,不需要在每次渲染时都重新创建。所以稳定的 UI 树是很重要的。</p><div class="widget-codetool" style="display: none;">
<div class="widget-codetool--inner">
<button type="button" class="btn btn-dark far fa-copy rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="// ✅ Good
return <div>{dynamic}</div>
// ❌ Bad
return Math.random() > 0.5 ? <div>{dynamic}</div> : <p>sad</p>;" aria-label="复制" data-bs-original-title="复制"></button>
</div>
// ✅ Good
return <div>{dynamic}</div>
// ❌ Bad
return Math.random() > 0.5 ? <div>{dynamic}</div> : <p>sad</p>;
细粒度使用
初学者犯的最大错误之一是到处使用 Block virtual DOM。这是个坏主意,因为它不是灵丹妙药。开发者应该识别块虚拟 DOM 更快的某些模式,并仅在这些情况下使用它。
规则
以下是一些要遵循的一般准则
- 嵌套数据:块非常适合呈现嵌套数据。Million.js 将树遍历从O(tree)变为O(1),允许快速访问和更改。
- 使用 For 组件而不是 Array.map:For 组件会做针对性优化
使用前需要先声明:编译器需要进行分析,没有申明将无法进行分析
<button type="button" class="btn btn-dark far fa-copy rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="// ✅ Good const Block = block()// ❌ Bad console.log(block(<div />)) export default block(<div />)" aria-label="复制" data-bs-original-title="复制"></button> </div> </div><pre class="hljs language-oxygene"><span class="hljs-comment">// ✅ Good</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">Block</span> = <span class="hljs-keyword">block</span>(<<span class="hljs-keyword">div</span> />) <span class="hljs-comment">// ❌ Bad</span> console.log(<span class="hljs-keyword">block</span>(<<span class="hljs-keyword">div</span> />)) export <span class="hljs-keyword">default</span> <span class="hljs-keyword">block</span>(<<span class="hljs-keyword">div</span> />)</pre></li><li><p>传递组件而不是 JSX</p><div class="widget-codetool" style="display: none;"> <div class="widget-codetool--inner"> <button type="button" class="btn btn-dark far fa-copy rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="// ✅ Good const GoodBlock = block(App) // ❌ Bad const BadBlock = block(<Component />)" aria-label="复制" data-bs-original-title="复制"></button> </div> </div><pre class="hljs language-oxygene"><span class="hljs-comment">// ✅ Good</span> <span class="hljs-keyword">const</span> GoodBlock = <span class="hljs-keyword">block</span>(App) <span class="hljs-comment">// ❌ Bad</span> <span class="hljs-keyword">const</span> BadBlock = <span class="hljs-keyword">block</span>(<Component />)</pre></li><li>确定的返回值:返回必须是“确定性的”,这意味着在返回稳定树的块末尾只能有一个返回语句(组件库,Spread attributes 都有可能造成不确定的返回值而导致性能下降)</li></ul><h3 id="item-0-5">其他</h3><p>million 源码中有非常多的缓存优化。同时在它最开始就拆分传入的 dom 节点,将其分成多个可变量,放入数组,patch 和 mount 时仅遍历数组数据对比(这也是需要确定的返回值原因),较为新颖。源代码也较为简单明了。大家可以自行阅读源码学习。</p><ul><li><a href="https://link.segmentfault.com/?enc=lK01Gck6L0zT%2F9677OaCtA%3D%3D.qKRrUYnrS%2BTlFRHyyipwJ76cdaXLudqRob8vBdUBmDgQre3UzaChU5frSvZpM10Q5UL%2F1xqGdr64ZhCw5h%2FWYQC5KHj8Noi9jLknpxlfEn0%3D" rel="nofollow" target="_blank">million react/block.ts</a></li><li><a href="https://link.segmentfault.com/?enc=cqiniHaAakHeUl99L3uzyg%3D%3D.ecD7kjV3jFnPkqdqZbsvoyBtrd3nIOpZ0TqbT8NLOLF4s9oUTRlkot9x8G5T1vCEdZIRrdV36x1isdwDgLEJo%2BvbOHPgDWJasD9Pfjcqlns%3D" rel="nofollow" target="_blank">million template.ts</a></li><li><a href="https://link.segmentfault.com/?enc=jFMXTnkNx4FvCfQ9FJCGIg%3D%3D.Tu1COTAlewMMMiQ%2BQN%2FDA9Gy5aaAIiz0QoKXqK6OfsjO7IJHbt5mseqIVUnQRJvSLFfWdP3M0kcKvMsrve4xscQquu8TCankhPfxoPQPx3w%3D" rel="nofollow" target="_blank">million block.ts</a> .</li></ul><p>million 的 Block Virtual DOM 的思路来源于 <a href="https://link.segmentfault.com/?enc=tZFZVLAbNOu0lnAu9ZKTPQ%3D%3D.geUjJzrxiGJEC%2FWNJcQl7uPU3v9lHIdSNwoSjGPiEoP6CO8PMVD5xhRcny24hext" rel="nofollow" target="_blank">blockdom</a>, blockdom 是一个较低级别的抽象层。这个框架同时提供了制作框架的教程 <a href="https://link.segmentfault.com/?enc=svDp8dbTotvO%2BwzHqNdusQ%3D%3D.VTX8dL4xWIAuohI3dGoi4OlRPs%2B3MWYgGC1iIcAWN8GaWjLVmRAFbW5FAAeooOrpNMQ14gwLEvZHI%2Ffnq%2FGW09aatLTkOm5qHw%2Bc7TuHi7AMAusCMvuxwhXyzN11gC4Q" rel="nofollow" target="_blank">制作自己的框架</a>。</p><h2 id="item-0-6">鼓励一下</h2><p>如果你觉得这篇文章不错,希望可以给与我一些鼓励,在我的 github 博客下帮忙 star 一下。</p><p><a href="https://link.segmentfault.com/?enc=UBGthI4ifuvFd67tQE0lrA%3D%3D.3Lr95q%2FMoDEQg74lSkKVMT2HXro6QRDnMtK677Gzcf4nMb0nNTOqvaRM6UXIfyfC" rel="nofollow" target="_blank">博客地址</a></p><h2 id="item-0-7">参考资料</h2><p><a href="https://link.segmentfault.com/?enc=p%2FSf4sM%2F2lWqv3NuFM%2FPMg%3D%3D.j%2F8lSoIzXeOhShelT4BBsN8B1WfMW%2F%2FeB82wi%2FsgQe4%3D" rel="nofollow" target="_blank">Million.js</a></p><p><a href="https://link.segmentfault.com/?enc=6E%2BHNLxrHnH8vr6M2oXC8g%3D%3D.C666G7Mt1nQoR9EI3cwUeRQaXq9gH3KjXsZ0DMAvXstJu%2BGVmhSokKnhYvFFv5l7" rel="nofollow" target="_blank">blockdom</a></p>