基于range的DOM绘制

setState既要改变state值, 又要启动一个重新render的动作。多次setState之后会在整个生命周期结束的时候,发起一次重新的render。
自定义组件继承Component的state
获取root(取元素)改为_renderToDom方法来更新DOM。

  1. _renderToDom(range) { // range 位置
  2. this.render()._renderToDom(range)
  3. }
  4. appendChild(component) {
  5. let range = document.createRange()
  6. range.setStart(parentElement, 0)
  7. range.setEnd(parentElement, parentElement.childNodes.length)
  8. component[RENDER_TO_DOM](range)
  9. this.root.appendChild(component.root)
  10. }
  11. function rerender() { // Component rerender 替换 get root
  12. let oldRange = this._range
  13. let range = document.createRange()
  14. range.setStart(oldRange.)
  15. range.setEnd()
  16. }

为了保护私有方法名, symbol代替_renderToDom

  1. const RENDER_TO_DOM = Symbal("render to dom")
  2. [RENDER_TO_DOM](range) {
  3. this.render()[RENDER_TO_DOM](range)
  4. }

同样需要给ElementWrapper 和TextWrapper添加RENDER_TO_DOM方法。 需要项删除range内容, 再进行添加。

  1. [RENDER_TO_DOM](range){
  2. range.deleteContents()
  3. range.insertNode(this.root)
  4. }

修改render

  1. export function render(component, parentElement) {
  2. let range = document.createRange()
  3. range.setStart(parentElement, 0)
  4. range.setEnd(parentElement, parentElement.childNodes.length)
  5. component[RENDER_TO_DOM](range)
  6. }

同样, ElementWrapper修改appendChild

  1. let range = document.createRange()
  2. range.setStart(this.root, this.root.childNodes.length)
  3. range.setEnd(this.root, this.root.childNodes.length)
  4. component[RENDER_TO_DOM](range)

重新绘制和渲染

第一步: Component 类存储range, 添加rerender方法

  1. this._range.deleteContents()
  2. this[RENDER_TO_DOM](this._range)

ElementWrapper, setAttribute添加attribute需要过滤事件,并添加事件监听

  1. if (name.match(/^on[\s\S]+/))
  2. this.root.addEventListener(RegExp.$1.replace(/^[\s\S]/, c => c.toLowerCase()), value)

附1: \s\S 表示匹配全部字符
附2: 首字母转小写

  1. var a = 'Hello'
  2. a.replace(/^[\s\S]/, c => c.toLowerCase()) // hello
  3. a.replace(a.charAt(0), c => c.toLowerCase()) // hello

实现setState(监听state, 调用rerender方法)

  1. setState (newState) {
  2. // 没有state 或者 不是对象, 短路
  3. if (this.state == null || typeof this.state != "object") {
  4. this.setState = newState
  5. this.rerender()
  6. return;
  7. }
  8. // 深拷贝
  9. let merge = (oldState, newState) => {
  10. for (let p in newState) {
  11. if (oldState[p] == null || typeof oldState[p] != "object") {
  12. oldState[p] = newState[p]
  13. } else {
  14. merge(oldState[p], newState[p])
  15. }
  16. }
  17. }
  18. merge(this.state, newState)
  19. this.rerender()
  20. }

可以运行TicTacToe

Cannot read property ‘Symbol(render to dom)’ of null at ElementWrapper.appendChild
插入child时, 添加null的判断

  1. if (child === null) {
  2. continue;
  3. }

样式未生效, 需要特殊处理className为class。setAttribute('class', value)
rerender产生一个空的range, 修改rerender方法

  1. let oldRange = this._range;
  2. let range = document.createRange();
  3. range.setStart(oldRange.startContainer, oldRange.startOffset)
  4. range.setEnd(oldRange.startContainer, oldRange.startOffset)
  5. this[RENDER_TO_DOM](range)
  6. oldRange.setStart(range.endContainer, range.endOffset)
  7. oldRange.deleteContents()