实现虚拟Dom
vdom: type、props、children
ElementWrapper 、 TextWrapper 继承 Component (props、children来自父类)
class ElementWrapper extends Component
class TextWrapper extends Component
Component添加get vdom方法 (由ren的决定, 递归调用)
get vdom() {
return this.render().vdom;
}
ElementWrapper存储type ,添加get vdom方法(type)
super(type)
this.type = type
get vdom() {
return {
type: this.type,
props: this.props,
children: this.children.map(child => child.vdom);
}
// 可简写
// this.children = this.children.map(child => child.vdom)
// return this;
}
TextWrapper存储content, 添加get vdom方法(type、content)
super(content)
this.content = content
get vdom() {
return {
type: '#text',
content: this.content
};
// 可简写, 构造函数中添加this.type='#text'
// return this;
}
虚拟DOM到实体DOM的更新
废弃this.root, setAttribute、appendChild在RENDER_TO_DOM中实现
[RENDER_TO_DOM](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
for (let child of this.children) {
let childRange = document.createRange()
childRange.setStart(root, root.childNodes.length)
childRange.setEnd(root, root.childNodes.length)
child[RENDER_TO_DOM](childRange)
}
range.insertNode(root)
}
vBOM比对, 实现patch
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
}
ElementWrapper 、 TextWrapper render方法中处理, 先插入后删除。replaceContent单独封装
function replaceContent (range, node) {
// 先插入后删除
range.insertNode(node)
range.setStartAfter(node)
range.deleteContents()
range.setStartBefore(node)
range.setEndAfter(node)
}