DOM-Diff讲解
中
秋
快
乐
前言
我是歌谣 最好的种树是十年前 其次是现在 今天继续给大家带来的是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; // vNode = virtual Node // vnPatch = virtual Node patch // rNode = real Node // rnPatch = real Node patch
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帮你做增删改查!!