目标
本文的目标:理解 JSX、虚拟DOM,以及 vdom 的优点和缺点,理解这些有助于对项目的优化和对虚拟DOM的理解。
JSX 是什么
JSX 权威解释 JSX 是一种 JavaScript 的语法扩展,它允许我们在 JavaScript 代码中直接书写类似于 HTML 的标记语言,从而使得编写 React 组件更加方便和直观。
既然是语法扩展,那么 JS 解析器肯定是不认识的,就需要 babel 将其转换为 JS 解析器能理解的 JS 语法代码。 转换由 babel 相关插件附理,下面是转换前后的对比:
// ./src/main.jsx
const el = (
<div>
hello, <p key="111" style={{ color: 'red' }}>GWJ</p>
{Math.random() < 0.5 && <main>Content</main>}
</div>
);
转换为JS语法:
const el = const el = jsx('div', {
children: [
'hello',
jsx('p', {
style: {
color: 'red',
},
children: 'GaoWuJie',
}),
Math.random() < 0.5 &&
jsx('main', {
children: 'Content',
}),
],
});
VDOM
jsx 函数最终在运行时生成的对象,就称作为 VDOM。虚拟DOM用最小的内存用来描述真实的DOM,即用最小的内存用来描述 UI 界面。下面就是 JSX 在运行时生成的 vdom(虚拟对象树):
const el = {
type: 'div',
props: {
children: [
'hello',
{
type: 'p',
props: {
style: { color: 'red' },
children: 'GWJ'
}
},
{
type: 'main',
props: {
children: 'Content'
}
}
]
}
}
优点
- 提高一般性
更新时性能:VDOM 可以避免不必要的 DOM 操作,因为它可以比较两次VDOM之间的差异,只更新需要更新的部分。这种方式比直接操作 DOM 更快,因为直接操作 DOM 会导致浏览器重绘和重排,而VDOM 可以减少这些操作,因为按需更新。
例外:
- 当 VDOM tree 非常大时,VDOM diff 操作是很耗时的,而 VDOM diff 又是同步的,这时 VDOM 反而是累赘;所以,我们尽量要组件细分化,频繁更新尽可局部性。
- 当 VDOM diff 后发现更新的 dom 有很多时,VDOM diff 反而也是累赘了。
总结:当更新量小时,vdom diff 才有助于性能的提升,所以我们尽量要组件细分化,频繁更新尽可能局部性。
-
提高开发效率:VDOM 可以让开发者更方便地进行
组件化开发,因为它可以将整个组件树看作一个单一的整体,而不是分散的部分。这使得开发者可以更快地编写和维护代码。 -
跨平台支持:VDOM 不依赖于浏览器的实现,因为就是 VDOM 就是 JS 对象。因此可以在多个平台上使用,例如 Node.js、React Native 等。
缺点
- 初次生成造成性能浪费:初次渲染时,由于还没有前一次的 VDOM tree,React 需要先生成完整的 VDOM tree,然后再将其转换为真实的 DOM tree,这个过程比直接操作 DOM 更加耗时。
注意: 当项目很庞大时,VDOM tree 很大,VDOM 在初次渲染时可能会带来一些额外的开销,这个开销可能无法被忽略。在这种情况下,我们可以采取一些优化措施来提高初次渲染的性能,比如使用
代码拆分、懒加载等技术来减少页面的初始渲染量。
另外,需要注意的是,虚拟 DOM 可以帮助我们避免一些常见的性能问题,比如频繁的 DOM 操作、重复的元素计算等。在项目很庞大时,这些问题可能会更加突出,因此虚拟 DOM 对性能的优化作用可能也更加明显。 总之,在实际开发中,我们需要根据具体情况来选择合适的技术和优化策略,以提高应用的性能和用户体验。
-
需要额外的内存和计算开销:虚拟 DOM 需要在内存中维护一个虚拟的 DOM 树,因此会占用一定的内存空间。而且每次渲染都需要计算虚拟 DOM 和真实 DOM 之间的差异,这也会占用一定的计算资源,这也是无法避免的,因为要按需更新 DOM 不不得对前后 VDOM 进行计算,更新需要更新的 DOM。
-
不支持直接操作 DOM:VDOM 不能直接操作 DOM,因此在一些需要直接操作 DOM 的场景下可能不太方便。比如实现一些高级动画效果可能需要直接操作 DOM。
-
可能存在重复渲染的问题:因为 VDOM 是在内存中维护的,所以在某些情况下它可能会不准确,导致组件重复渲染。这个问题可以通过使用 React.memo 或 PureComponent 进行优化来避免。
JSX 细节
一些历史
React 17 以前,JSX语法babel 编译,会引入下面的代码,这需要我们手动引入 React
import React from 'react';
React.createElement ...
17 以及之后,无需我们手动引入 jsx api,babel 编译时,会自动引入如下代码:
import { jsxDEV } from 'react/jsx-runtime';
jsxDEV(......);
当然,jsx 函数和 React.createElement 是一样的,都是生成 ReactElement,即vdom