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

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. name: 'Jack'
  9. }
  10. }
  11. render () {
  12. return <div>
  13. <button onClick={() => {this.setState({count: this.state.count+1})} }>add</button>
  14. <p>count:{this.state.count.toString()}</p>
  15. <p>name:{this.state.name}</p>
  16. {this.children}
  17. </div>
  18. }
  19. }
  20. window.a = <Hello id="app" class="head">
  21. <span>1</span>
  22. <span>2</span>
  23. <span><h1>3</h1><h1>4</h1></span>
  24. hello
  25. </Hello>
  26. 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. setState (newState) {
  24. // console.log(newState)
  25. // 没有state 或者 不是对象, 短路
  26. if (this.state == null || typeof this.state != "object") {
  27. this.setState = newState
  28. this.rerender()
  29. return;
  30. }
  31. // 深拷贝
  32. let merge = (oldState, newState) => {
  33. for (let p in newState) {
  34. if (oldState[p] == null || typeof oldState[p] != "object") {
  35. oldState[p] = newState[p]
  36. } else {
  37. merge(oldState[p], newState[p])
  38. }
  39. }
  40. }
  41. merge(this.state, newState)
  42. this.rerender()
  43. }
  44. // get root() {
  45. // if (!this._root) {
  46. // this._root = this.render().root
  47. // }
  48. // return this._root
  49. // }
  50. }
  51. class ElementWrapper {
  52. constructor(type) {
  53. this.root = document.createElement(type)
  54. }
  55. setAttribute(name, value) {
  56. // 过滤事件, 如onClick
  57. if (name.match(/^on([\s\S]+)$/)) {
  58. // console.log(RegExp.$1)
  59. // 绑定事件, Click转click
  60. this.root.addEventListener(RegExp.$1.replace(/^[\s\S]/, c => c.toLowerCase()), value)
  61. } else {
  62. this.root.setAttribute(name, value)
  63. }
  64. this.root.setAttribute(name, value)
  65. }
  66. appendChild(component) {
  67. // this.root.appendChild(component.root)
  68. let range = document.createRange()
  69. range.setStart(this.root, this.root.childNodes.length)
  70. range.setEnd(this.root, this.root.childNodes.length)
  71. component[RENDER_TO_DOM](range)
  72. }
  73. [RENDER_TO_DOM](range){
  74. range.deleteContents()
  75. range.insertNode(this.root)
  76. }
  77. }
  78. class TextWrapper {
  79. constructor(content) {
  80. this.root = document.createTextNode(content)
  81. }
  82. [RENDER_TO_DOM](range){
  83. range.deleteContents()
  84. range.insertNode(this.root)
  85. }
  86. }
  87. export function createElement(tagName, attributes, ...rest) {
  88. let element
  89. if (typeof tagName == 'string') {
  90. element = new ElementWrapper(tagName)
  91. } else {
  92. element = new tagName
  93. }
  94. if (typeof attributes === 'object' && attributes instanceof Object) {
  95. for (const key in attributes) {
  96. element.setAttribute(key, attributes[key])
  97. }
  98. }
  99. let insertChildren = (children) => {
  100. // console.log(children)
  101. for(const child of children) {
  102. if (typeof child == 'string') {
  103. child = new TextWrapper(child)
  104. }
  105. if ((typeof child == 'object') && (child instanceof Array)) {
  106. insertChildren(child)
  107. } else {
  108. element.appendChild(child)
  109. }
  110. }
  111. }
  112. insertChildren(rest)
  113. return element
  114. }
  115. export function render(component, parentElement) {
  116. // parentElement.appendChild(component.root)
  117. let range = document.createRange()
  118. range.setStart(parentElement, 0)
  119. range.setEnd(parentElement, parentElement.childNodes.length)
  120. component[RENDER_TO_DOM](range)
  121. }