基于range的DOM绘制
setState既要改变state值, 又要启动一个重新render的动作。多次setState之后会在整个生命周期结束的时候,发起一次重新的render。
自定义组件继承Component的state
获取root(取元素)改为_renderToDom方法来更新DOM。
_renderToDom(range) { // range 位置
this.render()._renderToDom(range)
}
appendChild(component) {
let range = document.createRange()
range.setStart(parentElement, 0)
range.setEnd(parentElement, parentElement.childNodes.length)
component[RENDER_TO_DOM](range)
this.root.appendChild(component.root)
}
function rerender() { // Component rerender 替换 get root
let oldRange = this._range
let range = document.createRange()
range.setStart(oldRange.)
range.setEnd()
}
为了保护私有方法名, symbol代替_renderToDom
const RENDER_TO_DOM = Symbal("render to dom")
[RENDER_TO_DOM](range) {
this.render()[RENDER_TO_DOM](range)
}
同样需要给ElementWrapper 和TextWrapper添加RENDER_TO_DOM方法。 需要项删除range内容, 再进行添加。
[RENDER_TO_DOM](range){
range.deleteContents()
range.insertNode(this.root)
}
修改render
export function render(component, parentElement) {
let range = document.createRange()
range.setStart(parentElement, 0)
range.setEnd(parentElement, parentElement.childNodes.length)
component[RENDER_TO_DOM](range)
}
同样, ElementWrapper修改appendChild
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)
重新绘制和渲染
第一步: Component 类存储range, 添加rerender方法
this._range.deleteContents()
this[RENDER_TO_DOM](this._range)
ElementWrapper, setAttribute添加attribute需要过滤事件,并添加事件监听
if (name.match(/^on[\s\S]+/))
this.root.addEventListener(RegExp.$1.replace(/^[\s\S]/, c => c.toLowerCase()), value)
附1: \s\S 表示匹配全部字符
附2: 首字母转小写
var a = 'Hello'
a.replace(/^[\s\S]/, c => c.toLowerCase()) // hello
a.replace(a.charAt(0), c => c.toLowerCase()) // hello
实现setState(监听state, 调用rerender方法)
setState (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()
}
可以运行TicTacToe
Cannot read property ‘Symbol(render to dom)’ of null at ElementWrapper.appendChild
插入child时, 添加null的判断
if (child === null) {
continue;
}
样式未生效, 需要特殊处理className为class。setAttribute('class', value)
rerender产生一个空的range, 修改rerender方法
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()