中
秋
快
乐
前言
我是歌谣 最好的种树是十年前 其次是现在 今天继续给大家带来的是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
}
运行结果