├── README.md ├── build-and-learn ├── 1.text-element.html ├── 2.tag-element.html ├── 3.composite-element.html ├── 4.handling-updates.html └── 5.todo-list-example.html ├── demo └── todo-list-complete.html └── react-lite.js /README.md: -------------------------------------------------------------------------------- 1 | # React Lite 2 | 3 | A simple light-weight prototype of React :) 4 | 5 | Built for learning purposes and to get the essence of what happens under the hood. 6 | 7 | Size: 2kb gzipped. 8 | 9 | ### Reference 10 | 11 | [React - Basic Theoretical Concepts](https://github.com/reactjs/react-basic) 12 | 13 | [React - Codebase overview](https://facebook.github.io/react/contributing/codebase-overview.html) 14 | 15 | [React - Implementation details](https://facebook.github.io/react/contributing/implementation-notes.html) 16 | 17 | [React Source parsing](http://zhenhua-lee.github.io/react/react.html) 18 | 19 | [React in 200 bytes](https://medium.com/@gab_montes/react-in-200-bytes-28156e714165#.a82m7wii6) 20 | 21 | [React source code analysis series - 1. Life cycle management](https://zhuanlan.zhihu.com/p/20312691) 22 | 23 | [React source code analysis series - 2. Decrypting setState](https://zhuanlan.zhihu.com/p/20328570) 24 | 25 | [React source code analysis series - 3. React diff](https://zhuanlan.zhihu.com/p/20346379) 26 | 27 | [React Source code analysis series - 4. React Transition](https://zhuanlan.zhihu.com/p/20419592) 28 | 29 | [Understanding React and reimplementing it from scratch Part 1: Views](https://gcanti.github.io/2014/10/29/understanding-react-and-reimplementing-it-from-scratch-part-1.html) 30 | 31 | [Understanding React and reimplementing it from scratch Part 2: Controllers](https://gcanti.github.io/2014/11/24/understanding-react-and-reimplementing-it-from-scratch-part-2.html) 32 | 33 | [React Transaction - What happens after setState](https://undefinedblog.com/what-happened-after-set-state/) 34 | 35 | [In depth understanding of the React BatchUpdate mechanism](https://undefinedblog.com/understand-react-batch-update/) 36 | 37 | [Dive into React codebase: Transactions](http://reactkungfu.com/2015/12/dive-into-react-codebase-transactions/) 38 | 39 | [Dive into React codebase: Handling state changes](http://reactkungfu.com/2016/03/dive-into-react-codebase-handling-state-changes/) 40 | 41 | [How to implement a Virtual DOM Algorithm](https://github.com/livoras/blog/issues/13) 42 | 43 | [React Source Analysis - Part I (first rendering principle)](http://purplebamboo.github.io/2015/09/15/reactjs_source_analyze_part_one/) 44 | 45 | [React Source Analysis - Part II (update mechanism principle)](http://purplebamboo.github.io/2015/09/15/reactjs_source_analyze_part_two/) 46 | 47 | [Tiny React Renderer](https://github.com/iamdustan/tiny-react-renderer) 48 | 49 | [Reconciliation - Docs](https://facebook.github.io/react/docs/reconciliation.html) 50 | 51 | [React Fiber Architecture](https://github.com/acdlite/react-fiber-architecture) 52 | 53 | [In-depth diffing](http://buildwithreact.com/article/in-depth-diffing) 54 | 55 | [Performance Engineering with React](http://benchling.engineering/performance-engineering-with-react/) 56 | 57 | [React performance - Vjeux](http://blog.vjeux.com/2013/javascript/react-performance.html) 58 | 59 | [React architecture - Vjeux](http://blog.vjeux.com/2014/javascript/react-architecture-oscon.html) 60 | 61 | [Preact - Lightweight 3kb version of React](https://github.com/developit/preact) 62 | 63 | [React Lite - an implementation of React that optimizes for small script size](https://github.com/Lucifier129/react-lite) 64 | 65 | ### Videos 66 | 67 | [Building React from Scratch - Paul O Shannessy](https://www.youtube.com/watch?v=_MAD4Oly9yg) 68 | 69 | [React.js Internals with Nick Niemeir](https://www.youtube.com/watch?v=FAgSdSikSCc) 70 | 71 | [Standing On The Shoulders Of Giants Or How To Read The Internals Of React js](https://www.youtube.com/watch?v=IbXZL0e1haI) 72 | 73 | [ReactJS: Under the hood](https://www.youtube.com/watch?v=xsKYAa1ZXpQ) 74 | 75 | [React events in depth](https://www.youtube.com/watch?v=dRo_egw7tBc) 76 | 77 | [React reconciliation - Jim Sproch](https://www.youtube.com/watch?v=EZV2rwnGgZA) 78 | 79 | [React Fiber: What's next for React](https://www.youtube.com/watch?v=aV1271hd9ew) 80 | 81 | [A Cartoon Intro to Fiber - Lin Clark](https://www.youtube.com/watch?v=ZCuYPiUIONs) 82 | -------------------------------------------------------------------------------- /build-and-learn/1.text-element.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 24 | 25 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /build-and-learn/2.tag-element.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /build-and-learn/3.composite-element.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 276 | 277 | 278 | 279 | -------------------------------------------------------------------------------- /build-and-learn/4.handling-updates.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 665 | 666 | 667 | 668 | -------------------------------------------------------------------------------- /build-and-learn/5.todo-list-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 33 | 34 | 35 | 36 | 37 | 38 |
39 | 40 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /demo/todo-list-complete.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |
24 | 25 | 220 | 221 | 222 | 223 | -------------------------------------------------------------------------------- /react-lite.js: -------------------------------------------------------------------------------- 1 | /** */ 2 | /** REACT DOM TEXT COMPONENT */ 3 | /** */ 4 | 5 | // Component class used to represent text render/update/delete 6 | function ReactDOMTextComponent(text) { 7 | // Saves text passed through 8 | this._currentElement = '' + text; 9 | // Root NodeID is used to identify the current DOMTextComponent 10 | this._rootNodeID = null; 11 | } 12 | 13 | // Handles Mount - Here's where the component render generates it's corresponding DOM structure. 14 | ReactDOMTextComponent.prototype.mountComponent = function (rootID) { 15 | this._rootNodeID = rootID; 16 | return '' + this._currentElement + ''; 17 | }; 18 | 19 | ReactDOMTextComponent.prototype.receiveComponent = function (nextText) { 20 | var nextStringText = '' + nextText; 21 | // Comparison with the previously saved string 22 | if (nextStringText !== this._currentElement) { 23 | this._currentElement = nextStringText; 24 | // Replace entire node 25 | document.querySelector('[data-reactid="' + this._rootNodeID + '"]').innerHTML = this._currentElement; 26 | } 27 | }; 28 | 29 | /** */ 30 | /** REACT DOM COMPONENT */ 31 | /** */ 32 | 33 | // Component class used to represent HTML tag DOM elements' render/update/delete 34 | function ReactDOMComponent(element) { 35 | // Save the current element object's reference 36 | this._currentElement = element; 37 | this._rootNodeID = null; 38 | } 39 | 40 | // Mounting the component to generate the DOM element structure. 41 | ReactDOMComponent.prototype.mountComponent = function (rootID) { 42 | // Copy root id to the instance. 43 | this._rootNodeID = rootID; 44 | var props = this._currentElement.props; 45 | var tagOpen = '<' + this._currentElement.type; 46 | var tagClose = ''; 47 | 48 | // Add react-id identification to the node as a data attribute. 49 | // The data-reactid attribute is a custom attribute used so that React can uniquely identify its components within the DOM. 50 | // This is important because React applications can be rendered at the server as well as the client. 51 | // 52 | // More: http://stackoverflow.com/a/33967810/1672655 53 | // React 15 uses document.createElement instead, so client rendered markup won't include these attributes anymore. 54 | tagOpen += ' data-reactid="' + this._rootNodeID + '"'; 55 | 56 | // Put together prop values into the tag 57 | for (var propKey in props) { 58 | // Handle EventListeners passed in props - identified via regex. 59 | if (/^on[A-Za-z]/.test(propKey)) { 60 | var eventType = propKey.replace('on', ''); 61 | // Add the event listener as a custom event on the current DOM node - identfied via the rootNodeId values. 62 | $(document).on(eventType + '.' + this._rootNodeID, '[data-reactid="' + this._rootNodeID + '"]', props[propKey]); 63 | } 64 | 65 | // Ignore children and event listener props. -- Rest of the props are assigned as attributes to the DOM element. 66 | if (props[propKey] && propKey !== 'children' && !/^on[A-Za-z]/.test(propKey)) { 67 | tagOpen += ' ' + propKey + '="' + props[propKey] + '"'; 68 | } 69 | } 70 | 71 | // Recursively get the child nodes' render() content as well. 72 | var content = ''; 73 | var children = props.children || []; 74 | 75 | // Book-keeping: Save component instances of all children. 76 | var childrenInstances = []; 77 | var that = this; 78 | $.each(children, function (key, child) { 79 | var childComponentInstance = instantiateReactComponent(child); 80 | childComponentInstance._mountIndex = key; 81 | 82 | childrenInstances.push(childComponentInstance); 83 | // Root id of children is calculated by concatenating it with the rootId of the parent. 84 | var curRootId = that._rootNodeID + '.' + key; 85 | // 86 | var childMarkup = childComponentInstance.mountComponent(curRootId); 87 | // Concatenate together the markup obtained 88 | content += ' ' + childMarkup; 89 | }); 90 | 91 | // The children instances are saved to the current DOM component instance 92 | this._renderedChildren = childrenInstances; 93 | 94 | // Return the final markup of the DOM component 95 | return tagOpen + '>' + content + tagClose; 96 | }; 97 | 98 | // Tag elements mainly have 2 types of updates 99 | // 1. Attribute property updates - including events 100 | // 2. Updates to child nodes (more complex) 101 | // 102 | // In order to do updates efficiently, we need to do it in 2 steps: 103 | // Diff - Take a new child node tree and compare it with the previous old child nodes of the tree, to find out the differences between them. 104 | // Patch - After all differences are found out, do a one-time update. 105 | // Tip: All Reads + All writes is far more performant that RWRWRW - because browser does a bunch of recals/pains on every RW cycle. 106 | ReactDOMComponent.prototype.receiveComponent = function (nextElement) { 107 | var lastProps = this._currentElement.props; 108 | var nextProps = nextElement.props; 109 | 110 | this._currentElement = nextElement; 111 | // Update DOM properties 112 | this._updateDOMProperties(lastProps, nextProps); 113 | // Update DOM children 114 | this._updateDOMChildren(nextElement.props.children); 115 | }; 116 | 117 | ReactDOMComponent.prototype._updateDOMProperties = function (lastProps, nextProps) { 118 | var propKey; 119 | 120 | // If an old attribute/event is not part of the new props - Remove attribute/event listener. 121 | for (propKey in lastProps) { 122 | // Continue loop if old prop is still passed in as a part of the new props. 123 | if (nextProps.hasOwnProperty(propKey) || !lastProps.hasOwnProperty(propKey)) { 124 | continue; 125 | } 126 | // Remove event listeners if they are not part of the new props anymore. 127 | if (/^on[A-Za-z]/.test(propKey)) { 128 | var eventType = propKey.replace('on', ''); 129 | // Remove event listener for the current node. 130 | $(document).off(eventType, '[data-reactid="' + this._rootNodeID + '"]', lastProps[propKey]); 131 | continue; 132 | } 133 | 134 | // Remove old attribute/events that are not part of the new props 135 | $('[data-reactid="' + this._rootNodeID + '"]').removeAttr(propKey) 136 | } 137 | 138 | // For new attributes, write it to DOM node 139 | for (propKey in nextProps) { 140 | // Check if event listener using regex. 141 | if (/^on[A-Za-z]/.test(propKey)) { 142 | var eventType = propKey.replace('on', ''); 143 | // Remove previously attached eventType specific event listeners for the node (generic event) 144 | lastProps[propKey] && $(document).undelegate('[data-reactid="' + this._rootNodeID + '"]', eventType, lastProps[propKey]); 145 | // Add an event listener for the current node with the _rootNodeID namespace (custom Event) 146 | $(document).on(eventType + '.' + this._rootNodeID, '[data-reactid="' + this._rootNodeID + '"]', nextProps[propKey]); 147 | continue; 148 | } 149 | 150 | if (propKey === 'children') { 151 | continue; 152 | } 153 | 154 | // Add a new property, or update old property 155 | $('[data-reactid="' + this._rootNodeID + '"]').prop(propKey, nextProps[propKey]) 156 | } 157 | }; 158 | 159 | // Keep track of the current update depth. 160 | var updateDepth = 0; 161 | // All the diffs are pushed to this global array. 162 | var diffQueue = []; 163 | 164 | ReactDOMComponent.prototype._updateDOMChildren = function (nextChildrenElements) { 165 | updateDepth++; 166 | // _ diff the tree recursively to find the difference - this diff is add to the update queue diffQueue. 167 | this._diff(diffQueue, nextChildrenElements); 168 | updateDepth--; 169 | if (updateDepth == 0) { 170 | // Call this finally to make a batched update/patch to the DOM 171 | this._patch(diffQueue); 172 | diffQueue = []; 173 | } 174 | }; 175 | 176 | // NOTE: 177 | // 1. The _diff internal recursive calls will call the child nodes' receiveComponent. 178 | // 2. When a child node is an ordinary browser node, it will go to _updateDOMChildren() in this step. 179 | // 3. We use `updateDepth` to record the recursive process, only to come back recursively to updateDepth 0. 180 | // 4. When depth is back to 0 - this means the diff has been analyzed of the trees have been analyzed 181 | // 5. We can now start making patch updates based on the differences in the queue. 182 | 183 | // Types of DOM updates 184 | var UPDATE_TYPES = { 185 | MOVE_EXISTING: 1, 186 | REMOVE_NODE: 2, 187 | INSERT_MARKUP: 3 188 | }; 189 | 190 | // Book keeping - This is an object of element keys mapped to children 191 | // 192 | // Normal children is an array, this method converts it to a map. 193 | // key element is the key 194 | // - If the key is not passed or if the node is a text element - we use the array index converted to base 36 instead. 195 | // 196 | // This key is later used during diffing to determine if the nodes in both the trees are derived form the same component. 197 | function flattenChildren(componentChildren) { 198 | var child; 199 | var name; 200 | var childrenMap = {}; 201 | for (var i = 0; i < componentChildren.length; i++) { 202 | child = componentChildren[i]; 203 | name = child && child._currentElement && child._currentElement.key ? child._currentElement.key : i.toString(36); 204 | // Note: toString(36) - converts to base 36 (hexatrigesimal) to allow large ints 205 | childrenMap[name] = child; 206 | } 207 | return childrenMap; 208 | } 209 | 210 | // Child node elements mainly used to generate a set of components 211 | // There is a logic to check: 212 | // 1. If has been updated in th past, if so - it will continue to use the previous componentInstance & call the corresponding receiveComponent(). 213 | // 2. If it is a new node, it will create a new componentInstance, 214 | function generateComponentChildren(prevChildren, nextChildrenElements) { 215 | var nextChildren = {}; 216 | nextChildrenElements = nextChildrenElements || []; 217 | $.each(nextChildrenElements, function (index, element) { 218 | var name = element.key ? element.key : index; 219 | var prevChild = prevChildren && prevChildren[name]; 220 | var prevElement = prevChild && prevChild._currentElement; 221 | var nextElement = element; 222 | 223 | // Call _shouldUpdateReactComponent() to determine whether the update is required 224 | if (_shouldUpdateReactComponent(prevElement, nextElement)) { 225 | // To update call receiveComponent() 226 | prevChild.receiveComponent(nextElement); 227 | // Save the updated child instance to `nextChildren` 228 | nextChildren[name] = prevChild; 229 | } else { 230 | // If not an update - create a new instance 231 | var nextChildInstance = instantiateReactComponent(nextElement, null); 232 | // Save the new child instance to `nextChildren` 233 | nextChildren[name] = nextChildInstance; 234 | } 235 | }); 236 | 237 | return nextChildren; 238 | } 239 | 240 | // _ diff() recursively to find the difference - then add it to the update queue diffQueue. 241 | ReactDOMComponent.prototype._diff = function (diffQueue, nextChildrenElements) { 242 | var self = this; 243 | // _renderedChildren - originally an array, we make it into an object Map 244 | var prevChildren = flattenChildren(self._renderedChildren); 245 | // Set of component objects generate a new child node, where attention will reuse the old component objects 246 | var nextChildren = generateComponentChildren(prevChildren, nextChildrenElements); 247 | self._renderedChildren = []; 248 | // Update rendered children array with the the latest component instance 249 | $.each(nextChildren, function (key, instance) { 250 | self._renderedChildren.push(instance); 251 | }); 252 | 253 | // The index of the last visited node 254 | var lastIndex = 0; 255 | // The index of the next node to be visited 256 | var nextIndex = 0; 257 | // Placeholder name variable used in loops. 258 | var name; 259 | 260 | // Compare the differences between two sets and add nodes to the queue 261 | for (name in nextChildren) { 262 | if (!nextChildren.hasOwnProperty(name)) { 263 | continue; 264 | } 265 | var prevChild = prevChildren && prevChildren[name]; 266 | var nextChild = nextChildren[name]; 267 | // If the same reference -- same words/description is used for a component - we need to do a MOVE operation 268 | if (prevChild === nextChild) { 269 | // Add properties to diff queue 270 | // TYPE:MOVE_EXISTING 271 | prevChild._mountIndex < lastIndex && diffQueue.push({ 272 | parentId: self._rootNodeID, 273 | parentNode: $('[data-reactid="' + self._rootNodeID + '"]'), 274 | type: UPDATE_TYPES.MOVE_EXISTING, 275 | fromIndex: prevChild._mountIndex, 276 | toIndex: nextIndex 277 | }); 278 | lastIndex = Math.max(prevChild._mountIndex, lastIndex); 279 | } else { // If not identical -- meaning we need to newly add or remove node. 280 | // But if there is still the old, that is a different element, but the same component. We need to delete the corresponding old element. 281 | if (prevChild) { 282 | // Add properties to diff queue, 283 | // TYPE:REMOVE_NODE 284 | diffQueue.push({ 285 | parentId: self._rootNodeID, 286 | parentNode: $('[data-reactid="' + self._rootNodeID + '"]'), 287 | type: UPDATE_TYPES.REMOVE_NODE, 288 | fromIndex: prevChild._mountIndex, 289 | toIndex: null 290 | }); 291 | 292 | // If you have previously rendered it - remember to remove all previous namespaced event listeners. 293 | if (prevChild._rootNodeID) { 294 | $(document).off('[data-reactid="' + prevChild._rootNodeID + '"]'); 295 | } 296 | lastIndex = Math.max(prevChild._mountIndex, lastIndex); 297 | } 298 | 299 | // Add properties to diff queue, 300 | // TYPE:INSERT_MARKUP 301 | diffQueue.push({ 302 | parentId: self._rootNodeID, 303 | parentNode: $('[data-reactid="' + self._rootNodeID + '"]'), 304 | type: UPDATE_TYPES.INSERT_MARKUP, 305 | fromIndex: null, 306 | toIndex: nextIndex, 307 | markup: nextChild.mountComponent(self._rootNodeID + '.' + name) // Extra key in object to indicate the content of new DOM node. 308 | }); 309 | } 310 | // Update the mount index. 311 | nextChild._mountIndex = nextIndex; 312 | nextIndex++; 313 | } 314 | 315 | // For old nodes with no new nodes - remove them all. 316 | for (name in prevChildren) { 317 | if (prevChildren.hasOwnProperty(name) && !(nextChildren && nextChildren.hasOwnProperty(name))) { 318 | // Add properties to diff queue, 319 | // TYPE:REMOVE_NODE 320 | diffQueue.push({ 321 | parentId: self._rootNodeID, 322 | parentNode: $('[data-reactid="' + self._rootNodeID + '"]'), 323 | type: UPDATE_TYPES.REMOVE_NODE, 324 | fromIndex: prevChildren[name]._mountIndex, 325 | toIndex: null 326 | }); 327 | if (prevChildren[name]._rootNodeID) { 328 | $(document).off('[data-reactid="' + prevChildren._rootNodeID + '"]'); 329 | } 330 | // Side Note: 331 | // If a DOM element is removed and is reference-free (no references pointing to it) 332 | // then the element is picked up by the garbage collector 333 | // and any event handlers/listeners associated with it are alss removed. 334 | } 335 | } 336 | }; 337 | 338 | // For inserting child nodes into a specific 339 | function insertChildAt(parentNode, childNode, index) { 340 | var beforeChild = parentNode.children().get(index); 341 | beforeChild ? childNode.insertBefore(beforeChild) : childNode.appendTo(parentNode); 342 | 343 | // ANOTHER WAY TO DO THIS: 344 | // var beforeChild = index >= parentNode.childNodes.length ? null : parentNode.childNodes.item(index); 345 | // parentNode.insertBefore(childNode, beforeChild); 346 | } 347 | 348 | ReactDOMComponent.prototype._patch = function (updates) { 349 | var update; 350 | var initialChildren = {}; 351 | var deleteChildren = []; 352 | for (var i = 0; i < updates.length; i++) { 353 | update = updates[i]; 354 | if (update.type === UPDATE_TYPES.MOVE_EXISTING || update.type === UPDATE_TYPES.REMOVE_NODE) { 355 | var updatedIndex = update.fromIndex; 356 | var updatedChild = $(update.parentNode.children().get(updatedIndex)); 357 | var parentId = update.parentId; 358 | 359 | // Need to update all nodes - Saved for later use. 360 | initialChildren[parentId] = initialChildren[parentId] || []; 361 | // Use parentID as a simple namespace 362 | initialChildren[parentId][updatedIndex] = updatedChild; 363 | 364 | // For the node to be moved: 365 | // 1. First need to delete the node. 366 | // 2. Re-insert it into the correct position. 367 | deleteChildren.push(updatedChild) 368 | } 369 | } 370 | // Do Step 1 of Move - Delete 371 | $.each(deleteChildren, function (index, child) { 372 | $(child).remove(); 373 | }); 374 | 375 | // Iterate once again, this time Step 2 - Insert it back to the new position. 376 | for (var k = 0; k < updates.length; k++) { 377 | update = updates[k]; 378 | switch (update.type) { 379 | case UPDATE_TYPES.INSERT_MARKUP: 380 | insertChildAt(update.parentNode, $(update.markup), update.toIndex); 381 | break; 382 | case UPDATE_TYPES.MOVE_EXISTING: 383 | insertChildAt(update.parentNode, initialChildren[update.parentID][update.fromIndex], update.toIndex); 384 | break; 385 | case UPDATE_TYPES.REMOVE_NODE: 386 | // This has already been handled in the previous for-loop. 387 | break; 388 | } 389 | } 390 | }; 391 | 392 | /** */ 393 | /** REACT COMPOSITE COMPONENT */ 394 | /** */ 395 | 396 | function ReactCompositeComponent(element) { 397 | // Save the current element object's reference. 398 | this._currentElement = element; 399 | // To store the node reference. 400 | this._rootNodeID = null; 401 | // To store the corresponding instance of ReactClass later on. 402 | this._instance = null; 403 | } 404 | 405 | // Mounting the component to generate the DOM element structure. 406 | // Should return the current custom elements' render contents. 407 | ReactCompositeComponent.prototype.mountComponent = function (rootID) { 408 | this._rootNodeID = rootID; 409 | // The current element's prop values. 410 | var publicProps = this._currentElement.props; 411 | // The current element's ReactClass (Component class). 412 | var ReactClass = this._currentElement.type; 413 | // Initialize the public class 414 | var inst = new ReactClass(publicProps); 415 | this._instance = inst; 416 | // Maintain reference to the current component - for future updates. 417 | inst._reactInternalInstance = this; 418 | 419 | if (inst.componentWillMount) { 420 | inst.componentWillMount(); 421 | // There is a lot more additional logic here in the complete library - but we'll keep it simple here. 422 | } 423 | // Call the ReactClass instance's render method - this returns an element or a text node 424 | var renderedElement = this._instance.render(); 425 | // Get component class instance of the rendered element. 426 | var renderedComponentInstance = instantiateReactComponent(renderedElement); 427 | this._renderedComponent = renderedComponentInstance; // Save component instance for later use. 428 | 429 | // Get string content after rendering, the current _rootNodeID passed to render the node. 430 | var renderedMarkup = renderedComponentInstance.mountComponent(this._rootNodeID); 431 | 432 | // Add an event listener for 'mountReady' event - which is triggered when React.render() completes. 433 | $(document).on('mountReady', function () { 434 | // call inst.componentDidMount 435 | inst.componentDidMount && inst.componentDidMount(); 436 | }); 437 | 438 | return renderedMarkup; 439 | }; 440 | 441 | // Update 442 | ReactCompositeComponent.prototype.receiveComponent = function (nextElement, newState) { 443 | // If nextElement is passed - it becomes the current element (we pass nextElement below while recursing) 444 | this._currentElement = nextElement || this._currentElement; 445 | 446 | var inst = this._instance; 447 | // Merge state and assign props. 448 | var nextState = $.extend(inst.state, newState); 449 | var nextProps = this._currentElement.props; 450 | 451 | // Update state & props in instance 452 | inst.state = nextState; 453 | inst.props = nextProps; 454 | 455 | // If there is shouldComponentUpdate inst and returns false. Do not update the component - just return. 456 | if (inst.shouldComponentUpdate && (inst.shouldComponentUpdate(nextProps, nextState) === false)) { 457 | return; 458 | } 459 | 460 | // If there is componentWillUpdate - Call it to indicate start of update. 461 | if (inst.componentWillUpdate) { 462 | inst.componentWillUpdate(nextProps, nextState); 463 | } 464 | 465 | var prevComponentInstance = this._renderedComponent; 466 | var prevRenderedElement = prevComponentInstance._currentElement; 467 | // Execute render for the current instance. 468 | var nextRenderedElement = this._instance.render(); 469 | 470 | // Condition checks if the component needs to be updated or render a completely different element (replace). 471 | // Note that the _shouldUpdateReactComponent is a global method and different from the component's shouldComponentUpdate 472 | if (_shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) { 473 | // If you need to update, continue to call the child node receiveComponent method, passing the new element updating child nodes. 474 | prevComponentInstance.receiveComponent(nextRenderedElement); 475 | // Complete update by calling the component's corresponding componentDidUpdate() 476 | inst.componentDidUpdate && inst.componentDidUpdate(); 477 | } else { 478 | // If you find two completely different element, then just re-render the 479 | var thisID = this._rootNodeID; 480 | // Instantiate the corresponding Component. 481 | this._renderedComponent = this._instantiateReactComponent(nextRenderedElement); 482 | // Mount the corresponding component and get its DOM content. 483 | var nextMarkup = _renderedComponent.mountComponent(thisID); 484 | // Replace the entire node. 485 | $('[data-reactid="' + this._rootNodeID + '"]').replaceWith(nextMarkup); 486 | } 487 | }; 488 | 489 | // Used to compare two elements and determine if update is needed 490 | // Checks if element types are the same and if an object also checks for the `key` which uniquely identifies the element. 491 | var _shouldUpdateReactComponent = function (prevElement, nextElement) { 492 | if (prevElement && nextElement) { 493 | var prevType = typeof prevElement; 494 | var nextType = typeof nextElement; 495 | if (prevType === 'string' || prevType === 'number') { 496 | return nextType === 'string' || nextType === 'number'; 497 | } else { 498 | return nextType === 'object' && prevElement.type === nextElement.type && prevElement.key === nextElement.key; 499 | } 500 | } 501 | return false; 502 | }; 503 | 504 | function instantiateReactComponent(node) { 505 | // If a text node 506 | if (typeof node === 'string' || typeof node === 'number') { 507 | return new ReactDOMTextComponent(node); 508 | } 509 | // If a browser tag/node 510 | if (typeof node === 'object' && typeof node.type === 'string') { 511 | return new ReactDOMComponent(node); 512 | } 513 | // If composite element node 514 | if (typeof node === 'object' && typeof node.type === 'function') { 515 | return new ReactCompositeComponent(node); 516 | } 517 | } 518 | 519 | // React Element's instance is basically what we call the Virtual DOM. 520 | // It has a type attribute representing the current node type, as well as props attribute for the node. 521 | // Also here the `key` is used to uniquely identify this element regardless of where you are in the DOM tree + optimizations for future updates 522 | function ReactElement(type, key, props) { 523 | this.type = type; 524 | this.key = key; 525 | this.props = props; 526 | } 527 | 528 | // ReactClass - In a way this is the super class 529 | // For demo purposes, we're keep ReactClass simple - the original code handles a lot of things, 530 | // eg. mixin class inheritance support, componentDidMount etc. 531 | var ReactClass = function () { 532 | }; 533 | 534 | // This leaves subclasses to inherit the render 535 | ReactClass.prototype.render = function () { 536 | }; 537 | 538 | // setState 539 | ReactClass.prototype.setState = function (newState) { 540 | // Remember when we mount the ReactCompositeComponent - we save a reference to its instance. 541 | // So here, we can get the corresponding ReactCompositeComponent instance _reactInternalInstance 542 | // and call its receiveComponent() - which handles updates to a component. 543 | this._reactInternalInstance.receiveComponent(null, newState); 544 | // Note: 545 | // mountComponent is implemented to deal with the component's initial rendering. 546 | // receiveComponent - should be implemented in all classes that need to handle their own updates. 547 | }; 548 | 549 | React = { 550 | nextReactRootIndex: 0, 551 | createClass: function (spec) { 552 | // Generate a subclass 553 | var Constructor = function (props) { 554 | this.props = props; 555 | this.state = this.getInitialState ? this.getInitialState() : null; 556 | }; 557 | // Prototype Inheritance 558 | // This is a way to implement two levels of inheritance with constructor. 559 | // If not clear check this: http://stackoverflow.com/a/7719184/1672655 560 | Constructor.prototype = new ReactClass(); // (or) Object.create(ReactClass.prototype) 561 | Constructor.prototype.constructor = Constructor; // Setting back the constructor to `Constructor` 562 | 563 | // Combine spec with prototype 564 | $.extend(Constructor.prototype, spec); 565 | return Constructor; 566 | }, 567 | createElement: function (type, config, children) { 568 | var props = {}; 569 | var propName; 570 | config = config || {}; 571 | // Check if the key is specified - if so it is easy to quickly identify and update the element in future. 572 | var key = config.key || null; 573 | 574 | // Copy config object over as props. 575 | for (propName in config) { 576 | if (config.hasOwnProperty(propName) && propName !== 'key') { 577 | props[propName] = config[propName]; 578 | } 579 | } 580 | 581 | // Handle children - which can be passed as additional params to createElement() 582 | var childrenLength = arguments.length - 2; 583 | if (childrenLength === 1) { 584 | props.children = Array.isArray(children) ? children : [children]; 585 | } else if (childrenLength > 1) { 586 | var childArray = []; 587 | for (var i = 0; i < childrenLength; i++) { 588 | childArray[i] = arguments[i + 2]; 589 | } 590 | props.children = childArray; 591 | } 592 | 593 | return new ReactElement(type, key, props); 594 | }, 595 | render: function (element, container) { 596 | var componentInstance = instantiateReactComponent(element); 597 | var markup = componentInstance.mountComponent(React.nextReactRootIndex++); 598 | $(container).html(markup); 599 | // Trigger the mount complete event 600 | $(document).trigger('mountReady'); 601 | } 602 | }; --------------------------------------------------------------------------------