Created
April 25, 2020 15:20
-
-
Save marlonramirez/29d69a46bbb0c799bb78359f843da495 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function setBooleanProp($target, name, value) { | |
if (value) { | |
$target.setAttribute(name, value); | |
$target[name] = true; | |
} else { | |
$target[name] = false; | |
} | |
} | |
function removeBooleanProp($target, name) { | |
$target.removeAttribute(name); | |
$target[name] = false; | |
} | |
function isEventProp(name) { | |
return /^on/.test(name); | |
} | |
function extractEventName(name) { | |
return name.slice(2).toLowerCase(); | |
} | |
function isCustomProp(name) { | |
return isEventProp(name) || name === 'forceUpdate'; | |
} | |
function setProp($target, name, value) { | |
if (isCustomProp(name)) return; | |
if (name === 'className') { | |
$target.setAttribute('class', value); | |
} else if (typeof value === 'boolean') { | |
setBooleanProp($target, name, value); | |
} else { | |
$target.setAttribute(name, value); | |
} | |
} | |
function removeProp($target, name, value) { | |
if (isCustomProp(name)) return; | |
if (name === 'className') { | |
$target.removeAttribute('class'); | |
} else if (typeof value === 'boolean') { | |
removeBooleanProp($target, name); | |
} else { | |
$target.removeAttribute(name); | |
} | |
} | |
function setProps($target, props) { | |
for (const name in props) { | |
setProp($target, name, props[name]); | |
} | |
} | |
function updateProp($target, name, newVal, oldVal) { | |
if (!newVal) { | |
removeProp($target, name, oldVal); | |
} else if (!oldVal || newVal !== oldVal) { | |
setProp($target, name, newVal); | |
} | |
} | |
function updateProps($target, newProps, oldProps = {}) { | |
const props = Object.assign({}, newProps, oldProps); | |
for (const name in props) { | |
updateProp($target, name, newProps[name], oldProps[name]); | |
} | |
} | |
function addEventListeners($target, props) { | |
for (const name in props) { | |
if (isEventProp(name)) { | |
$target.addEventListener( | |
extractEventName(name), | |
props[name] | |
); | |
} | |
} | |
} | |
function createElement(node) { | |
if (typeof node === 'string') { | |
return document.createTextNode(node); | |
} | |
const $el = document.createElement(node.type); | |
setProps($el, node.props); | |
addEventListeners($el, node.props); | |
node.children | |
.map(createElement) | |
.forEach($el.appendChild.bind($el)); | |
return $el; | |
} | |
function changed(node1, node2) { | |
return typeof node1 !== typeof node2 || | |
typeof node1 === 'string' && node1 !== node2 || | |
node1.type !== node2.type || | |
node1.props && node1.props.forceUpdate; | |
} | |
function updateElement($parent, newNode, oldNode, index = 0) { | |
if (!oldNode) { | |
$parent.appendChild( | |
createElement(newNode) | |
); | |
} else if (!newNode) { | |
$parent.removeChild( | |
$parent.childNodes[index] | |
); | |
} else if (changed(newNode, oldNode)) { | |
$parent.replaceChild( | |
createElement(newNode), | |
$parent.childNodes[index] | |
); | |
} else if (newNode.type) { | |
updateProps( | |
$parent.childNodes[index], | |
newNode.props, | |
oldNode.props | |
); | |
updateNodes($parent.childNodes[index], newNode.children, oldNode.children); | |
} | |
} | |
function updateNodes($parent, newNodes, oldNodes = []) { | |
const newLength = newNodes.length; | |
const oldLength = oldNodes.length; | |
for (let i = 0; i < newLength || i < oldLength; i++) { | |
updateElement( | |
$parent, | |
newNodes[i], | |
oldNodes[i], | |
i | |
); | |
} | |
} | |
export default class VirtualDom { | |
constructor(element, node) { | |
this.element = element; | |
this.patch(node); | |
} | |
static create(type, props = {}, ...children) { | |
return {type, props, children}; | |
} | |
patch(node) { | |
if (Array.isArray(node)) { | |
updateNodes(this.element, node, this.node); | |
} else { | |
updateElement(this.element, node, this.node); | |
} | |
this.node = node; | |
} | |
} | |
function log(e) { | |
console.log(e.target.value); | |
} | |
const f = VirtualDOM.create('ul', {style: 'list-style: none'}, | |
VirtualDOM.create('li', {className: 'item', onClick: () => alert('hi!')}, 'item 1'), | |
VirtualDOM.create('li', {className: 'item'}, | |
VirtualDOM.create('input', {type: 'checkbox', checked: true}), | |
VirtualDOM.create('input', {type: 'text', onInput: log}) | |
), | |
VirtualDOM.create('li', {}, 'text') | |
); | |
const dom = new VirtualDOM(document.getElementById('root'), f); | |
let i = 0; | |
document.getElementById('reload').addEventListener('click', () => { | |
const g = VirtualDOM.create('ul', {style: 'list-style: none'}, | |
VirtualDOM.create('li', {className: 'item item2', onClick: () => alert('hola!')}, 'item 1'), | |
VirtualDOM.create('li', {style: 'background: red'}, | |
VirtualDOM.create('input', {type: 'checkbox', checked: false}), | |
VirtualDOM.create('input', {type: 'text', onInput: log}) | |
), | |
VirtualDOM.create('li', {}, 'text' + i) | |
); | |
dom.patch(g); | |
i++; | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment