前言
我是歌谣 最好的种树是十年前 其次是现在 今天继续给大家带来的是DOM-Diff讲解
环境配置
| npm init -y |
| yarn add vite -D |
修改page.json配置端口
| { |
| "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
| 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
| 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; |
| |
| |
| |
| |
| |
element.js
| class Element{ |
| constructor(type,props,children){ |
| this.type=type |
| this.props=props |
| this.children=children |
| } |
| |
| } |
| |
| export default Element |
index.js
| 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
| 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 |
| } |
运行结果

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!