vue3原理和源码分析 - VirtualDOM和DOMDIFF

文章详细介绍了Vue3中的h/createVNode函数,阐述了VirtualDOM作为组件化概念模型的优势。接着,讨论了DOM-DIFF算法在处理VirtualDOM更新时的角色,包括属性变更、节点类型变化和子节点更新的不同场景,以及为何不采用动态规划算法的原因。最后,文章总结了DOM-DIFF更新的本质和常见误区。
摘要由CSDN通过智能技术生成

目录

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速度非常快,动态规划虽然会比对出最小更新,但是在比对时间上太耗时了,我们不一定要最小更新,比对时间+更新时间的最小值才是我们要的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值