vBOM比对, 实现patch
const RENDER_TO_DOM = Symbol('render to dom')export class Component { constructor() { this.props = Object.create(null); this.children = []; this._root = null; this._range = null; } setAttribute(name, value) { this.props[name] = value } appendChild(component) { this.children.push(component) } get vdom() { return this.render().vdom; } // get vchildren() { // return this.children.map(child => child.vdom) // } [RENDER_TO_DOM](range) { this._range = range this._vdom = this.vdom // this.render()[RENDER_TO_DOM](range) this._vdom[RENDER_TO_DOM](range) } update () { let isSame = (oldNode, newNode) => { if (oldNode.type != newNode.type) { return false } for (let name in newNode.props) { if (newNode.props[name] != oldNode.props[name]) { return false } } if (Object.keys(oldNode.props).length > Object.keys(newNode.props).length) { return false } if (newNode.type == '#text') { if (newNode.content != oldNode.content) { return false } } return true } let update = (oldNode, newNode) => { // 根结点type和props完全一致, #text需要对比content if (!isSame(oldNode, newNode)) { newNode[RENDER_TO_DOM](oldNode._range) return } newNode._range = oldNode._range let newChildren = newNode.vchildren let oldChildren = oldNode.vchildren if (!newChildren || !newChildren.length) { return } let tailRange = oldChildren[oldChildren.length - 1]._range for (let i = 0; i < newChildren.length; i++) { let newChild = newChildren[i] let oldChild = oldChildren[i] if (i < oldChildren.length) { update(oldChild, newChild) } else { let range = document.createRange() range.setStart(tailRange.endContainer, tailRange.endOffset) range.setEnd(tailRange.endContainer, tailRange.endOffset) newChild[RENDER_TO_DOM](range) tailRange = range } } } let vdom = this.vdom update(this._vdom, vdom) this._vdom = vdom } // rerender() { // // this._range.deleteContents() // // this[RENDER_TO_DOM](this._range) // // 先插入再删除 // let oldRange = this._range; // let range = document.createRange(); // range.setStart(oldRange.startContainer, oldRange.startOffset) // range.setEnd(oldRange.startContainer, oldRange.startOffset) // this[RENDER_TO_DOM](range) // oldRange.setStart(range.endContainer, range.endOffset) // oldRange.deleteContents() // } setState (newState) { // console.log(newState) // 没有state 或者 不是对象, 短路 if (this.state == null || typeof this.state != "object") { this.setState = newState this.rerender() return; } // 深拷贝 let merge = (oldState, newState) => { for (let p in newState) { if (oldState[p] == null || typeof oldState[p] != "object") { oldState[p] = newState[p] } else { merge(oldState[p], newState[p]) } } } merge(this.state, newState) // this.rerender() this.update() } // get root() { // if (!this._root) { // this._root = this.render().root // } // return this._root // }}class ElementWrapper extends Component { constructor(type) { super(type) this.type = type // this.root = document.createElement(type) } // setAttribute(name, value) { // // 过滤事件, 如onClick // if (name.match(/^on([\s\S]+)$/)) { // // console.log(RegExp.$1) // // 绑定事件, Click转click // this.root.addEventListener(RegExp.$1.replace(/^[\s\S]/, c => c.toLowerCase()), value) // } else if (name == 'className'){ // this.root.setAttribute('class', value) // } else { // this.root.setAttribute(name, value) // } // this.root.setAttribute(name, value) // } // appendChild(component) { // // this.root.appendChild(component.root) // let range = document.createRange() // range.setStart(this.root, this.root.childNodes.length) // range.setEnd(this.root, this.root.childNodes.length) // component[RENDER_TO_DOM](range) // } get vdom() { this.vchildren = this.children.map(child => child.vdom); return this } [RENDER_TO_DOM](range){ // range.deleteContents() // range.insertNode(this.root) this._range = range // 实现setAttribute // range.deleteContents() let root = document.createElement(this.type) for (let name in this.props) { let value = this.props[name] if (name.match(/^on([\s\S]+)$/)) { // console.log(RegExp.$1) // 绑定事件, Click转click root.addEventListener(RegExp.$1.replace(/^[\s\S]/, c => c.toLowerCase()), value) } else { if (name == 'className') name = 'class' root.setAttribute(name, value) } } // 实现 appendChild if (!this.vchildren) this.vchildren = this.children.map(child => child.vdom) for (let child of this.vchildren) { let childRange = document.createRange() childRange.setStart(root, root.childNodes.length) childRange.setEnd(root, root.childNodes.length) child[RENDER_TO_DOM](childRange) } // range.insertNode(root) replaceContent(range, root) }}class TextWrapper extends Component{ constructor(content) { super(content) this.type = '#text' this.content = content // this.root = document.createTextNode(content) } get vdom() { return this; } [RENDER_TO_DOM](range){ this._range = range // range.deleteContents() // range.insertNode(this.root) let root = document.createTextNode(this.content) replaceContent(range, root) }}function replaceContent (range, node) { // 先插入后删除 range.insertNode(node) range.setStartAfter(node) range.deleteContents() range.setStartBefore(node) range.setEndAfter(node)}export function createElement(tagName, attributes, ...rest) { let element if (typeof tagName == 'string') { element = new ElementWrapper(tagName) } else { element = new tagName } if (typeof attributes === 'object' && attributes instanceof Object) { for (const key in attributes) { element.setAttribute(key, attributes[key]) } } let insertChildren = (children) => { // console.log(children) for(const child of children) { if (typeof child == 'string') { child = new TextWrapper(child) } if (child === null) { continue; } if ((typeof child == 'object') && (child instanceof Array)) { insertChildren(child) } else { element.appendChild(child) } } } insertChildren(rest) return element}export function render(component, parentElement) { // parentElement.appendChild(component.root) let range = document.createRange() range.setStart(parentElement, 0) range.setEnd(parentElement, parentElement.childNodes.length) component[RENDER_TO_DOM](range)}