让 React 拥有更快的虚拟 DOM

227 阅读4分钟

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 组件

<button type="button" class="btn btn-dark far fa-copy rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="import { block as quickBlock } from "million/react";

                  // 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> ); }

当前是数组的情况下

<button type="button" class="btn btn-dark far fa-copy rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="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>
                                                );
                                                });
                                                
                                                // 使用 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">&lt;<span class="hljs-name">div</span>&gt;</span>
                                      <span class="hljs-tag">&lt;<span class="hljs-name">For</span> <span class="hljs-attr">each</span>=<span class="hljs-string">{data}</span>&gt;</span>
                                              {({ adjective, color, noun }) =&gt; (
                                                        <span class="hljs-tag">&lt;<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> 
                                                                                                      /&gt;</span>
                                                                                                              )}
                                                                                                                    <span class="hljs-tag">&lt;/<span class="hljs-name">For</span>&gt;</span>
                                                                                                                        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</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">&lt;<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span></span><span class="hljs-template-variable">{dynamic}</span><span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
          Lots and lots of static content...
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
          
          // ❌ Bad
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span></span><span class="hljs-template-variable">{dynamic}</span><span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span></span><span class="hljs-template-variable">{dynamic}</span><span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span></span><span class="hljs-template-variable">{dynamic}</span><span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span></span><span class="hljs-template-variable">{dynamic}</span><span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span></span><span class="hljs-template-variable">{dynamic}</span><span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</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>(&lt;<span class="hljs-keyword">div</span> /&gt;)
    
    <span class="hljs-comment">// ❌ Bad</span>
    console.log(<span class="hljs-keyword">block</span>(&lt;<span class="hljs-keyword">div</span> /&gt;))
    export <span class="hljs-keyword">default</span> <span class="hljs-keyword">block</span>(&lt;<span class="hljs-keyword">div</span> /&gt;)</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>(&lt;Component /&gt;)</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>