高中生打破React性能极限,将React性能提升70%!(上)https://developer.aliyun.com/article/1411538
Million 的虚拟 DOM
2022 年,Blockdom 发布了 。基于不同的思路,Blockdom 引入了“块虚拟DOM”的概念。块虚拟 DOM 采用不同的 diff 方法,可以分为两部分:
- 静态分析:对虚拟 DOM 进行分析,将树的动态部分提取到“Edit Map”中,即虚拟DOM的动态部分到状态的“Edit”(Map)列表中。
- 脏检查:对比状态(而不是虚拟 DOM 树)以确定发生了什么变化。如果状态发生了变化,通过 Edit Map 直接更新DOM。
简而言之,就是对比数据而不是 DOM,因为数据的大小通常比 DOM 的大小小得多。而且对比数据值可能比对比完整的 DOM 节点更简单。
由于 Million.js 采用了与 Blockdom 类似的方法,因此下面将使用 Million.js 的语法。
下面来看一个简单的计数器应用以及它如何使用 Million.js 处理:
import { useState } from 'react'; import { block } from 'million/react'; function Count() { const [count, setCount] = useState(0); const node1 = count + 1; const node2 = count + 2; return ( <div> <ul> <li>{node1}</li> <li>{node2}</li> </ul> <button onClick={() => { setCount(count + 1); }} > Increment Count </button> </div> ); } const CountBlock = block(Count);
constCountBlock = block(Count);
这个程序很简单,显示效果如下:
(1)静态分析
静态分析可以在编译时或运行时的第一步完成,具体取决于是否使用了 Million.js 的实验性编译器。
此步骤负责将虚拟 DOM 的动态部分提取到“编辑映射”中。
- 这里没有使用 React 来渲染 JSX,而是使用 Million.js 来渲染它,它将占位符节点(用“?”表示)传递到虚拟DOM。这些节点将充当动态内容的占位符,并在静态分析过程中使用。
- 现在开始静态分析,检查第一个节点是否有占位符,没有找到,继续下一步。
- 在第二个节点中检查占位符,没有找到,继续下一步。
- 检查第三个节点的占位符并找到“?”。 将占位符添加到“Edit Map”,它将
prop1
关联到占位符节点。然后从块中删除占位符。
- 检查第四个节点的占位符并找到“?”。 将占位符添加到“Edit Map”,它将
prop2
关联到占位符节点。 然后从块中删除占位符。
- 检查第五个节点是否有占位符,没有找到,完成检测。
(2)脏检查
创建 Edit Map 后,就可以开始脏检查了。 这一步负责确定状态发生了什么变化,并相应地更新 DOM。
- 可以只区分
prop1
和prop2
,而不是按元素进行区分。 由于两者都与在静态分析期间创建的“Edit Map”相关联,因此一旦确定差异,就可以直接更新 DOM。 - 比较当前的
prop1
和新的prop1
值,由于它们不同,因此更新了 DOM。
- 比较当前的
prop2
和新的prop2
值,由于它们不同,因此更新了 DOM。
可以看到,脏检查比 diff 步骤需要更少的计算。这是因为脏检查只关心状态,而不关心虚拟 DOM,因为每个虚拟节点可能需要许多级别的递归来确定它是否已经改变,状态只需要一个浅层相等检查。
Million.js使用场景
Million.js 具有相当高的性能,并且能够在 JavaScript 框架基准测试中胜过 React。 JavaScript 框架基准测试通过渲染一个包含行和列的大型表格来测试框架的性能。该基准测试旨在测试高度不切实际的性能测试(如添加/替换 1000 行),并不一定代表真实的应用。
那 Million.js 或块虚拟 DOM可以用在什么地方呢?
静态内容多,动态内容少
当有很多静态内容而动态内容很少时,最好使用块虚拟 DOM。 块虚拟 DOM最大的优势就是不需要考虑虚拟 DOM 的静态部分,所以如果能跳过很多静态内容,速度会非常快。
例如,在这种情况下,块虚拟 DOM 将比常规虚拟 DOM 快得多:
// ✅ <div> <div>{dynamic}</div> 很多静态内容... </div>
如果有很多动态内容,可能看不出块虚拟 DOM 和常规虚拟 DOM 的区别:
// ❌ <div> <div>{dynamic}</div> <div>{dynamic}</div> <div>{dynamic}</div> <div>{dynamic}</div> <div>{dynamic}</div> </div>
如果构建一个管理系统,或者一个包含大量静态内容的网站,块虚拟 DOM 可能非常适合。 但是,如果构建一个网站,其中比较数据所需的计算量明显大于比较虚拟 DOM 所需的计算量,那么可能看不出太大差异。
例如,这个组件不适合块虚拟 DOM,因为要比较的数据值多于虚拟 DOM 节点:
javascript
复制代码
// 5个要比较的数据值functionComponent({ a, b, c, d, e }) {
// 1个要比较的虚拟DOM节点 return<div>{a + b + c + d + e}div>;
}
“稳定”的 UI 树
块状虚拟 DOM 也适用于“稳定”的 UI 树,或者变化不大的 UI 树。 这是因为 Edit Map 只创建一次,不需要在每次渲染时都重新创建。
例如,以下组件是块虚拟 DOM 的一个很好的使用场景:
javascript
复制代码
functionComponent() {
return<div>{dynamic}div>;
}
但是这个组件可能比常规的虚拟 DOM 慢:
javascript
复制代码
functionComponent() {
returnMath.random() > 0.5 ? <div>{dynamic}div> : <p>sadp>;
}
注意,“稳定”返回意味着不允许具有非列表类动态的组件(如同一组件中的条件返回)。
细粒度地使用
初学者犯的最大错误之一是到处使用块虚拟 DOM。 这是个坏主意,因为块虚拟 DOM 并不总是比常规虚拟 DOM 快。
相反,应该识别块虚拟 DOM 更快的某些模式,并仅在这些情况下使用它。 例如,可能对大表使用块虚拟 DOM,但对具有少量静态内容的小表单使用常规虚拟 DOM。
总结
块虚拟 DOM 为虚拟 DOM 概念提供了一个全新的视角,提供了一种管理更新和最小化开销的替代方法。 尽管它具有潜力,但它并不是一种放之四海而皆准的解决方案,开发人员在决定是否采用这种方法之前应该评估应用的具体需求和性能要求。
对于很多应用来说,传统的虚拟 DOM 可能就足够了,可能不需要切换到块虚拟 DOM 或其他以性能为中心的框架。 如果应用在大多数设备上运行流畅且没有性能问题,那么可能不值得花时间和精力过渡到不同的框架。在对技术堆栈进行任何重大更改之前,必须仔细权衡取舍并评估应用的要求。