虚拟DOM到实体DOM的更新

    toy-react.js

    1. const RENDER_TO_DOM = Symbol('render to dom')
    2. export class Component {
    3. constructor() {
    4. this.props = Object.create(null);
    5. this.children = [];
    6. this._root = null;
    7. this._range = null;
    8. }
    9. setAttribute(name, value) {
    10. this.props[name] = value
    11. }
    12. appendChild(component) {
    13. this.children.push(component)
    14. }
    15. get vdom() {
    16. return this.render().vdom;
    17. }
    18. [RENDER_TO_DOM](range) {
    19. this._range = range
    20. this.render()[RENDER_TO_DOM](range)
    21. }
    22. rerender() {
    23. // this._range.deleteContents()
    24. // this[RENDER_TO_DOM](this._range)
    25. // 先插入再删除
    26. let oldRange = this._range;
    27. let range = document.createRange();
    28. range.setStart(oldRange.startContainer, oldRange.startOffset)
    29. range.setEnd(oldRange.startContainer, oldRange.startOffset)
    30. this[RENDER_TO_DOM](range)
    31. oldRange.setStart(range.endContainer, range.endOffset)
    32. oldRange.deleteContents()
    33. }
    34. setState (newState) {
    35. // console.log(newState)
    36. // 没有state 或者 不是对象, 短路
    37. if (this.state == null || typeof this.state != "object") {
    38. this.setState = newState
    39. this.rerender()
    40. return;
    41. }
    42. // 深拷贝
    43. let merge = (oldState, newState) => {
    44. for (let p in newState) {
    45. if (oldState[p] == null || typeof oldState[p] != "object") {
    46. oldState[p] = newState[p]
    47. } else {
    48. merge(oldState[p], newState[p])
    49. }
    50. }
    51. }
    52. merge(this.state, newState)
    53. this.rerender()
    54. }
    55. // get root() {
    56. // if (!this._root) {
    57. // this._root = this.render().root
    58. // }
    59. // return this._root
    60. // }
    61. }
    62. class ElementWrapper extends Component {
    63. constructor(type) {
    64. super(type)
    65. this.type = type
    66. // this.root = document.createElement(type)
    67. }
    68. // setAttribute(name, value) {
    69. // // 过滤事件, 如onClick
    70. // if (name.match(/^on([\s\S]+)$/)) {
    71. // // console.log(RegExp.$1)
    72. // // 绑定事件, Click转click
    73. // this.root.addEventListener(RegExp.$1.replace(/^[\s\S]/, c => c.toLowerCase()), value)
    74. // } else if (name == 'className'){
    75. // this.root.setAttribute('class', value)
    76. // } else {
    77. // this.root.setAttribute(name, value)
    78. // }
    79. // this.root.setAttribute(name, value)
    80. // }
    81. // appendChild(component) {
    82. // // this.root.appendChild(component.root)
    83. // let range = document.createRange()
    84. // range.setStart(this.root, this.root.childNodes.length)
    85. // range.setEnd(this.root, this.root.childNodes.length)
    86. // component[RENDER_TO_DOM](range)
    87. // }
    88. get vdom() {
    89. this.children = this.children.map(child => child.vdom);
    90. return this
    91. }
    92. [RENDER_TO_DOM](range){
    93. // range.deleteContents()
    94. // range.insertNode(this.root)
    95. // 实现setAttribute
    96. range.deleteContents()
    97. let root = document.createElement(this.type)
    98. for (let name in this.props) {
    99. let value = this.props[name]
    100. if (name.match(/^on([\s\S]+)$/)) {
    101. // console.log(RegExp.$1)
    102. // 绑定事件, Click转click
    103. root.addEventListener(RegExp.$1.replace(/^[\s\S]/, c => c.toLowerCase()), value)
    104. } else {
    105. if (name == 'className') name = 'class'
    106. root.setAttribute(name, value)
    107. }
    108. }
    109. // 实现 appendChild
    110. for (let child of this.children) {
    111. let childRange = document.createRange()
    112. childRange.setStart(root, root.childNodes.length)
    113. childRange.setEnd(root, root.childNodes.length)
    114. child[RENDER_TO_DOM](childRange)
    115. }
    116. range.insertNode(root)
    117. }
    118. }
    119. class TextWrapper extends Component{
    120. constructor(content) {
    121. super(content)
    122. this.type = '#text'
    123. this.content = content
    124. this.root = document.createTextNode(content)
    125. }
    126. get vdom() {
    127. return this;
    128. }
    129. [RENDER_TO_DOM](range){
    130. range.deleteContents()
    131. range.insertNode(this.root)
    132. }
    133. }
    134. export function createElement(tagName, attributes, ...rest) {
    135. let element
    136. if (typeof tagName == 'string') {
    137. element = new ElementWrapper(tagName)
    138. } else {
    139. element = new tagName
    140. }
    141. if (typeof attributes === 'object' && attributes instanceof Object) {
    142. for (const key in attributes) {
    143. element.setAttribute(key, attributes[key])
    144. }
    145. }
    146. let insertChildren = (children) => {
    147. // console.log(children)
    148. for(const child of children) {
    149. if (typeof child == 'string') {
    150. child = new TextWrapper(child)
    151. }
    152. if (child === null) {
    153. continue;
    154. }
    155. if ((typeof child == 'object') && (child instanceof Array)) {
    156. insertChildren(child)
    157. } else {
    158. element.appendChild(child)
    159. }
    160. }
    161. }
    162. insertChildren(rest)
    163. return element
    164. }
    165. export function render(component, parentElement) {
    166. // parentElement.appendChild(component.root)
    167. let range = document.createRange()
    168. range.setStart(parentElement, 0)
    169. range.setEnd(parentElement, parentElement.childNodes.length)
    170. component[RENDER_TO_DOM](range)
    171. }