一、react 的虚拟 DOM 和 Diff 算法
-
虚拟
DOM
和diff
算法是React
中非常核心的两个概念, 我们需要对此有一个很全面的认知。这对于我们用脚手架开发项目, 尤其是企业中前后端分离的项目(类似: 后台管理系统)等有很大的帮助。 -
对于虚拟
DOM
的内部执行流程,如下所示:
- 用
JavaScript
对象结构表示DOM
树的结构;然后用这个树构建一个真正的DOM
树,插到文档当中; - 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异;
- 把步骤 2 所记录的差异应用到步骤 1 所构建的真正的
DOM
树上,视图就更新了。
- 对于虚拟
DOM
的原理剖析,如下所示:
-
Virtual DOM
本质上就是在JS
和DOM
之间做了一个缓存。可以类比CPU
和硬盘,硬盘读取速度比较慢,我们会就在它们之间加缓存条; - 反之, 既然
DOM
运行速度慢,那么我们就在JS
和DOM
之间加个缓存。JS
只操作Virtual DOM
,最后的时候再把变更的结果写入DOM
。
- 对于
diff
算法,如下所示:
- 如果两棵树的根元素类型不同,
React
会销毁旧树,创建新树 - 对于类型相同的
React DOM
元素,React
会对比两者的属性是否相同,只更新不同的属性; 当处理完这个DOM
节点,React
就会递归处理子节点。 - 遍历插入元素, 如果没有
key
,React
将改变每一个子删除重新创建; 为了解决这个问题,React
提供了一个key
属性。当子节点带有key
属性,React
会通过key
来匹配原始树和后来的树。
-
对于
diff
算法的执行过程,通过绑定key
,React
就知道带有key '1024'
的元素是新的,对于'1025'
和'1026'
仅仅移动位置即可。 -
对于其中
key
的使用注意,如下所示:
-
key
属性只会在React
内部使用,不会传递给组件 - 在遍历数据时,推荐在组件中使用
key
属性,<li key={obj.id}>{obj.t}</li>
-
key
只需要保持与他的兄弟节点唯一即可,不需要全局唯一 - 尽可能的减少数组
Index
作为key
,数组中插入元素的等操作时,会降低效率
- 对于
react
的虚拟DOM
和Diff
算法,可应用于九宫格案例,代码如下所示:
<script type="text/babel">
class FlexView extends React.Component {
constructor (props) {
super(props)
this.state = {
shopArr: []
}
}
static defaultProps = {
dataArr: [
{
"icon": "f1",
"name": "番茄"
},
{
"icon": "f2",
"name": "苹果"
},
{
"icon": "f3",
"name": "水蜜桃"
},
{
"icon": "f4",
"name": "香蕉"
},
{
"icon": "f5",
"name": "蓝莓"
},
{
"icon": "f6",
"name": "菠萝"
},
{
"icon": "f7",
"name": "草莓"
},
{
"icon": "f8",
"name": "猕猴桃"
},
{
"icon": "f9",
"name": "橙子"
}
]
}
render () {
return (
<div className="box">
<div className="top">
<button onClick={() => this._addShop()}>Add</button>
<button onClick={() => this._removeShop()}>Remove</button>
</div>
<div className="bottom">
{this.state.shopArr}
</div>
</div>
)
}
// 添加商品的方法
_addShop () {
// 相关的变量
const cols = 3, shopW = 100, shopH = 120, width = 320, height = 420
// 取出数据
const { dataArr } = this.props
// 取出下标
const index = this.state.shopArr.length
if (index >= 9) {
alert('已经买了很多水果,不能再买了')
return
}
// 求出子组件的行和列
const row = Math.floor(index / cols)
const col = Math.floor(index % cols)
// 求出当前的盒子的left和top
const xSpace = (width - cols * shopW) / (cols - 1)
const ySpace = (height - 3 * shopW) / 2
const left = col * (shopW + xSpace)
const top = row * (shopH + ySpace)
// 创建子组件装入数组
const shopView = (
<div className="item" style={{left, top}} key={index}>
<img src={'images/' + dataArr[index].icon + '.png'}
style={{width: shopW * 0.8, height:shopW*0.8}}
/>
<span>{dataArr[index].name}</span>
</div>
)
// 更新状态
const tempArr = this.state.shopArr
tempArr.push(shopView)
this.setState({
shopArr: tempArr
})
}
// 删除商品的方法
_removeShop () {
const tempArr = this.state.shopArr
if (tempArr.length === 0) {
alert('购物车空空如也')
return
}
tempArr.pop()
this.setState({
shopArr: tempArr
})
}
}
// 渲染组件
ReactDOM.render(<FlexView />, document.getElementById('app'))
</script>