首页 > 编程语言 >Vue源码学习(十四):diff算法patch比对

Vue源码学习(十四):diff算法patch比对

时间:2023-11-03 19:11:24浏览次数:44  
标签:el Vue 元素 patch oldVnode let 节点 Vnode 源码

好家伙,

本篇将会解释要以下效果的实现

 

1.目标

我们要实现以下元素替换的效果

gif:

 

以上例子的代码:

    //创建vnode
    let vm1 = new Vue({data:{name:'张三'}})
    let render1 = compileToFunction(`<a>{{name}}</a>`)
    let vnode1 = render1.call(vm1)
     document.body.appendChild(createELm(vnode1))

   //数据更新
     let vm2 = new Vue({data:{name:'李四'}})
     let render2 = compileToFunction(`<div>{{name}}</div>`)
     let vnode2 = render2.call(vm2)
   //属性添加
     let vm3 = new Vue({data:{name:'李四'}})
     let render3 = compileToFunction(`<div style="color:red">{{name}}</div>`)
     let vnode3 = render3.call(vm3)
    
     //patch 比对
      setTimeout(()=>{
        patch(vnode1,vnode2)
      },2000)

      setTimeout(()=>{
        patch(vnode2,vnode3)
      },3000)

 

以上例子中compileToFunction()方法的详细解释

Vue源码学习(四):<templete>渲染第三步,将ast语法树转换为渲染函数

一句话解释,这是一个将模板变为render函数的方法

 

开搞:

思路非常简单,依旧是对不同的情况分类处理

 

2.代码解释

patch.js

export function patch(oldVnode, Vnode) {
    //原则  将虚拟节点转换成真实的节点
    console.log(oldVnode, Vnode)
    console.log(oldVnode.nodeType)
    console.log(Vnode.nodeType)
    //第一次渲染 oldVnode 是一个真实的DOM
    //判断ldVnode.nodeType是否唯一,意思就是判断oldVnode是否为属性节点
    if (oldVnode.nodeType === 1) {
        console.log(oldVnode, Vnode)  //注意oldVnode 需要在加载 mount 添加上去  vm.$el= el

        let el = createELm(Vnode) // 产生一个新的DOM
        let parentElm = oldVnode.parentNode //获取老元素(app) 父亲 ,body
        //   console.log(oldVnode)
        //  console.log(parentElm)
        
        parentElm.insertBefore(el, oldVnode.nextSibling) //当前真实的元素插入到app 的后面
        parentElm.removeChild(oldVnode) //删除老节点
        //重新赋值
        return el
    }else{ //  diff
        console.log(oldVnode.nodeType)
        console.log(oldVnode, Vnode)
         //1 元素不是一样 
         if(oldVnode.tag!==Vnode.tag){
            //旧的元素 直接替换为新的元素
          return  oldVnode.el.parentNode.replaceChild(createELm(Vnode),oldVnode.el) 
         }
         //2 标签一样 text  属性 <div>1</div>  <div>2</div>  tag:undefined
         if(!oldVnode.tag){
             if(oldVnode.text !==Vnode.text){
                 return oldVnode.el.textContent = Vnode.text
             }
         }
         //2.1属性 (标签一样)  <div id='a'>1</div>  <div style>2</div>
         //在updataRpors方法中处理
         //方法 1直接复制
       let el = Vnode.el = oldVnode.el
       updataRpors(Vnode,oldVnode.data)
       //diff子元素 <div>1</div>  <div></div>
       let oldChildren = oldVnode.children || []
       let newChildren = Vnode.children || []
       if(oldChildren.length>0&&newChildren.length>0){ //老的有儿子 新有儿子
              //创建方法
              updataChild(oldChildren,newChildren,el)
       }else if(oldChildren.length>0&&newChildren.length<=0){//老的元素 有儿子 新的没有儿子
             el.innerHTML = ''
       }else if(newChildren.length>0&&oldChildren.length<=0){//老没有儿子  新的有儿子
             for(let i = 0;i<newChildren.length;i++){
                 let child = newChildren[i]
                 //添加到真实DOM
                 el.appendChild(createELm(child))
             }
       }
 
    }
}
function updataChild (oldChildren,ewChildren,el){

}
//添加属性
function updataRpors(vnode,oldProps={}){ //第一次
  let newProps = vnode.data ||{} //获取当前新节点 的属性
  let el = vnode.el //获取当前真实节点 {}
  //1老的有属性,新没有属性
  for(let key in oldProps){
      if(!newProps[key]){
          //删除属性
          el.removeAttribute[key] //
      }
  }
  //2演示 老的 style={color:red}  新的 style="{background:red}"
   let newStyle = newProps.style ||{} //获取新的样式
   let oldStyle = oldProps.style ||{} //老的
   for(let key in oldStyle){
       if(!newStyle[key]){
           el.style =''
       }
   }
  //新的
  for(let key in newProps){
      if(key ==="style"){
         for(let styleName in newProps.style){
             el.style[styleName] =  newProps.style[styleName]
         }
      }else if( key ==='class'){
          el.className = newProps.class
      }else{
          el.setAttribute(key,newProps[key])
      }
  }
}
//vnode 变成真实的Dom
export function createELm(vnode) {
    let { tag, children, key, data, text } = vnode
    //注意
    if (typeof tag === 'string') { //创建元素 放到 vnode.el上
        vnode.el = document.createElement(tag)  //创建元素 
        updataRpors(vnode)
        //有儿子
        children.forEach(child => {
            // 递归 儿子 将儿子渲染后的结果放到 父亲中
            vnode.el.appendChild(createELm(child))
        })
    } else { //文本
        vnode.el = document.createTextNode(text)
    }
    return vnode.el //新的dom
}

 

