基于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 rootlet oldRange = this._rangelet 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()) // helloa.replace(a.charAt(0), c => c.toLowerCase()) // hello
实现setState(监听state, 调用rerender方法)
setState (newState) {// 没有state 或者 不是对象, 短路if (this.state == null || typeof this.state != "object") {this.setState = newStatethis.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()
