重新绘制和渲染

main.js

  1. import {render, Component, createElement} from './toy-react.js'
  2. // window.a = <div id="app" class="head"></div>
  3. class Hello extends Component {
  4. constructor () {
  5. super()
  6. this.state = {
  7. count: 0
  8. }
  9. }
  10. render () {
  11. return <div>
  12. <button onClick={() => {this.state.count++; this.rerender()} }>add</button>
  13. <p>{this.state.count.toString()}</p>
  14. {this.children}
  15. </div>
  16. }
  17. }
  18. window.a = <Hello id="app" class="head">
  19. <span>1</span>
  20. <span>2</span>
  21. <span><h1>3</h1><h1>4</h1></span>
  22. hello
  23. </Hello>
  24. render(window.a, document.body)

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. [RENDER_TO_DOM](range) {
  16. this._range = range
  17. this.render()[RENDER_TO_DOM](range)
  18. }
  19. rerender() {
  20. this._range.deleteContents()
  21. this[RENDER_TO_DOM](this._range)
  22. }
  23. // get root() {
  24. // if (!this._root) {
  25. // this._root = this.render().root
  26. // }
  27. // return this._root
  28. // }
  29. }
  30. class ElementWrapper {
  31. constructor(type) {
  32. this.root = document.createElement(type)
  33. }
  34. setAttribute(name, value) {
  35. // 过滤事件, 如onClick
  36. if (name.match(/^on([\s\S]+)$/)) {
  37. console.log(RegExp.$1)
  38. // 绑定事件, Click转click
  39. this.root.addEventListener(RegExp.$1.replace(/^[\s\S]/, c => c.toLowerCase()), value)
  40. } else {
  41. this.root.setAttribute(name, value)
  42. }
  43. this.root.setAttribute(name, value)
  44. }
  45. appendChild(component) {
  46. // this.root.appendChild(component.root)
  47. let range = document.createRange()
  48. range.setStart(this.root, this.root.childNodes.length)
  49. range.setEnd(this.root, this.root.childNodes.length)
  50. component[RENDER_TO_DOM](range)
  51. }
  52. [RENDER_TO_DOM](range){
  53. range.deleteContents()
  54. range.insertNode(this.root)
  55. }
  56. }
  57. class TextWrapper {
  58. constructor(content) {
  59. this.root = document.createTextNode(content)
  60. }
  61. [RENDER_TO_DOM](range){
  62. range.deleteContents()
  63. range.insertNode(this.root)
  64. }
  65. }
  66. export function createElement(tagName, attributes, ...rest) {
  67. let element
  68. if (typeof tagName == 'string') {
  69. element = new ElementWrapper(tagName)
  70. } else {
  71. element = new tagName
  72. }
  73. if (typeof attributes === 'object' && attributes instanceof Object) {
  74. for (const key in attributes) {
  75. element.setAttribute(key, attributes[key])
  76. }
  77. }
  78. let insertChildren = (children) => {
  79. console.log(children)
  80. for(const child of children) {
  81. if (typeof child == 'string') {
  82. child = new TextWrapper(child)
  83. }
  84. if ((typeof child == 'object') && (child instanceof Array)) {
  85. insertChildren(child)
  86. } else {
  87. element.appendChild(child)
  88. }
  89. }
  90. }
  91. insertChildren(rest)
  92. return element
  93. }
  94. export function render(component, parentElement) {
  95. // parentElement.appendChild(component.root)
  96. let range = document.createRange()
  97. range.setStart(parentElement, 0)
  98. range.setEnd(parentElement, parentElement.childNodes.length)
  99. component[RENDER_TO_DOM](range)
  100. }