如何理解React通过对DOM的模拟,最大限度地减少与DOM的交互
- 背景
- 分析
- 关于虚拟DOM
背景
在学习React的过程中,发现很多文档上关于React的高效都有这么一句话的描述——React通过对DOM的模拟,最大限度地减少与DOM的交互,对于我这种前端小白来说,理解起来还是挺费劲的,所以找了些文档学习了一番。
分析
在查找资料的过程中,笔者发现关于这句话的描述其实包含着下面的知识点:
- 虚拟DOM: React引入了虚拟DOM的概念。虚拟DOM是一个存在于内存中的树形结构,它对应着实际的DOM树。在React中,组件的状态变化会首先在虚拟DOM上进行操作,而不是直接操作实际的DOM。
关于虚拟DOM
这里我们先以一个计数器为例子,分别看看React和JavaScript对它的实现方式:
React实现方式
- 封装Counter.js
import React from "react";// 计数器组件
const Counter = ({ count, onIncrement }) => (<div><p>Count: {count}</p><button onClick={onIncrement}>Increment</button></div>
);export default Counter;
- 在App.js中引入组件
function App() {
/**计数器组件 */const [count, setCount] = useState(0);// 处理递增事件const handleIncrement = () => {setCount(count + 1);};return (<div><h1>Counter App</h1><Counter count={count} onIncrement={handleIncrement} /></div>);
}export default App;
JavaScript实现方式:
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Counter App</title></head><body><div id="counterContainer"><p id="countDisplay">Count: 0</p><button id="incrementButton">Increment</button></div><script>// 获取DOM元素var countDisplay = document.getElementById('countDisplay');var incrementButton = document.getElementById('incrementButton');// 初始化计数器var count = 0;// 处理按钮点击事件function handleIncrement() {count++;countDisplay.textContent = 'Count: ' + count;}// 添加按钮点击事件监听器incrementButton.addEventListener('click', handleIncrement);</script></body>
</html>
对比上面两种实现方式,JavaScript 直接对实际DOM进行操作,需要我们手动管理状态和UI之间的同步,每次计数+1时,同步修改UI展示。随着项目越来越大,当我们直接修改的DOM改动的频次越多,甚至涉及位置变换,那么就可能带来重排和重绘的问题了**。**
- 重排: 当改变影响到页面布局的属性时,例如修改了元素的尺寸、位置、添加或删除了DOM元素等,浏览器可能会触发重排。重排是一项比较昂贵的操作,因为它涉及到重新计算元素的几何属性和页面的布局,可能导致整个页面的重新渲染。
- 重绘: 当改变影响到元素的样式(但不涉及布局的改变)时,例如修改了颜色、背景等,浏览器可能会触发重绘。重绘相对于重排来说开销较小,因为它只涉及到重新绘制元素的外观。
再来看看React的实现方式,首先我们可以知道所有的HTML的DOM节点都可以用JS方式去表示,而React在这里巧妙的利用JS 表示 DOM对象的方式,避免了对DOM的直接操作,类似于中间加了一层缓存操作。(这里又想到一句话,计算机领域大部分问题都可以通过中间加一层解决)。如图所示,通过react-developer-tools工具,我们可以更加直观看到React如何表示我们的HTML的DOM结构。
React这种在内存中,采用JS 表示 DOM对象的方式,又有一个更加专业点的名称虚拟DOM。它通过采用虚拟DOM和差异算法来最小化对实际DOM的直接操作。
以上面计时器为例,React在渲染过程中,会首先根据render的结果将这个树状结构在JS里创建出来,这个树状结构就是虚拟DOM层,当我们的计数器发生变化时,React会将这个新的虚拟DOM和正在呈现的虚拟DOM进行对比,并找出其中的差异,然后用最少的DOM操作完成这个更新。
再回到我们的标题**如何理解React通过对DOM的模拟,最大限度地减少与DOM的交互?**这里我们可以做一下小结了,React通过引入虚拟DOM,当DOM发生修改时,在内存中进行差异对比,最大限度地的用最少的DOM操作完成这些操作。
随着深入了解,除去DOM,关于这句话的理解,其实也还包含了如下几个知识点,这里先列举出来:
- 批处理: React会将一系列对虚拟DOM的操作合并成一个批处理。这样可以减少实际DOM操作的次数,从而提高性能。React通过一些策略,例如合并相邻的setState调用,来优化实际DOM的更新。
- 差异算法(Reconciliation): 在组件状态发生变化时,React会通过比较新旧虚拟DOM树的差异,找出最小的更新操作。然后,只对实际需要更新的部分进行DOM操作。这就是所谓的“协调”(Reconciliation)过程。
- DOM更新的延迟执行: React会尽量将DOM更新的执行时机延迟到最合适的时候。例如,在浏览器的空闲时期,或者通过使用 requestAnimationFrame 等API来调度更新,以确保更新不会影响用户的交互体验。