UNPKG

wft-utils

Version:

The commonly used tool functions in daily development

154 lines (136 loc) 4.38 kB
/** * Vue渲染系统实现 * 1. h函数,用于返回一个VNode对象; * 2. mount函数,用于将VNode挂载到DOM上; * 3. patch函数,用于对两个VNode进行对比,决定如何处理新的VNode; */ /** * 虚拟dom本身就是个javascript对象 * @param {String} tag 标签名称 * @param {Object} props 属性配置 * @param {String | Array} children 子元素 * @returns */ const h = (tag, props, children) => ({ tag, props, children }) /** * 将虚拟dom转为真实dom并挂载到指定容器 * @param {Object} vnode 虚拟dom * @param {Element} container 容器 */ const mount = (vnode, container) => { const el = vnode.el = document.createElement(vnode.tag) if (vnode.props) { for (let key in vnode.props) { const value = vnode.props[key] if (key.startsWith('on')) { el.addEventListener(key.slice(2).toLowerCase(), value) } else { el.setAttribute(key, value) } } } if (vnode.children) { if (getObjType(vnode.children) === 'string') { el.textContent = vnode.children } else if (getObjType(vnode.children) === 'array') { vnode.children.forEach(item => { mount(item, el) }) } } container.appendChild(el) } /** * 类似diff对比两个vnode * @param {Object} n1 旧vnode * @param {Object} n2 新vnode */ const patch = (n1, n2) => { if (n1.tag !== n2.tag) { const n1parentEl = n1.el.parentElement n1parentEl.removeChild(n1.el) mount(n2, n1parentEl) } else { const el = n2.el = n1.el // 处理props const oldProps = n1.props || {} const newProps = n2.props || {} for (let key in newProps) { const oldValue = oldProps[key] const newValue = newProps[key] if (newValue !== oldValue) { if (key.startsWith('on')) { el.addEventListener(key.slice(2).toLowerCase(), newValue) } else { el.setAttribute(key, newValue) } } } for (let key in oldProps) { if (key.startsWith('on')) { const oldValue = oldProps[key] el.removeEventListener(key.slice(2).toLowerCase(), oldValue) } if (!(key in newProps)) { el.removeAttribute(key) } } const oldChildren = n1.children || [] const newChildren = n2.children || [] if (getObjType(newChildren) === 'string') { if (getObjType(oldChildren) === 'string') { if (newChildren !== oldChildren) { el.textContent = newChildren } } else { el.innerHTML = newChildren } } else if (getObjType(newChildren) === 'array') { if (getObjType(oldChildren) === 'string') { el.innerHTML = '' newChildren.forEach(item => { mount(item, el) }) } else if (getObjType(oldChildren) === 'array') { // oldChildren -> [vnode1, vnode2, vnode3] // newChildren -> [vnode1, vnode5, vnode6, vnode7, vnode8] // 前面有相同节点的元素进行patch操作 const commonLength = Math.min(newChildren.length, oldChildren.length) for (let i = 0; i < commonLength; i++) { patch(oldChildren[i], newChildren[i]) } // 2. newChildren.length > oldChildren.length 多余的做挂载操作 if (newChildren.length > oldChildren.length) { newChildren.slice(commonLength).forEach(item => { mount(item, el) }) } // 3. newChildren.length < oldChildren.length 多余的做移除操作 if (newChildren.length < oldChildren.length) { oldChildren.slice(commonLength).forEach(item => { el.removeChild(item.el) }) } } } } } function getObjType (obj) { let toString = Object.prototype.toString let map = { '[object Boolean]': 'boolean', '[object Number]': 'number', '[object String]': 'string', '[object Function]': 'function', '[object Array]': 'array', '[object Date]': 'date', '[object RegExp]': 'regExp', '[object Undefined]': 'undefined', '[object Null]': 'null', '[object Object]': 'object' } if (obj instanceof Element) { return 'element' } return map[toString.call(obj)] }