引言
大约五年前,Rich Harris 发布了《虚拟 DOM 是纯粹的开销》一文,分析了传统虚拟 DOM 操作的性能问题。在他的文章中,Harris 认为虚拟 DOM 并不像许多开发者认为的那样高效,并提出了一种替代方法。然而,几年后,一个新的观点开始流行:虚拟 DOM 是纯粹的开销。这一观点变得如此有力,以至于将“无虚拟 DOM”框架运动从一个小众群体转变为一场全面的运动。虚拟 DOM 被视为一种必要的恶,一种为了声明式 UI 的便利性而必须支付的性能代价,直到现在。
React 的渲染过程
在深入探讨 React Compiler 之前,我们先来回顾一下 React 的基本渲染过程:
- Render:React 每次渲染都会创建一个虚拟 DOM。
- Diffing:虚拟 DOM 和上一次的虚拟 DOM 进行比较。
- Reconciliation:将差异点应用到真实 DOM 上。
为了优化这个过程,React 引入了 Fiber 架构,以防止整个过程阻塞浏览器的
尽管 React 针对这些步骤做了很多优化,但在某些情况下,仍然存在大量的资源浪费和不必要的运算。
引入 React Compiler
React 19 引入了一个全新的特性——React Compiler,旨在进一步优化 React 应用的性能。
案例演示
function DemoContent({ list }: { list: number[] }) {
const [count, setCount] = useState(0);
const handleCountChange = (count = 0) => {
setCount(count);
};
return (
<div>
{/* SVG动画可以直观的看到当前的动画卡顿程度 */}
<Suspense fallback={<></>}>
<LagRadar size={200} />
</Suspense>
{/* 倒计时 */}
<CountDown count={count} onChange={handleCountChange} />
{/* 列表 */}
<Table list={list} />
</div>
);
}
React 18
React 19 With React Compiler
通过上述图片可以很直观的看到 同样的代码 React 18 中动画会有卡顿,但是React 19 完全没有
我们把上述组件利用React Compiler Playground 编译后查看一下
playground.react.dev/#N4Igzg9grg…
编译后的代码可以看出来 Table 组件只有在 list 发生变化时才会重新渲染,这样如果组件的list 没有发生变化Table 组件不会重新渲染,虚拟DOM也不会重新生成,diff 时就可以直接跳过。
这个就是React Compiler 的核心原理,当然React Compiler的优化不止这一点
React Compiler 的实现原理
React Compiler 的核心思想是将更多的工作从运行时转移到编译时。它通过静态分析和编译时优化,减少运行时的开销,提高应用的执行效率。
优化策略
- 组件内联:将简单组件内联到父组件中,减少函数调用和组件边界的开销。
- 属性优化:识别静态和动态属性,生成更高效的代码来处理属性更新。
- 渲染路径优化:生成更高效的条件渲染代码,减少不必要的计算。
- Hook 优化:分析 hooks 的使用模式,优化其实现,减少运行时的开销。
虚拟 DOM 在 React 和大多数其他实现中都是纯运行时的:更新算法无法预知新的虚拟 DOM 树会是怎样,因此它总是需要遍历整棵树、比较每个 vnode 上 props 的区别来确保正确性。另外,即使一棵树的某个部分从未改变,还是会在每次重渲染时创建新的 vnode,带来了大量不必要的内存压力。这也是虚拟 DOM 最受诟病的地方之一:这种有点暴力的更新过程通过牺牲效率来换取声明式的写法和最终的正确性。
但实际上我们并不需要这样。在 Vue 中,框架同时控制着编译器和运行时。这使得我们可以为紧密耦合的模板渲染器应用许多编译时优化。编译器可以静态分析模板并在生成的代码中留下标记,使得运行时尽可能地走捷径。与此同时,我们仍旧保留了边界情况时用户想要使用底层渲染函数的能力。我们称这种混合解决方案为带编译时信息的虚拟 DOM。
上述是Vue 官方文档 对于 React 和Vue 所采用的虚拟Dom的比较,Vue 之所以性能高是因为Vue 可以做到细粒度的更新,那React Compiler 是不是意味着React 的性能又上了一个台阶呢
总结
虽然说React Compiler 我们目前还用不上,但是我们可以做一些有必要的优化,来避免上述类似的问题,比如通过手动添加props的对比来决定是否重新渲染。