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


    好家伙,

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

     

    1.目标

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

    gif:

     

    以上例子的代码:

    复制代码
        //创建vnode
        let vm1 = new Vue({data:{name:'张三'}})
        let render1 = compileToFunction(`{{name}}`)
        let vnode1 = render1.call(vm1)
         document.body.appendChild(createELm(vnode1))
    
       //数据更新
         let vm2 = new Vue({data:{name:'李四'}})
         let render2 = compileToFunction(`
    {{name}}
    `) let vnode2 = render2.call(vm2) //属性添加 let vm3 = new Vue({data:{name:'李四'}}) let render3 = compileToFunction(`
    "color:red">{{name}}
    `) let vnode3 = render3.call(vm3) //patch 比对 setTimeout(()=>{ patch(vnode1,vnode2) },2000) setTimeout(()=>{ patch(vnode2,vnode3) },3000)
    复制代码

     

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

    Vue源码学习(四):渲染第三步,将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  属性 
    1
    2
    tag:undefined
    if(!oldVnode.tag){ if(oldVnode.text !==Vnode.text){ return oldVnode.el.textContent = Vnode.text } } //2.1属性 (标签一样)
    1
    2
    //在updataRpors方法中处理 //方法 1直接复制 let el = Vnode.el = oldVnode.el updataRpors(Vnode,oldVnode.data) //diff子元素
    1
    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){ 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  属性 
    1
    2
    tag:undefined
    if(!oldVnode.tag){ if(oldVnode.text !==Vnode.text){ return oldVnode.el.textContent = Vnode.text } } //2.1属性 (标签一样)
    1
    2
    //在updataRpors方法中处理 //方法 1直接复制 let el = Vnode.el = oldVnode.el updataRpors(Vnode,oldVnode.data) //diff子元素
    1
    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){ 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元素中。

     

     

  • 相关阅读:
    Hibernate EntityManager 指南
    R语言使用马尔可夫链对营销中的渠道归因建模
    Docker简介与安装
    包管理工具--》其他包管理器之cnpm、pnpm、nvm
    深度学习深陷困境
    如何成为一名高级数字 IC 设计工程师(1-6)Verilog 编码语法篇:经典数字 IC 设计
    springboot 毕业设计管理系统
    算法7.从暴力递归到动态规划0
    在Linux上安装QQ
    我怀疑这是IDEA的BUG,但是我翻遍全网没找到证据!
  • 原文地址:https://www.cnblogs.com/FatTiger4399/p/17806572.html