三个方法,我们一个个看

2.1.patch()

export function patch(oldVnode, Vnode) {
    //原则  将虚拟节点转换成真实的节点
    console.log(oldVnode, Vnode)
    console.log(oldVnode.nodeType)
    console.log(Vnode.nodeType)
    //第一次渲染 oldVnode 是一个真实的DOM
    //判断ldVnode.nodeType是否唯一,意思就是判断oldVnode是否为属性节点
    if (oldVnode.nodeType === 1) {
        console.log(oldVnode, Vnode)  //注意oldVnode 需要在加载 mount 添加上去  vm.$el= el

        let el = createELm(Vnode) // 产生一个新的DOM
        let parentElm = oldVnode.parentNode //获取老元素(app) 父亲 ,body
        //   console.log(oldVnode)
        //  console.log(parentElm)
        
        parentElm.insertBefore(el, oldVnode.nextSibling) //当前真实的元素插入到app 的后面
        parentElm.removeChild(oldVnode) //删除老节点
        //重新赋值
        return el
    }else{ //  diff
        console.log(oldVnode.nodeType)
        console.log(oldVnode, Vnode)
         //1 元素不是一样 
         if(oldVnode.tag!==Vnode.tag){
            //旧的元素 直接替换为新的元素
          return  oldVnode.el.parentNode.replaceChild(createELm(Vnode),oldVnode.el) 
         }
         //2 标签一样 text  属性 <div>1</div>  <div>2</div>  tag:undefined
         if(!oldVnode.tag){
             if(oldVnode.text !==Vnode.text){
                 return oldVnode.el.textContent = Vnode.text
             }
         }
         //2.1属性 (标签一样)  <div id='a'>1</div>  <div style>2</div>
         //在updataRpors方法中处理
         //方法 1直接复制
       let el = Vnode.el = oldVnode.el
       updataRpors(Vnode,oldVnode.data)
       //diff子元素 <div>1</div>  <div></div>
       let oldChildren = oldVnode.children || []
       let newChildren = Vnode.children || []
       if(oldChildren.length>0&&newChildren.length>0){ //老的有儿子 新有儿子
              //创建方法
              updataChild(oldChildren,newChildren,el)
       }else if(oldChildren.length>0&&newChildren.length<=0){//老的元素 有儿子 新的没有儿子
             el.innerHTML = ''
       }else if(newChildren.length>0&&oldChildren.length<=0){//老没有儿子  新的有儿子
             for(let i = 0;i<newChildren.length;i++){
                 let child = newChildren[i]
                 //添加到真实DOM
                 el.appendChild(createELm(child))
             }
       }
 
    }
}

patch()方法用于根据新的虚拟节点更新旧的虚拟节点以及对应的真实 DOM 元素。

 

首先判断旧的虚拟节点是否是一个真实 DOM 元素(即是否为属性节点),

--1--如果是,则表示这是第一次渲染,需要使用 createELm 函数创建新的 DOM 元素,并将其插入到旧的元素之前,最后再删除旧的元素,返回新创建的元素。

--2--如果不是第一次渲染,则进行 diff 操作,

  --2.1--首先判断新老节点的标签是否相同,如果不同,则直接使用新的节点替换旧的节点。

  --2.2--如果标签相同,则需要判断节点的文本内容和属性是否发生了变化,如果发生了变化,则通过 updataRpors 函数更新 DOM 元素属性或文本内容。

  --2.3--最后,需要 diff 子元素。

    --2.3.1--如果旧节点和新节点均有子元素,则需要将新旧子元素进行比较,通过 updataChild 函数更新旧节点的子元素与新节点的子元素。

    --2.3.2--如果旧节点有子元素而新节点没有,则直接将旧节点的内容清空;

    --2.3.3--如果新节点有子元素而旧节点没有,则直接将新节点的子元素添加到旧节点中。

 

2.2.updataRpors()

//添加属性
function updataRpors(vnode,oldProps={}){ //第一次
  let newProps = vnode.data ||{} //获取当前新节点 的属性
  let el = vnode.el //获取当前真实节点 {}
  //1老的有属性,新没有属性
  for(let key in oldProps){
      if(!newProps[key]){
          //删除属性
          el.removeAttribute[key] //
      }
  }
  //2演示 老的 style={color:red}  新的 style="{background:red}"
   let newStyle = newProps.style ||{} //获取新的样式
   let oldStyle = oldProps.style ||{} //老的
   for(let key in oldStyle){
       if(!newStyle[key]){
           el.style =''
       }
   }
  //新的
  for(let key in newProps){
      if(key ==="style"){
         for(let styleName in newProps.style){
             el.style[styleName] =  newProps.style[styleName]
         }
      }else if( key ==='class'){
          el.className = newProps.class
      }else{
          el.setAttribute(key,newProps[key])
      }
  }
}
updataRpors()是一个更新属性的方法,其主要功能是更新虚拟节点的属性,包括删除不再存在的属性、更新样式和类名等。

 

