三、虚拟DOM理解:React高效渲染的核心机制
1. 虚拟DOM的概念
虚拟DOM(Virtual DOM)是React中用于描述真实DOM结构的轻量级JavaScript对象。它是真实DOM的抽象表示,通过高效的Diff算法协调更新,减少直接操作真实DOM的开销17。
通俗理解: 想象虚拟DOM是建筑的设计蓝图,而真实DOM是建好的房子。修改蓝图(虚拟DOM)比拆墙重建(直接操作真实DOM)更快、更安全。
代码示例:
// 虚拟DOM对象的结构(简化版)
const virtualNode = {
type: 'div', // 元素类型
props: { // 属性
className: 'container',
children: [
{ type: 'h1', props: { children: 'Hello World' } }
]
}
};
关键点:
- 虚拟DOM本质是普通JS对象,操作成本远低于真实DOM9。
- 每次状态更新时,React会生成新的虚拟DOM树,与旧树对比后更新差异部分110。
2. 创建虚拟DOM的两种方式
(1) JSX(推荐)
// JSX语法(Babel编译后转为虚拟DOM)
const VDOM = (
<div id="app">
<p>React is awesome!</p>
</div>
);
(2) React.createElement()
// 原生JS创建(嵌套多层时复杂)
const VDOM = React.createElement(
'div',
{ id: 'app' },
React.createElement('p', {}, 'React is awesome!')
);
对比建议:
- JSX更直观:接近HTML语法,推荐日常使用。
- createElement更底层:适用于动态生成复杂结构(如循环渲染)。
3. 虚拟DOM vs 真实DOM:核心差异
4. Diff算法:虚拟DOM高效的秘密
核心规则:
-
同层比较:仅对比同一层级的节点,跨层级直接重建(避免O(n³)复杂度)
-
类型判断:节点类型不同(如
div
变span
)则直接替换整个子树 -
Key优化:列表项添加唯一
key
,帮助React识别元素移动/增删
代码示例(索引值index与数据的唯一标识id):
class Person extends React.Component{
state = {
persons:[
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
]
}
add = ()=>{
const {persons} = this.state
const p = {id:persons.length+1,name:'小王',age:20}
this.setState({persons:[p,...persons]})
}
render(){
return (
<div>
<h2>展示人员信息</h2>
<button onClick={this.add}>添加一个小王</button>
<h3>使用index(索引值)作为key</h3>
<ul>
{
this.state.persons.map((personObj,index)=>{
return <li key={index}>{personObj.name}---{personObj.age}<input type="text"/></li>
})
}
</ul>
<hr/>
<hr/>
<h3>使用id(数据的唯一标识)作为key</h3>
<ul>
{
this.state.persons.map((personObj)=>{
return <li key={personObj.id}>{personObj.name}---{personObj.age}<input type="text"/></li>
})
}
</ul>
</div>
)
}
}
执行效果图解(添加小王前):
执行效果图解(添加小王后):
解释
慢动作回放----使用index索引值作为key
初始数据:
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
初始的虚拟DOM:
<li key=0>小张---18<input type="text"/></li>
<li key=1>小李---19<input type="text"/></li>
更新后的数据:
{id:3,name:'小王',age:20},
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
更新数据后的虚拟DOM:
<li key=0>小王---20<input type="text"/></li>
<li key=1>小张---18<input type="text"/></li>
<li key=2>小李---19<input type="text"/></li>
-----------------------------------------------------------------
慢动作回放----使用id唯一标识作为key
初始数据:
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
初始的虚拟DOM:
<li key=1>小张---18<input type="text"/></li>
<li key=2>小李---19<input type="text"/></li>
更新后的数据:
{id:3,name:'小王',age:20},
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
更新数据后的虚拟DOM:
<li key=3>小王---20<input type="text"/></li>
<li key=1>小张---18<input type="text"/></li>
<li key=2>小李---19<input type="text"/></li>
说明:Key帮助React识别节点移动,避免全列表重新渲染。
5. 虚拟DOM的性能优化策略
-
合理使用Key
- 避免用数组索引,优先用数据唯一ID
-
组件记忆化
-
用
React.memo
缓存组件,避免无意义重渲染:const MemoizedComponent = React.memo(({ data }) => { return <div>{data}</div>; });
-
-
回调函数缓存
-
用
useCallback
避免函数重复创建:const handleClick = useCallback(() => { console.log('Clicked!'); }, []);
-
-
代码分割(React.lazy)
-
减少初次渲染的虚拟DOM树规模:
const LazyComponent = React.lazy(() => import('./HeavyComponent'));
-
结语:为什么虚拟DOM是React的基石?
虚拟DOM通过内存计算+批量更新,解决了直接操作DOM的性能瓶颈,同时为跨平台(如React Native)提供了统一抽象层17。对初学者来说,掌握其核心思想比深究算法更重要——只需记住:“数据变,则虚拟DOM变;React负责高效更新”。
经典面试题:
1). react/vue中的key有什么作用?(key的内部原理是什么)?
2). 为什么遍历列表时,key最好不要用index?
虚拟DOM中key的作用:
1). 简单的说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用。
2). 详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】, 随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
a. 旧虚拟DOM中找到了与新虚拟DOM相同的key: (1).若虚拟DOM中内容没变, 直接使用之前的真实DOM (2).若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
b. 旧虚拟DOM中未找到与新虚拟DOM相同的key 根据数据创建新的真实DOM,随后渲染到到页面
用index作为key可能会引发的问题:
1). 若对数据进行:逆序添加、逆序删除等破坏顺序操作: 会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
2). 如果结构中还包含输入类的DOM: 会产生错误DOM更新 ==> 界面有问题。
3). 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作, 仅用于渲染列表用于展示,使用index作为key是没有问题的。