Created
April 20, 2017 20:38
-
-
Save pomber/221d47de3afc1f84fc98c0b4ce9c1dbb to your computer and use it in GitHub Desktop.
Two big functions
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
export function createElement(type, config, ...args) { | |
const props = Object.assign({}, config); | |
if (args.length) { | |
props.children = [].concat(...args); | |
} | |
return { type, props }; | |
} | |
export function render(element, container) { | |
const instance = new DomComponent(element); | |
const dom = instance.mount(); | |
container.appendChild(dom); | |
} | |
class DomComponent { | |
constructor(element) { | |
this.currentElement = element; | |
this.dom = null; | |
this.childInstances = null; | |
} | |
mount() { | |
const { type, props } = this.currentElement; | |
this.dom = document.createElement(type); | |
// Set attributes | |
Object.keys(props).filter(isAttribute).forEach(name => { | |
const value = props[name]; | |
if (value != null && value !== false) { | |
this.dom[name] = value; | |
} | |
}); | |
// Set events | |
Object.keys(props).filter(isEvent).forEach(name => { | |
const eventType = name.toLowerCase().substring(2); | |
this.dom.addEventListener(eventType, props[name]); | |
}); | |
// Set children | |
const childElements = props.children || []; | |
this.childInstances = childElements.map( | |
childElement => new DomComponent(childElement) | |
); | |
this.childInstances | |
.map(childInstance => childInstance.mount()) | |
.forEach(childDom => this.dom.appendChild(childDom)); | |
return this.dom; | |
} | |
update(nextElement) { | |
const prevProps = this.currentElement.props; | |
const nextProps = nextElement.props; | |
// Remove attributes | |
Object.keys(prevProps).filter(isAttribute).forEach(name => { | |
this.dom[name] = null; | |
}); | |
// Set attributes | |
Object.keys(nextProps).filter(isAttribute).forEach(name => { | |
const value = nextProps[name]; | |
if (value != null && value !== false) { | |
this.dom[name] = value; | |
} | |
}); | |
// Remove events | |
Object.keys(prevProps).filter(isEvent).forEach(name => { | |
const eventType = name.toLowerCase().substring(2); | |
this.dom.removeEventListener(eventType, prevProps[name]); | |
}); | |
// Set events | |
Object.keys(nextProps).filter(isEvent).forEach(name => { | |
const eventType = name.toLowerCase().substring(2); | |
this.dom.addEventListener(eventType, nextProps[name]); | |
}); | |
// Update children | |
const prevChildElements = prevProps.children || []; | |
const nextChildElements = nextProps.children || []; | |
const prevChildInstances = this.childInstances; | |
const nextChildInstances = []; | |
const length = Math.max(prevChildElements.length, nextChildElements.length); | |
for (let i = 0; i < length; i++) { | |
const prevChildElement = prevChildElements[i]; | |
const nextChildElement = nextChildElements[i]; | |
if (prevChildElement === undefined) { | |
// Add new child | |
const childInstance = new DomComponent(nextChildElement); | |
nextChildInstances.push(childInstance); | |
const childDom = childInstance.mount(); | |
this.dom.appendChild(childDom); | |
} else if (nextChildElement === undefined) { | |
// Remove old child | |
const childInstance = prevChildInstances[i]; | |
const childDom = childInstance.getDom(); | |
this.dom.removeChild(childDom); | |
} else if (prevChildElement.type === nextChildElement.type) { | |
// Update child | |
const childInstance = prevChildInstances[i]; | |
nextChildInstances.push(childInstance); | |
childInstance.update(nextChildElement); | |
} else { | |
// Replace old with new | |
const nextChildInstance = new DomComponent(nextChildElement); | |
nextChildInstances.push(nextChildInstance); | |
const nextChildDom = nextChildInstance.mount(); | |
const prevChildInstance = prevChildInstances[i]; | |
const prevChildDom = prevChildInstance.getDom(); | |
this.dom.replaceChild(nextChildDom, prevChildDom); | |
} | |
} | |
this.currentElement = nextElement; | |
this.childInstances = nextChildInstances; | |
} | |
getDom() { | |
return this.dom; | |
} | |
} | |
const isEvent = name => name.startsWith("on"); | |
const isAttribute = name => !isEvent(name) && name != "children"; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment