var Hello = ({ dateTime }) => { return ['h1', {}, [`It is: ${dateTime}`]]; }; var App = () => { return ['div', { 'className': 'header' }, [Hello({ dateTime: new Date() }), ['input', { 'type': 'submit', 'disabled': 'disabled' }, []] ] ]; } var d = ['div', { 'className': 'header' }, [['h1', {}, ['Hello']], ['input', { 'type': 'submit', 'disabled': 'disabled' }, []] ] ]; d = ['div', { 'className': 'header' }, [['h1', {}, ['Hello, how have you been?']], ['input', { 'type': 'submit', 'style': 'color: red;' }, []] ] ]; var e = createElement(d); render(e, $0) var d = ['div', { 'className': 'header' }, [['h1', {}, ['Hello']], ['input', { 'type': 'submit', 'disabled': 'disabled' }, []] ] ]; function createElement(node) { // an array: not text, number, or other primitive if (typeof node === 'object') { const [ tag, props, children ] = node; return { type: tag, props: { ...props, children: children.map(createElement) } }; } // primitives like text or number return { type: 'TEXT', props: { nodeValue: node, children: [] } }; } let renderTrees = {}; function render(element, container) { const tree = render_internal(element, container, renderTrees[container]); // render complete, store the updated tree renderTrees[container] = tree; } function render_internal(element, container, prevElement) { let domElement, children; if (!element && prevElement) { removeDOMElement(prevElement); return; } else if (element && !prevElement) { domElement = createDOMElement(element); } else if (element.type === prevElement.type) { domElement = prevElement.domElement; } else { removeDOMElement(prevElement); domElement = createDOMElement(element); } setDOMProps(element, domElement, prevElement); children = renderChildren(element, domElement, prevElement); if (!prevElement || domElement !== prevElement.domElement) { container.appendChild(domElement); } return { domElement: domElement, parent: container, type: element.type, props: { ...element.props, children: children } }; } function removeDOMElement(prevElement) { prevElement.parent.removeChild(prevElement.domElement); } function createDOMElement(element) { return element.type === 'TEXT' ? document.createTextNode(element.props.nodeValue) : document.createElement(element.type); } function setDOMProps(element, domElement, prevElement) { if (prevElement) { Object.keys(prevElement.props) .filter((key) => key !== 'children') .forEach((key) => { domElement[key] = ''; }); } Object.keys(element.props) .filter((key) => key !== 'children') .forEach((key) => { domElement[key] = element.props[key]; }); } function renderChildren(element, domElement, prevElement = { props: { children: [] }}) { const elementLen = element.props.children.length; const prevElementLen = prevElement.props.children.length; // remove now unused elements for (let i = elementLen; i < prevElementLen - elementLen; i++) { removeDOMElement(element.props.children[i]); } // render existing and new elements return element.props.children.map((child, i) => { const prevChild = i < prevElementLen ? prevElement.props.children[i] : undefined; return render_internal(child, domElement, prevChild); }); } function defaultAreEqual(oldProps, newProps) { if (typeof oldProps !== 'object' || typeof newProps !== 'object') { return false; } const oldKeys = Object.keys(oldProps); const newKeys = Object.keys(newProps); if (oldKeys.length !== newKeys.length) { return false; } for (let i = 0; i < oldKeys.length; i++) { // Object.is - the comparison to note if (!oldProps.hasOwnProperty(newKeys[i]) || !Object.is(oldProps[newKeys[i]], newProps[newKeys[i]])) { return false; } } return true; } function memo(component, areEqual = defaultAreEqual) { let oldProps = {}; let lastResult = false; return (props) => { if (lastResult && areEqual(oldProps, props) { return lastResult; } else { lastResult = component(props); oldProps = Object.assign({}, props); // shallow copy return lastResult; } }; } var Hello = memo(({ dateTime }) => { return ['h1', {}, [`It is: ${dateTime}`]]; });