虚拟DOM优点
减少DOM操作
- 虚拟 DOM 可以将多次操作合并为一次操作,比如你添加 1000 个节点,却是一个接一个操作的(减少频率)
- 虚拟 DOM 借助 DOM diff 可以把多余的操作省掉,比如你添加 1000 个节点,其实只有 10 个是新增的(减少范围)
跨平台
- 虚拟 DOM 不仅可以变成 DOM,还可以变成小程序、iOS 应用、安卓应用,因为虚拟 DOM 本质上是一个 JS 对象
React虚拟DOM
// React
const vNode = {
key: null,
props: {
children: [ // 子元素们
{ type: 'span', ... },
{ type: 'span', ... }
],
className: "red" // 标签上的属性
onClick: () => {} // 事件
},
ref: null,
type: "div", // 标签名 or 组件名
...
}
Vue虚拟DOM
// Vue
const vNode = {
tag: "div", // 标签名 or 组件名
data: {
class: "red", // 标签上的属性
on: {
click: () => {} // 事件
}
},
children: [ // 子元素们
{ tag: "span", ... },
{ tag: "span", ... }
],
...
}
如何创建虚拟DOM
// React.createElement
createElement('div',{className:'red',onClick:()=> {}},[
createElement('span', {}, 'span1'),
createElement('span', {}, 'span2')
]
)
// Vue(只能在 render 函数里得到 h)
h('div', {
class: 'red',
on: {
click: () => { }
},
}, [h('span',{},'span1'), h('span', {}, 'span2'])
使用JSX简化
// React JSX
<div className="red" onClick="{()=> {}}">
<span>span1</span>
<span>span2</span>
</div>
// Vue Template
h('div', {
class: 'red',
on: {
click: () => { }
},
}, [h('span',{},'span1'), h('span', {}, 'span2'])
// React
<div className="red" onClick={fn}>
<span>span1</span>
<span>span2</span>
</div>
// 通过 babel 转为 createElement 形式
// Vue Template
<div class="red" @click="fn">
<span>span1</span>
<span>span2</span>
</div>
// 通过 vue-loader 转为 h 形式
// 现在创建虚拟 DOM 的方法
// React
<div className="red" onClick={fn}>
<span>span1</span>
<span>span2</span>
</div>
// 通过 babel 转为 createElement 形式
// Vue Template
<div class="red" @click="fn">
<span>span1</span>
<span>span2</span>
</div>
// 通过 vue-loader 转为 h 形式
总结
虚拟 DOM 是什么
- 虚拟dom的本质一个能代表dom树的JS对象,包含tag、props、children三个属性
- 通常含有标签名、标签上的属性、事件监听和子元素们,以及其他属性
虚拟 DOM 有什么缺点
- 需要额外的创建函数,如 createElement 或 h,但可以通过 JSX 来简化成 XML 写法,会依赖打包工具。
DOM diff
/*
什么是 DOM diff
就是一个函数,我们称之为 patch
patches = patch(oldVNode, newVNode)
patches 就是要运行的 DOM 操作,可能长这样:
*/
[
{type: 'INSERT', vNode: ... },
{type: 'TEXT', vNode: ... },
{type: 'PROPS', propsPatch: [...]}
]
Tree diff
- 将新旧两棵树逐层对比,找出哪些节点需要更新
- 如果节点是组件就看 Component diff
- 如果节点是标签就看 Element diff
Component diff
- 如果节点是组件,就先看组件类型
- 类型不同直接替换(删除旧的)
- 类型相同则只更新属性
- 然后深入组件做 Tree diff(递归)
Element diff
- 如果节点是原生标签,则看标签名
- 标签名不同直接替换,相同则只更新属性
- 然后进入标签后代做 Tree diff(递归)
存在一些小小的问题
DOM diff在同层级对比中有bug。造成页面渲染错误。
同一层级的一组节点可以通过唯一的id进行区分, 所以可以给节点设定唯一的key。
从而消除bug。key只能是number和string类型,一定不要用index作为key值。