目录
VUE3的h/createVNode函数【vue的概念模型】
Virtual DOM(组件化的概念模型)
VirtualDOM更新
WHY DOM-DIFF?
DOM-DIFF原理
DOM-DIFF伪代码
DOM-DIFF分类讨论:属性变更
DOM-DIFF分类讨论:节点类型不同
DOM-DIFF分类讨论:子节点变更1
DOM-DIFF分类讨论:子节点变更2
小结总结:常见误区
VUE3的h/createVNode函数【vue的概念模型】
h()是vue3提供的渲染函数
<h1>{{blogTitle}}</h1> => h('h1',{},this.blogTitle)
<anchored-heading :level="1">
<span>Hello</span>world!
</anchored-heading>
=>
h(
'anchored-heading',
{level: 1},
{default:()=>[h('span','Hello'),'world!']}
)
Virtual DOM(组件化的概念模型)
让写作,创造更加方便
<Content>
<Button><div>点我</div></Button>
<p>Hi</p>
</Content>
虚拟DOM结构式一个概念模型,像DOM又不是DOM
VirtualDOM更新
- 替换(例如Panel->Swiper)
- 插入(例如订单列表新增一个订单)
- 属性替换(例如修改div的颜色,修改Tabs的activeTab)
- 删除
思考:Virtual DOM更新怎么应用到DOM?
WHY DOM-DIFF?
function SomeComponent({a}) {
return <div>
<span>...</span>
<Button>...</Button>
{a && <p>新增的节点</p>}
</div>
}
<SomeComponent a={true} />
// 变成
<SomeComponent a={false} />
不用动态规划【一个算法简单有效又能工作,好读,易理解,就没必要用复杂算法】
不预先分析,要事后检验算法
DOM-DIFF原理
DOM-DIFF伪代码
//主体逻辑
function domDiff(...) {
let VDOMOld // 上一次调用SomeComponent产生的virtualDOM
...
update() {
const vdomNext = SomeComponent(...)
const patches = domDiff(vDOMOld, vDOMNext)
vDOMOld = vDOMNext
apply(dom, patches)
}
}
DOM-DIFF分类讨论:属性变更
- 节点相同
- -vDOMOld.type === vDOMNext.type
- 例如:div的样式发生变更
- 方案:替换属性的补丁(更新)
DOM-DIFF分类讨论:节点类型不同
- 节点类型发生变化
- 例如:vue-router切换
- 方案:替换全部的补丁(更新)
<div>
<Couter />
</div>
// 到
<span>
<Counter />
</span>
DOM-DIFF分类讨论:子节点变更1
<ul>
<li>Duke</li>
<li>Villanova</li>
</ul>
// 到
<ul>
<li>Connecticut</li>
<li>Duke</li>
<li>Villanova</li>
</ul>
- 如上情况中,如果想要识别Connecticut是插入,需要动态规划算法(增加比较的复杂度)
- DIFF算法可以考虑顺序比较
DOM-DIFF分类讨论:子节点变更2
<ul>
<li key="2015">Duke</li>
<li key="2016">Villanova</li>
</ul>
// 到
<ul>
<li key="2014">Connecticut</li>
<li key="2015">Duke</li>
<li key="2016">Villanova</li>
</ul>
- 如上情况中,当带有key时,vue可以识别变更
/**
* 这个是伪代码不可以真的执行
*/
// diff主体,在产生东西的时候,生成器函数加个*
function* domDIFF(vDOM1, vDOM2) {
// 插入 || 删除
if(!vDOM1 || !vDOM2) {
// 迭代器作用,yield会往外抛值
yield new InsertPatch(vDOM1, vDOM2)
return
}
if(vDOM1.type === vDOM2.type) { //节点类型一致
if(vDOM1.key === vDOM2.key) { // key值都一样
yield new AttributePatch(vDOM1, vDOM2) // 可能存在属性变更
yield * domDIFFArray(vDOM1.children, vDOM2.children) // diff当前vDOM的子节点
} else {
yield new ReplacePatch(vDOM1, vDOM2)
}
return
} else {
yield new ReplacePatch(vDOM1, vDOM2)
}
}
function toMap(arr) { // 做成key:value
const map = new Map()
arr.forEach(item => {
if(item.key)
map.set(item.key, item)
})
return map
}
// diff当前vDOM的子节点
function * domDiffArray(arr1, arr2) {
// 插入 || 删除
if(!arr1 || !arr2) {
yield new ReplacePatch(vDOM1, vDOM2)
return
}
const m1 = toMap(arr1)
const m2 = toMap(arr2)
// 需要删除的VDOM,m1有key,m2没有key时
const deletes = arr1.filter( (item, i) => {
return item.key ?
!m2.has(item.key)
: i >= arr2.length // 如果arr2子节点比arr1少,那也是要删除多的
})
for(let item of deletes){
yield new ReplacePatch(item, null)
}
// 需要Replace的VDOM
for(let i = 0; i <arr1.length; i++) {
const a = arr1[i]
if(a.key ) { // 有key的
if(m2.has(a.key)) {
yield * domDIFF(a, m2.get(a.key))
}
}
else {// 没有key的就去找对应的位置i
if(i < arr2.length) {// i 得小于 arr2的数
yield * domDIFF(a, arr2[i])
}
}
}
// 需要Insert的VDOM;arr1中有,arr2中没有
for(let i = 0; i <arr2.length; i++) {
const b = arr2[i]
if(b.key) {
if(!m1.has(b.key)) {// m1中没有arr2中的这个key时渲染
yield new InsertPatch(i, b)
}
}else { // 没有key时,长度i大于arr1就会渲染
if(i >= arr1.length) {
yield new InsertPatch(i, arr[2])
}
}
}
}
class InsertPatch {
constructor(pos, to){
this.pos = pos
this.to = to
}
}
class ReplacePatch {
constructor(from, to){
this.form = from
this.to = to
}
}
小结总结:常见误区
- 为什么更新被称作patch(补丁)?这种增量更新的模式我们称为补丁,热更新
- 为什么不用动态规划(O(n^3))算法?我们需要domDiff速度非常快,动态规划虽然会比对出最小更新,但是在比对时间上太耗时了,我们不一定要最小更新,比对时间+更新时间的最小值才是我们要的。