UNPKG

nervjs

Version:

A react-like framework based on virtual-dom

283 lines (265 loc) 7.44 kB
import VPatch from './vpatch' import { isFunction, isString, isObject, getPrototype } from '~' import shallowEqual from '~/shallow-equal' import domIndex from './dom-index' import { isWidget, isHook } from './vnode/types' import createElement from './create-element' function patch (rootNode, patches) { let patchIndices = getPatchIndices(patches) if (patchIndices.length === 0) { return rootNode } let oldTree = patches.old let nodes = domIndex(rootNode, oldTree, patchIndices) patchIndices.forEach(index => { rootNode = applyPatch(rootNode, nodes[index], patches[index]) }) return rootNode } function applyPatch (rootNode, domNode, patch) { if (!domNode) { return rootNode } let newNode if (!Array.isArray(patch)) { patch = [patch] } patch.forEach(patchItem => { newNode = patchSingle(domNode, patchItem) if (domNode === rootNode) { rootNode = newNode } }) return rootNode } function patchSingle (domNode, vpatch) { let type = vpatch.type let oldVNode = vpatch.vnode let patchObj = vpatch.patch switch (type) { case VPatch.VTEXT: return patchVText(domNode, patchObj) case VPatch.VNODE: return patchVNode(domNode, patchObj) case VPatch.INSERT: return patchInsert(domNode, patchObj) case VPatch.WIDGET: return patchWidget(domNode, oldVNode, patchObj) case VPatch.STATELESS: return patchStateLess(domNode, oldVNode, patchObj) case VPatch.PROPS: return patchProps(domNode, patchObj, oldVNode.props, oldVNode.isSvg) case VPatch.ORDER: return patchOrder(domNode, patchObj) case VPatch.REMOVE: return patchRemove(domNode, oldVNode) default: return domNode } } function patchVText (domNode, patch) { if (domNode === null) { return createElement(patch) } if (domNode.nodeType === 3) { if (domNode.textContent) { domNode.textContent = patch.text } else { domNode.nodeValue = patch.text } return domNode } let parentNode = domNode.parentNode let newNode = createElement(patch) if (parentNode) { parentNode.replaceChild(newNode, domNode) } return newNode } function patchVNode (domNode, patch) { if (domNode === null) { return createElement(patch) } let parentNode = domNode.parentNode let newNode = createElement(patch) if (parentNode && newNode !== domNode) { parentNode.replaceChild(newNode, domNode) } return newNode } function patchInsert (parentNode, vnode) { let newNode = createElement(vnode) if (parentNode && newNode) { parentNode.appendChild(newNode) } return parentNode } function patchWidget (domNode, vnode, patch) { const isUpdate = isUpdateWidget(vnode, patch) let newNode if (isUpdate) { newNode = patch.update(vnode, domNode) || domNode } else { newNode = createElement(patch) } const parentNode = domNode.parentNode if (parentNode && domNode !== newNode) { parentNode.replaceChild(newNode, domNode) } if (!isUpdate && vnode) { destroyWidget(domNode, vnode) } return newNode } function patchStateLess (domNode, vnode, patch) { const oldProps = vnode.props const newProps = patch.props if (shallowEqual(oldProps, newProps)) { return domNode } const newNode = createElement(patch) const parentNode = domNode.parentNode if (parentNode && domNode !== newNode) { parentNode.replaceChild(newNode, domNode) } return newNode } function destroyWidget (domNode, widget) { if (isFunction(widget.destroy) && isWidget(widget)) { widget.destroy(domNode) } } function patchProps (domNode, patch, previousProps, isSvg) { for (let propName in patch) { if (propName === 'children') { continue } let propValue = patch[propName] let previousValue = previousProps[propName] if (propValue == null || propValue === false) { if (isHook(previousValue) && previousValue.unhook) { previousValue.unhook(domNode, propName, propValue) continue } else if (propName === 'style') { if (isString(previousValue)) { for (let styleName in previousValue) { domNode.style[styleName] = '' } } else { domNode.removeAttribute(propName) } continue } else if (propName in domNode) { if (isString(previousValue)) { domNode[propName] = '' } else { domNode[propName] = null } domNode.removeAttribute(propName) } else { domNode.removeAttribute(propName) } } else { if (isHook(propValue)) { if (isHook(previousValue) && previousValue.unhook) { previousValue.unhook(domNode, propName, propValue) } if (propValue && propValue.hook) { propValue.hook(domNode, propName, previousValue) } continue } else if (propName === 'style') { if (isString(propValue)) { domNode.setAttribute(propName, propValue) } else { for (let styleName in propValue) { let styleValue = propValue[styleName] if (styleValue != null && styleValue !== false) { try { domNode[propName][styleName] = styleValue } catch (err) {} } } } continue } else if (isObject(propValue)) { if (previousValue && isObject(previousValue) && getPrototype(previousValue) !== getPrototype(propValue)) { if (propName in domNode) { try { domNode[propName] = propValue } catch (err) {} } else { domNode.setAttribute(propName, propValue) } } continue } else if (propName !== 'list' && propName !== 'type' && !isSvg && propName in domNode) { try { domNode[propName] = propValue } catch (err) {} continue } else if (!isFunction(propValue)) { domNode.setAttribute(propName, propValue) } } } return domNode } function patchOrder (domNode, patch) { let removes = patch.removes let inserts = patch.inserts let childNodes = domNode.childNodes let keyMap = {} let node let remove let insert for (let i = 0; i < removes.length; i++) { remove = removes[i] node = childNodes[remove.from] if (remove.key) { keyMap[remove.key] = node } domNode.removeChild(node) } let length = childNodes.length for (let j = 0; j < inserts.length; j++) { insert = inserts[j] node = keyMap[insert.key] domNode.insertBefore(node, insert.to >= length++ ? null : childNodes[insert.to]) } return domNode } function patchRemove (domNode, vnode) { let parentNode = domNode.parentNode if (parentNode) { parentNode.removeChild(domNode) } if (isWidget(vnode)) { destroyWidget(domNode, vnode) } return null } function isUpdateWidget (a, b) { if (isWidget(a) && isWidget(b)) { const keyA = a.props.key const keyB = b.props.key if ('name' in a && 'name' in b) { return a.name === b.name && keyA === keyB } return a.init === b.init && keyA === keyB } return false } function getPatchIndices (patches) { let indices = [] if (patches) { for (let i in patches) { if (i !== 'old' && patches.hasOwnProperty(i)) { indices.push(Number(i)) } } } return indices } export default patch