DOM-Diff讲解

2023-10-24 15:02:26 浏览数 (2)

前言

我是歌谣 最好的种树是十年前 其次是现在 今天继续给大家带来的是DOM-Diff讲解

环境配置

代码语言:javascript复制
npm init -y
yarn add vite -D

修改page.json配置端口

代码语言:javascript复制
{
  "name": "react_ts",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "vite --port 3002",
    "server":"ts-node-dev ./server/app.ts"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@types/express": "^4.17.17",
    "@types/jquery": "^3.5.18",
    "express": "^4.18.2",
    "jquery": "^3.7.1",
    "ts-node-dev": "^2.0.0",
    "typescript": "^5.2.2",
    "vite": "^4.4.9"
  }
}

目录结构

domDiff.js

代码语言:javascript复制
import {
    ATTR,
    TEXT,
    REPLACE,
    REMOVE
  } from './pathTypes';
  
  let patches = {},
      vnIndex = 0;
  
  function domDiff (oldVDom, newVDom) {
    let index = 0;
    vNodeWalk(oldVDom, newVDom, index);
    return patches;
  }


  function vNodeWalk(oldNode,newNode,index){
    let vnPatch = [];


    if (!newNode) {
      vnPatch.push({
        type: REMOVE,
        index
      })
    } else if (typeof oldNode === 'string' && typeof newNode === 'string') {
      if (oldNode !== newNode) {
        vnPatch.push({
          type: TEXT,
          text: newNode
        })
      }
    } else if (oldNode.type === newNode.type) {
      const attrPatch = attrsWalk(oldNode.props, newNode.props);
      console.log(attrPatch,"attrPatch is")
      if (Object.keys(attrPatch).length > 0) {
        vnPatch.push({
          type: ATTR,
          attrs: attrPatch
        });
      }
  
      childrenWalk(oldNode.children, newNode.children);
    } else {
      vnPatch.push({
        type: REPLACE,
        newNode
      })
    }
  
    if (vnPatch.length > 0) {
      patches[index] = vnPatch;
    }


  }
  function attrsWalk (oldAttrs, newAttrs) {
    let attrPatch = {};
  
    for (let key in oldAttrs) {
      // 修改属性
      if (oldAttrs[key] !== newAttrs[key]) {
        attrPatch[key] = newAttrs[key];
      }
    }
  
    for (let key in newAttrs) {
      // 新增
      if (!oldAttrs.hasOwnProperty(key)) {
        attrPatch[key] = newAttrs[key];
      }
    }
  
    return attrPatch;
  }
  
  function childrenWalk (oldChildren, newChildren) {
    oldChildren.map((c, idx) => {
      vNodeWalk(c, newChildren[idx],    vnIndex);
    });
  }
  export default domDiff;

dopatch.js

代码语言:javascript复制
import {
    ATTR,
    TEXT,
    REPLACE,
    REMOVE
  } from './pathTypes';
  import { setAttrs, render } from './virtualDom';
  import Element from './element';
  
  let finalPatches = {},
      rnIndex = 0;
  
  function doPatch (rDom, patches) {
    finalPatches = patches;
    rNodeWalk(rDom);
  }
  
  function rNodeWalk (rNode) {
    const rnPatch = finalPatches[rnIndex   ],
          childNodes = rNode.childNodes;
    
    [...childNodes].map((c) => {
      rNodeWalk(c);
    });
  
    if (rnPatch) {
      patchAction(rNode, rnPatch);
    }
  }
  
  function patchAction (rNode, rnPatch) {
    rnPatch.map((p) => {
      switch (p.type) {
        case ATTR:
          for (let key in p.attrs) {
            const value = p.attrs[key];
  
            if (value) {
              setAttrs(rNode, key, value);
            } else {
              rNode.removeAttribute(key);
            }
          }
          break;
        case TEXT:
          rNode.textContent = p.text;
          break;
        case REPLACE:
          const newNode = (p.newNode instanceof Element)
                        ? render(p.newNode)
                        : document.createTextNode(p.newNode);
          
          rNode.parentNode.replaceChild(newNode, rNode);
          break;
        case REMOVE:
          rNode.parentNode.removeChild(rNode);
          break;
        default:
          break;
      }
    });
  }
  
  export default doPatch;
  
  // vNode = virtual Node
  // vnPatch = virtual Node patch
  // rNode = real Node
  // rnPatch = real Node patch

element.js

代码语言:javascript复制
class Element{
    constructor(type,props,children){
        this.type=type
        this.props=props
        this.children=children
    }


}


export default Element

index.js

代码语言:javascript复制
import { createElement, render, renderDom } from './virtualDom';
import domDiff from './domDiff';
 import doPatch from './doPatch';


const vDom1 = createElement('ul', { 
    class: 'list', 
    style: 'width: 300px; height: 300px; background-color: orange'
  }, [
    createElement('li', { 
      class: 'item', 
      'data-index': 0 
    }, [
      createElement('p', { 
        class: 'text'
      }, [
        '第1个列表项'
      ])
    ]),
    createElement('li', { 
      class: 'item', 
      'data-index': 1
    }, [
      createElement('p', { 
        class: 'text'
      }, [
        createElement('span', { 
          class: 'title' 
        }, [])
      ])
    ]),
    createElement('li', { 
      class: 'item', 
      'data-index': 2
    }, [
      '第3个列表项'
    ])
  ]);
  
const vDom2 = createElement('ul', {
  class: 'list-wrap',
  style: 'width: 300px; height: 300px; background-color: orange'
}, [
  createElement('li', {
    class: 'item',
    'data-index': 0
  }, [
    createElement('p', {
      class: 'title'
    }, [
      '特殊列表项'
    ])
  ]),
  createElement('li', {
    class: 'item',
    'data-index': 1
  }, [
    createElement('p', {
      class: 'text'
    }, [])
  ]),
  createElement('div', {
    class: 'item',
    'data-index': 2
  }, [
    '第3个列表项'
  ])
]);


const rDom = render(vDom1);
renderDom(
  rDom,
  document.getElementById('app')
);


const patches = domDiff(vDom1, vDom2);


doPatch(rDom, patches);


console.log(patches);

Virtualdom.js

代码语言:javascript复制
import Element from "./element";
import domDiff from "./domDiff"
function createElement(type, props, children) {
    return new Element(type, props, children)
}
function setAttrs(node,prop,value){
    switch(prop){
        case 'value':
            if(node.tagName==='INPUT'||node.tagName==='TEXTAREA'){
                node.value=value
            }else{
                node.setAttribute(prop,value)
            }
            break;
        case 'style':
            node.style.cssText=value
            break;
        default:
            node.setAttribute(prop,value);
            break
    }
}
function render (vDom) {
    const { type, props, children } = vDom,
          el = document.createElement(type);
    
    for (let key in  props) {
      setAttrs(el, key, props[key]);
    }
    
    children.map((c) => {
      c = c instanceof Element
          ?
          render(c)
          :
          document.createTextNode(c);
      
      el.appendChild(c);
    });
    return el;
  }


  function renderDom(rDom,rootEl){
    rootEl.appendChild(rDom)
  }


export {
    createElement,
    render,
    setAttrs,
    renderDom
}

运行结果

0 人点赞