vBOM比对, 实现patch

  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. // get vchildren() {
  19. // return this.children.map(child => child.vdom)
  20. // }
  21. [RENDER_TO_DOM](range) {
  22. this._range = range
  23. this._vdom = this.vdom
  24. // this.render()[RENDER_TO_DOM](range)
  25. this._vdom[RENDER_TO_DOM](range)
  26. }
  27. update () {
  28. let isSame = (oldNode, newNode) => {
  29. if (oldNode.type != newNode.type) {
  30. return false
  31. }
  32. for (let name in newNode.props) {
  33. if (newNode.props[name] != oldNode.props[name]) {
  34. return false
  35. }
  36. }
  37. if (Object.keys(oldNode.props).length > Object.keys(newNode.props).length) {
  38. return false
  39. }
  40. if (newNode.type == '#text') {
  41. if (newNode.content != oldNode.content) {
  42. return false
  43. }
  44. }
  45. return true
  46. }
  47. let update = (oldNode, newNode) => {
  48. // 根结点type和props完全一致, #text需要对比content
  49. if (!isSame(oldNode, newNode)) {
  50. newNode[RENDER_TO_DOM](oldNode._range)
  51. return
  52. }
  53. newNode._range = oldNode._range
  54. let newChildren = newNode.vchildren
  55. let oldChildren = oldNode.vchildren
  56. if (!newChildren || !newChildren.length) {
  57. return
  58. }
  59. let tailRange = oldChildren[oldChildren.length - 1]._range
  60. for (let i = 0; i < newChildren.length; i++) {
  61. let newChild = newChildren[i]
  62. let oldChild = oldChildren[i]
  63. if (i < oldChildren.length) {
  64. update(oldChild, newChild)
  65. } else {
  66. let range = document.createRange()
  67. range.setStart(tailRange.endContainer, tailRange.endOffset)
  68. range.setEnd(tailRange.endContainer, tailRange.endOffset)
  69. newChild[RENDER_TO_DOM](range)
  70. tailRange = range
  71. }
  72. }
  73. }
  74. let vdom = this.vdom
  75. update(this._vdom, vdom)
  76. this._vdom = vdom
  77. }
  78. // rerender() {
  79. // // this._range.deleteContents()
  80. // // this[RENDER_TO_DOM](this._range)
  81. // // 先插入再删除
  82. // let oldRange = this._range;
  83. // let range = document.createRange();
  84. // range.setStart(oldRange.startContainer, oldRange.startOffset)
  85. // range.setEnd(oldRange.startContainer, oldRange.startOffset)
  86. // this[RENDER_TO_DOM](range)
  87. // oldRange.setStart(range.endContainer, range.endOffset)
  88. // oldRange.deleteContents()
  89. // }
  90. setState (newState) {
  91. // console.log(newState)
  92. // 没有state 或者 不是对象, 短路
  93. if (this.state == null || typeof this.state != "object") {
  94. this.setState = newState
  95. this.rerender()
  96. return;
  97. }
  98. // 深拷贝
  99. let merge = (oldState, newState) => {
  100. for (let p in newState) {
  101. if (oldState[p] == null || typeof oldState[p] != "object") {
  102. oldState[p] = newState[p]
  103. } else {
  104. merge(oldState[p], newState[p])
  105. }
  106. }
  107. }
  108. merge(this.state, newState)
  109. // this.rerender()
  110. this.update()
  111. }
  112. // get root() {
  113. // if (!this._root) {
  114. // this._root = this.render().root
  115. // }
  116. // return this._root
  117. // }
  118. }
  119. class ElementWrapper extends Component {
  120. constructor(type) {
  121. super(type)
  122. this.type = type
  123. // this.root = document.createElement(type)
  124. }
  125. // setAttribute(name, value) {
  126. // // 过滤事件, 如onClick
  127. // if (name.match(/^on([\s\S]+)$/)) {
  128. // // console.log(RegExp.$1)
  129. // // 绑定事件, Click转click
  130. // this.root.addEventListener(RegExp.$1.replace(/^[\s\S]/, c => c.toLowerCase()), value)
  131. // } else if (name == 'className'){
  132. // this.root.setAttribute('class', value)
  133. // } else {
  134. // this.root.setAttribute(name, value)
  135. // }
  136. // this.root.setAttribute(name, value)
  137. // }
  138. // appendChild(component) {
  139. // // this.root.appendChild(component.root)
  140. // let range = document.createRange()
  141. // range.setStart(this.root, this.root.childNodes.length)
  142. // range.setEnd(this.root, this.root.childNodes.length)
  143. // component[RENDER_TO_DOM](range)
  144. // }
  145. get vdom() {
  146. this.vchildren = this.children.map(child => child.vdom);
  147. return this
  148. }
  149. [RENDER_TO_DOM](range){
  150. // range.deleteContents()
  151. // range.insertNode(this.root)
  152. this._range = range
  153. // 实现setAttribute
  154. // range.deleteContents()
  155. let root = document.createElement(this.type)
  156. for (let name in this.props) {
  157. let value = this.props[name]
  158. if (name.match(/^on([\s\S]+)$/)) {
  159. // console.log(RegExp.$1)
  160. // 绑定事件, Click转click
  161. root.addEventListener(RegExp.$1.replace(/^[\s\S]/, c => c.toLowerCase()), value)
  162. } else {
  163. if (name == 'className') name = 'class'
  164. root.setAttribute(name, value)
  165. }
  166. }
  167. // 实现 appendChild
  168. if (!this.vchildren) this.vchildren = this.children.map(child => child.vdom)
  169. for (let child of this.vchildren) {
  170. let childRange = document.createRange()
  171. childRange.setStart(root, root.childNodes.length)
  172. childRange.setEnd(root, root.childNodes.length)
  173. child[RENDER_TO_DOM](childRange)
  174. }
  175. // range.insertNode(root)
  176. replaceContent(range, root)
  177. }
  178. }
  179. class TextWrapper extends Component{
  180. constructor(content) {
  181. super(content)
  182. this.type = '#text'
  183. this.content = content
  184. // this.root = document.createTextNode(content)
  185. }
  186. get vdom() {
  187. return this;
  188. }
  189. [RENDER_TO_DOM](range){
  190. this._range = range
  191. // range.deleteContents()
  192. // range.insertNode(this.root)
  193. let root = document.createTextNode(this.content)
  194. replaceContent(range, root)
  195. }
  196. }
  197. function replaceContent (range, node) {
  198. // 先插入后删除
  199. range.insertNode(node)
  200. range.setStartAfter(node)
  201. range.deleteContents()
  202. range.setStartBefore(node)
  203. range.setEndAfter(node)
  204. }
  205. export function createElement(tagName, attributes, ...rest) {
  206. let element
  207. if (typeof tagName == 'string') {
  208. element = new ElementWrapper(tagName)
  209. } else {
  210. element = new tagName
  211. }
  212. if (typeof attributes === 'object' && attributes instanceof Object) {
  213. for (const key in attributes) {
  214. element.setAttribute(key, attributes[key])
  215. }
  216. }
  217. let insertChildren = (children) => {
  218. // console.log(children)
  219. for(const child of children) {
  220. if (typeof child == 'string') {
  221. child = new TextWrapper(child)
  222. }
  223. if (child === null) {
  224. continue;
  225. }
  226. if ((typeof child == 'object') && (child instanceof Array)) {
  227. insertChildren(child)
  228. } else {
  229. element.appendChild(child)
  230. }
  231. }
  232. }
  233. insertChildren(rest)
  234. return element
  235. }
  236. export function render(component, parentElement) {
  237. // parentElement.appendChild(component.root)
  238. let range = document.createRange()
  239. range.setStart(parentElement, 0)
  240. range.setEnd(parentElement, parentElement.childNodes.length)
  241. component[RENDER_TO_DOM](range)
  242. }