The code after chapter Updating children

    1. function createVComponent(tag, props) {
    2. return {
    3. tag: tag,
    4. props: props,
    5. dom: null,
    6. }
    7. }
    8. function createVElement(tag, config, children = null) {
    9. const { className, style } = config;
    10. return {
    11. tag: tag,
    12. style: style,
    13. props: {
    14. children: children,
    15. },
    16. className: className,
    17. dom: null,
    18. }
    19. }
    20. function createElement(tag, config, children) {
    21. // If the tag is a function. We have a component!
    22. // we will see later why.
    23. if (typeof tag === 'function') {
    24. //of course we could do some checks here if the props are
    25. //valid or not.
    26. const vNode = createVComponent(tag, config);
    27. return vNode;
    28. }
    29. //Add children on our props object, just as in React. Where
    30. //we can acces it using this.props.children;
    31. const vNode = createVElement(tag, config, children);
    32. return vNode;
    33. }
    34. function mount(input, parentDOMNode) {
    35. if (typeof input === 'string' || typeof input === 'number') {
    36. //we have a vText
    37. return mountVText(input, parentDOMNode);
    38. }
    39. else if (typeof input.tag === 'function') {
    40. //we have a component
    41. return mountVComponent(input, parentDOMNode);
    42. }
    43. // for brevity make an else if statement. An
    44. // else would suffice.
    45. else if (typeof input.tag === 'string') {
    46. //we have a vElement
    47. return mountVElement(input, parentDOMNode)
    48. }
    49. }
    50. function mountVComponent(vComponent, parentDOMNode) {
    51. const { tag, props } = vComponent;
    52. const Component = tag;
    53. const instance = new Component(props);
    54. const nextRenderedElement = instance.render();
    55. //create a reference of our currenElement
    56. //on our component instance.
    57. instance._currentElement = nextRenderedElement;
    58. //create a reference to the passed
    59. //DOMNode. We might need it.
    60. instance._parentNode = parentDOMNode;
    61. const dom = mount(nextRenderedElement, parentDOMNode);
    62. //save the instance for later!
    63. vComponent._instance = instance;
    64. vComponent.dom = dom;
    65. parentDOMNode.appendChild(dom);
    66. }
    67. function mountVText(vText, parentDOMNode) {
    68. // Oeeh we received a vText with it's associated parentDOMNode.
    69. // we can set it's textContent to the vText value.
    70. // https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent
    71. parentDOMNode.textContent = vText;
    72. }
    73. function mountVElement(vElement, parentDOMNode) {
    74. const { className, tag, props, style } = vElement;
    75. const domNode = document.createElement(tag);
    76. vElement.dom = domNode;
    77. if (props.children) {
    78. if (!Array.isArray(props.children)) {
    79. mount(props.children, domNode)
    80. } else {
    81. props.children.forEach(child => mount(child, domNode));
    82. }
    83. }
    84. if (className !== undefined) {
    85. domNode.className = className;
    86. }
    87. if (style !== undefined) {
    88. Object.keys(style).forEach(sKey => domNode.style[sKey] = style[sKey]);
    89. }
    90. parentDOMNode.appendChild(domNode);
    91. return domNode;
    92. }
    93. function updateVElement(prevElement, nextElement) {
    94. const dom = prevElement.dom;
    95. nextElement.dom = dom;
    96. if (nextElement.props.children) {
    97. updateChildren(prevElement.props.children, nextElement.props.children, dom);
    98. }
    99. if (prevElement.style !== nextElement.style) {
    100. Object.keys(nextElement.style).forEach((s) => dom.style[s] = nextElement.style[s])
    101. }
    102. }
    103. function updateVText(prevText, nextText, parentDOM) {
    104. if (prevText !== nextText) {
    105. parentDOM.firstChild.nodeValue = nextText;
    106. }
    107. }
    108. function updateChildren(prevChildren, nextChildren, parentDOMNode) {
    109. if (!Array.isArray(nextChildren)) {
    110. nextChildren = [nextChildren];
    111. }
    112. if (!Array.isArray(prevChildren)) {
    113. prevChildren = [prevChildren];
    114. }
    115. for (let i = 0; i < nextChildren.length; i++) {
    116. //We're skipping a lot of cases here. Like what if
    117. //the children array have different lenghts? Then we
    118. //should replace smartly etc. :)
    119. const nextChild = nextChildren[i];
    120. const prevChild = prevChildren[i];
    121. //Check if the vNode is a vText
    122. if (typeof nextChild === 'string' && typeof prevChild === 'string') {
    123. //We're taking a shortcut here. It would cleaner to
    124. //let the `update` function handle it, but we would to add some extra
    125. //logic because we don't have a `tag` property.
    126. updateVText(prevChild, nextChild, parentDOMNode);
    127. continue;
    128. } else {
    129. update(prevChild, nextChild);
    130. }
    131. }
    132. }
    133. function updateVComponent(prevComponent, nextComponent) {
    134. //get the instance. This is Component. It also
    135. //holds the props and _currentElement;
    136. const { _instance } = prevComponent;
    137. const { _currentElement } = _instance;
    138. //get the new and old props!
    139. const prevProps = prevComponent.props;
    140. const nextProps = nextComponent.props;
    141. //Time for the big swap!
    142. nextComponent.dom = prevComponent.dom;
    143. nextComponent._instance = _instance;
    144. nextComponent._instance.props = nextProps;
    145. if (_instance.shouldComponentUpdate()) {
    146. const prevRenderedElement = _currentElement;
    147. const nextRenderedElement = _instance.render();
    148. //finaly save the nextRenderedElement for the next iteration!
    149. nextComponent._instance._currentElement = nextRenderedElement;
    150. //call update
    151. update(prevRenderedElement, nextRenderedElement, _instance._parentNode);
    152. }
    153. }
    154. class Component {
    155. constructor(props) {
    156. this.props = props || {};
    157. this.state = {};
    158. this._currentElement = null;
    159. this._pendingState = null;
    160. this._parentNode = null;
    161. }
    162. //add this in existing code
    163. shouldComponentUpdate() {
    164. return true;
    165. }
    166. updateComponent() {
    167. const prevState = this.state;
    168. const prevElement = this._currentElement;
    169. if (this._pendingState !== prevState) {
    170. this.state = this._pendingState;
    171. }
    172. this._pendingState = null;
    173. const nextElement = this.render();
    174. this._currentElement = nextElement;
    175. update(prevElement, nextElement, this._parentNode);
    176. }
    177. setState(partialNewState) {
    178. // I know this looks weired. Why don't pass state to updateComponent()
    179. // function, I agree.
    180. // We're just getting a little familiair with putting data on instances.
    181. // seomthing that React uses heavily :)
    182. this._pendingState = Object.assign({}, this.state, partialNewState);
    183. this.updateComponent();
    184. }
    185. //will be overridden
    186. render() {}
    187. }
    188. function update(prevElement, nextElement) {
    189. //Implement the first assumption!
    190. if (prevElement.tag === nextElement.tag) {
    191. //Inspect the type. If the `tag` is a string
    192. //we have a `vElement`. (we should actually
    193. //made some helper functions for this ;))
    194. if (typeof prevElement.tag === 'string') {
    195. updateVElement(prevElement, nextElement);
    196. } else if (typeof prevElement.tag === 'function') {
    197. updateVComponent(prevElement, nextElement);
    198. }
    199. } else {
    200. //Oh oh two elements of different types. We don't want to
    201. //look further in the tree! We need to replace it!
    202. }
    203. }
    204. //get native DOM, For now let's use the body;
    205. const root = document.body;
    206. //create vElement
    207. class NestedApp extends Component {
    208. constructor(props) {
    209. super(props);
    210. }
    211. shouldComponentUpdate() {
    212. return this.props.counter % 2;
    213. }
    214. render() {
    215. return createElement('h1', { style: { color: '#'+Math.floor(Math.random()*16777215).toString(16) } }, `The count from parent is: ${this.props.counter}`)
    216. }
    217. }
    218. class App extends Component {
    219. constructor() {
    220. super();
    221. this.state = {
    222. counter: 1
    223. }
    224. setInterval(() => {
    225. this.setState({ counter: this.state.counter + 1 })
    226. }, 500);
    227. }
    228. render() {
    229. const { counter } = this.state;
    230. //background color stolen from: https://www.paulirish.com/2009/random-hex-color-code-snippets/
    231. return createElement('div', { style: { height: `${10 * counter}px`, background: '#'+Math.floor(Math.random()*16777215).toString(16) } }, [
    232. `the counter is ${counter}`,
    233. createElement('h1', { style: { color: '#'+Math.floor(Math.random()*16777215).toString(16) } }, `${'BOOM! '.repeat(counter)}`),
    234. createElement(NestedApp, { counter: counter})
    235. ]);
    236. }
    237. }
    238. mount(createElement(App, { message: 'Hello there!' }), root);