Skip to content

Instantly share code, notes, and snippets.

@casperin
Created May 23, 2018 12:30
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save casperin/aefd85269eb992a4a5366fe6ef489474 to your computer and use it in GitHub Desktop.
Save casperin/aefd85269eb992a4a5366fe6ef489474 to your computer and use it in GitHub Desktop.
function h(type, props, children) {
children = children || []
return {
type,
props: props || {},
children: Array.isArray(children) ? children : [children]
}
}
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 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
)
const newLength = newNode.children.length
const oldLength = oldNode.children.length
for (let i = 0; i < newLength || i < oldLength; i++) {
updateElement(
$parent.childNodes[index],
newNode.children[i],
oldNode.children[i],
i
)
}
}
}
function updateProps($target, newProps, oldProps = {}) {
const props = Object.assign({}, newProps, oldProps)
Object.keys(props).forEach(name => {
updateProp($target, name, newProps[name], oldProps[name])
})
}
function updateProp($target, name, newVal, oldVal) {
if (!newVal) {
removeProp($target, name, oldVal)
} else if (!oldVal || newVal !== oldVal) {
setProp($target, name, newVal)
}
}
function changed(node1, node2) {
return typeof node1 !== typeof node2 ||
typeof node1 === "string" && node1 !== node2 ||
node1.type !== node2.type ||
node.props.forceUpdate
}
function setProps($target, props) {
Object.keys(props).forEach(name => {
setProp($target, name, props[name])
})
}
function setProp($target, name, value) {
if (isCustomProp(name)) {
return
} else if (name === "className") {
$target.setAttribute("class", value)
} else if (typeof value === "boolean") {
setBooleanProp($target, name, value)
} else {
$target.setAttribute(name, value)
}
}
function setBooleanProp($target, name, value) {
if (value) {
$target.setAttribute(name, value)
$target[name] = true
} else {
$target[name] = false
}
}
function removeProp($target, name, value) {
if (isCustomProp(name)) {
return
} else if (name === "className") {
$target.removeAttribute("class")
} else if (typeof value === "boolean") {
removeBooleanProp($target, name)
} else {
$target.removeAttribute(name)
}
}
function removeBooleanProp($target, name) {
$target.removeAttribute(name)
$target[name] = false
}
function addEventListeners($target, props) {
Object.keys(props).forEach(name => {
if (isEventProp(name)) {
$target.addEventListener(
name.slice(2).toLowerCase(),
props[name]
)
}
})
}
function isCustomProp(name) {
return isEventProp(name) || name === "forceUpdate"
}
function isEventProp(name) {
return /^on/.test(name)
}
@casperin
Copy link
Author

From "How to write your own Virtual DOM" part 1 and part 2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment