项目结构

public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
src/index.js
import {createElement, render, renderDom} from './virtualDom'
import domDiff from './domDiff'
import doPatch from './doPatch'
const vDomOld = createElement('ul', {
class: 'list1',
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'}, ['第2个列表项'])
]),
createElement('li', {
class: 'item',
'data-index': 2
}, [
createElement('p', {class: 'text'}, ['第3个列表项'])
]),
createElement('li', {
class: 'item',
'data-index': 3
}, [
createElement('p', {class: 'text'}, ['第4个列表项'])
])
]
)
const rDom = render(vDomOld);
renderDom(rDom,document.getElementById('app'));
console.log(vDomOld)
console.log(rDom)
const vDomNew = createElement('ul', {
class: 'list2',
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'}, ['第2个列表项'])
]),
createElement('li', {
class: 'item',
'data-index': 2
}, [
createElement('p', {class: 'text'}, ['第3个列表项'])
]),
createElement('li', {
class: 'item',
'data-index': 3
}, [
createElement('h1', {class: 'text'}, ['零三的笔记','https://web03.cn'])
])
]
)
const patches = domDiff(vDomOld,vDomNew)
console.log(patches)
doPatch(rDom, patches)
src/virtualDom.js
import Element from "./Element";
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)
}
}
function render(vDom) {
const {type, props, children} = vDom,
el = document.createElement(type);
for (let key in props) {
setAttrs(el,key, props[key])
}
children.map(cEl => {
if (cEl instanceof Element){
cEl = render(cEl)
} else {
cEl = document.createTextNode(cEl)
}
el.appendChild(cEl)
})
return el;
}
function renderDom(rDom, rooTel) {
rooTel.appendChild(rDom)
}
export {
createElement,
render,
renderDom,
setAttrs
}
src/pathType.js
const ATTR = 'ATTR',
TEXT = 'TEXT',
REPLACE = 'REPLACE',
REMOVE = 'REMOVE';
export {
ATTR,
TEXT,
REPLACE,
REMOVE
}
src/domDiff.js
import {
ATTR,
TEXT,
REPLACE,
REMOVE
} from './patchTypes'
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 attrPath = attrsWalk(oldNode.props, newNode.props);
if (Object.keys(attrPath).length > 0){
vnPatch.push({
type: ATTR,
attrs: attrPath
})
}
childrenWalk(oldNode.children, newNode.children)
} else {
vnPatch.push({
type: REPLACE,
newNode
})
}
if (vnPatch.length > 0){
patches[index] = vnPatch;
}
}
function attrsWalk(oldAttrs, newAttrs) {
let attrPath = {};
for (let key in oldAttrs) {
if (oldAttrs[key] !== newAttrs[key]){
attrPath[key] = newAttrs[key]
}
}
for (let key in newAttrs) {
if (!oldAttrs.hasOwnProperty(key)){
attrPath[key] = newAttrs[key]
}
}
return attrPath;
}
function childrenWalk(oldChildren, newChildren) {
oldChildren.map((children, index) => {
vNodeWalk(children,newChildren[index], ++vnIndex)
})
}
export default domDiff;
src/Element.js
class Element {
constructor(type, props, children) {
this.type = type;
this.props = props;
this.children = children;
}
}
export default Element;
src/doPath.js
import {
ATTR,
TEXT,
REPLACE,
REMOVE
} from './patchTypes';
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 rnPath = finalPatches[rnIndex++],
childNodes = rNode.childNodes;
[...childNodes].map((childrenNode)=>{
rNodeWalk(childrenNode)
})
if (rnPath){
patchAction(rNode, rnPath);
}
}
function patchAction(rNode, rnPath) {
rnPath.map(path => {
switch (path.type) {
case ATTR:
for(let key in path.attrs){
const value = path.attrs[key];
if (value){
setAttrs(rNode, key, value)
}else {
rNode.removeAttribute(key)
}
}
break
case TEXT:
rNode.textContent = path.text
break
case REPLACE:
const newNode = (path.newNode instanceof Element)
? render(path.newNode)
: document.createTextNode(path.newNode);
rNode.parentNode.replaceChild(newNode, rNode);
break
case REMOVE:
rNode.parentNode.removeChild(rNode);
break
default:
break
}
})
}
export default doPatch;

原文 blog.csdn.net/weixin_4384…