├── 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 = '' + this._currentElement.type + '>';
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 | };
--------------------------------------------------------------------------------