2.3.createELm()

//vnode 变成真实的Dom
export function createELm(vnode) {
    let { tag, children, key, data, text } = vnode
    //注意
    if (typeof tag === 'string') { //创建元素 放到 vnode.el上
        vnode.el = document.createElement(tag)  //创建元素 
        updataRpors(vnode)
        //有儿子
        children.forEach(child => {
            // 递归 儿子 将儿子渲染后的结果放到 父亲中
            vnode.el.appendChild(createELm(child))
        })
    } else { //文本
        vnode.el = document.createTextNode(text)
    }
    return vnode.el //新的dom
}


 createELm()是一个用于创建和渲染虚拟DOM的函数.

函数名称为`createELm`,它接收一个参数`vnode`,这个参数是一个虚拟DOM节点对象。

这段代码的主要作用是根据传入的虚拟DOM节点数据结构(`vnode`)创建一个相应的实际DOM元素,并返回该元素。

如果虚拟DOM节点包含子节点,它会递归地为每个子节点创建相应的DOM元素并添加到父节点的DOM元素中。

 

 

标签:el,Vue,元素,patch,oldVnode,let,节点,Vnode,源码
From: https://www.cnblogs.com/FatTiger4399/p/17806572.html

相关文章

  • vue 上传照片插件
    Vue是一种流行的JavaScript框架,拥有轻量级、直观且易于学习的特点,并提供丰富的插件集合。其中,上传照片插件是Vue的常用插件之一。上传照片插件能够快速地将照片上传到服务器,支持多图上传、拍照上传和拖拽上传等功能。此外,插件还可以对上传过程进行自定义,如上传时的进度条显示、图......
  • 在线点餐系统(课设) springboot 免费源码
    项目源码获取方式见文章末尾处项目技术数据库:Mysql8.0数据表数:5张开发工具:idea前端技术:Springboot+MybatisPlus后端技术:BootStrap+Thymeleaf功能简介该项目是一个在线点餐系统,分为用户端和商家端。商家端:    登录/注册    首页    菜单管理    订单管理   ......
  • 用vite创建vue3项目
    打算用vite创建vue3项目1.安装npminitvite-app<项目名称>cd<项目名称>npminstallnpmrundev##执行完以上命令,就意味着你的vue3.0项目已经用上了vite了提示  deprecated 改为:npminit@vitejs/app又提示deprecated  改为:npmcreatevite@latest2输......
  • Vue3Router路由传参
    import{useRouter}from'vue-router'//首先在setup中定义constrouter=useRouter()//字符串router.push('home')//对象router.push({path:'home'})//命名的路由router.push({name:'user',params:{userId:'123......
  • 智慧工地管理系统源码,数据进行实时采集分析,危险事件智能预警和联动管控
    以施工现场风险预知和联动预控为目标,将智能AI、传感技术、人像识别、监控、虚拟现实、物联网、5G、大数据、互联网等新一代科技信息技术植入到建筑、机械、人员穿戴设施、场地进出关口等各类设备中,实现工程管理与工程施工现场的整合,并构建成为一个完整的智慧工地管理系统,实现对工地......
  • java语言实现医院绩效考核系统源码
    医院绩效考核系统通常是指对医院员工个体的工作绩效进行评估的工具,其目的是通过评估员工的工作表现和成果,为医院的薪酬、晋升、培训和发展等方面提供依据。绩效考核系统通常包括具体的考核指标、考核标准、考核周期等,通过量化的方式对员工的工作绩效进行评估。DRG点数绩效DRG点数......
  • vue数据双向绑定的原理是什么?
    Vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。需要Observe的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter,这样的话,给这个对象的某个......
  • Vue上传视频组件
    整合上传组件<el-form-itemlabel="上传视频"><el-upload:on-success="handleVodUploadSuccess":on-remove="handleVodRemove":before-remove="beforeVodRemove":on-exceed="h......
  • [vue-router] hash模式与history模式的区别
    单页面应用(SPA)单页面应用程序将所有的活动局限于一个Web页面中,在该Web页面初始化时加载相应的HTML、JavaScript和CSS。一旦页面加载完成,单页面应用不会因为用户的操作而进行页面的重新加载或跳转。取而代之的是利用JavaScript动态的变换HTML的内容,从而实现UI与用户的交互。由......
  • centos7源码安装MySQL 5
    安装前准备检查系统是否安装过mysql:rpm-qa|grepmysql查询所有mysql对应的文件夹,(人工判断不需要后)删除:whereismysqlfind/-namemysql卸载CentOS7系统自带mariadb:#查看系统自带的Mariadbrpm-qa|grepmariadb#卸载系统自带的Mariadbrpm-e--nodepsmariadb-libs#删......