实现虚拟Dom

vdom: type、props、children

  • ElementWrapper 、 TextWrapper 继承 Component (props、children来自父类)

    1. class ElementWrapper extends Component
    2. class TextWrapper extends Component
  • Component添加get vdom方法 (由ren的决定, 递归调用)

    1. get vdom() {
    2. return this.render().vdom;
    3. }
  • ElementWrapper存储type ,添加get vdom方法(type)

    1. super(type)
    2. this.type = type
    3. get vdom() {
    4. return {
    5. type: this.type,
    6. props: this.props,
    7. children: this.children.map(child => child.vdom);
    8. }
    9. // 可简写
    10. // this.children = this.children.map(child => child.vdom)
    11. // return this;
    12. }
  • TextWrapper存储content, 添加get vdom方法(type、content)

    1. super(content)
    2. this.content = content
    3. get vdom() {
    4. return {
    5. type: '#text',
    6. content: this.content
    7. };
    8. // 可简写, 构造函数中添加this.type='#text'
    9. // return this;
    10. }

    image.png

    虚拟DOM到实体DOM的更新

    废弃this.root, setAttribute、appendChild在RENDER_TO_DOM中实现

    1. [RENDER_TO_DOM](range){
    2. // 实现setAttribute
    3. range.deleteContents()
    4. let root = document.createElement(this.type)
    5. for (let name in this.props) {
    6. let value = this.props[name]
    7. if (name.match(/^on([\s\S]+)$/)) {
    8. // console.log(RegExp.$1)
    9. // 绑定事件, Click转click
    10. root.addEventListener(RegExp.$1.replace(/^[\s\S]/, c => c.toLowerCase()), value)
    11. } else {
    12. if (name == 'className') name = 'class'
    13. root.setAttribute(name, value)
    14. }
    15. }
    16. // 实现 appendChild
    17. for (let child of this.children) {
    18. let childRange = document.createRange()
    19. childRange.setStart(root, root.childNodes.length)
    20. childRange.setEnd(root, root.childNodes.length)
    21. child[RENDER_TO_DOM](childRange)
    22. }
    23. range.insertNode(root)
    24. }

    vBOM比对, 实现patch

    1. update () {
    2. let isSame = (oldNode, newNode) => {
    3. if (oldNode.type != newNode.type) {
    4. return false
    5. }
    6. for (let name in newNode.props) {
    7. if (newNode.props[name] != oldNode.props[name]) {
    8. return false
    9. }
    10. }
    11. if (Object.keys(oldNode.props).length > Object.keys(newNode.props).length) {
    12. return false
    13. }
    14. if (newNode.type == '#text') {
    15. if (newNode.content != oldNode.content) {
    16. return false
    17. }
    18. }
    19. return true
    20. }
    21. let update = (oldNode, newNode) => {
    22. // 根结点type和props完全一致, #text需要对比content
    23. if (!isSame(oldNode, newNode)) {
    24. newNode[RENDER_TO_DOM](oldNode._range)
    25. return
    26. }
    27. newNode._range = oldNode._range
    28. let newChildren = newNode.vchildren
    29. let oldChildren = oldNode.vchildren
    30. if (!newChildren || !newChildren.length) {
    31. return
    32. }
    33. let tailRange = oldChildren[oldChildren.length - 1]._range
    34. for (let i = 0; i < newChildren.length; i++) {
    35. let newChild = newChildren[i]
    36. let oldChild = oldChildren[i]
    37. if (i < oldChildren.length) {
    38. update(oldChild, newChild)
    39. } else {
    40. let range = document.createRange()
    41. range.setStart(tailRange.endContainer, tailRange.endOffset)
    42. range.setEnd(tailRange.endContainer, tailRange.endOffset)
    43. newChild[RENDER_TO_DOM](range)
    44. tailRange = range
    45. }
    46. }
    47. }
    48. let vdom = this.vdom
    49. update(this._vdom, vdom)
    50. this._vdom = vdom
    51. }

    ElementWrapper 、 TextWrapper render方法中处理, 先插入后删除。replaceContent单独封装

    1. function replaceContent (range, node) {
    2. // 先插入后删除
    3. range.insertNode(node)
    4. range.setStartAfter(node)
    5. range.deleteContents()
    6. range.setStartBefore(node)
    7. range.setEndAfter(node)
    8. }