UNPKG

5.69 kBJavaScriptView Raw
1/**
2 * Copyright (c) 2013-present, Facebook, Inc.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 */
8
9'use strict';
10
11var _prodInvariant = require('./reactProdInvariant'),
12 _assign = require('object-assign');
13
14var DOMChildrenOperations = require('./DOMChildrenOperations');
15var DOMLazyTree = require('./DOMLazyTree');
16var ReactDOMComponentTree = require('./ReactDOMComponentTree');
17
18var escapeTextContentForBrowser = require('./escapeTextContentForBrowser');
19var invariant = require('fbjs/lib/invariant');
20var validateDOMNesting = require('./validateDOMNesting');
21
22/**
23 * Text nodes violate a couple assumptions that React makes about components:
24 *
25 * - When mounting text into the DOM, adjacent text nodes are merged.
26 * - Text nodes cannot be assigned a React root ID.
27 *
28 * This component is used to wrap strings between comment nodes so that they
29 * can undergo the same reconciliation that is applied to elements.
30 *
31 * TODO: Investigate representing React components in the DOM with text nodes.
32 *
33 * @class ReactDOMTextComponent
34 * @extends ReactComponent
35 * @internal
36 */
37var ReactDOMTextComponent = function (text) {
38 // TODO: This is really a ReactText (ReactNode), not a ReactElement
39 this._currentElement = text;
40 this._stringText = '' + text;
41 // ReactDOMComponentTree uses these:
42 this._hostNode = null;
43 this._hostParent = null;
44
45 // Properties
46 this._domID = 0;
47 this._mountIndex = 0;
48 this._closingComment = null;
49 this._commentNodes = null;
50};
51
52_assign(ReactDOMTextComponent.prototype, {
53 /**
54 * Creates the markup for this text node. This node is not intended to have
55 * any features besides containing text content.
56 *
57 * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
58 * @return {string} Markup for this text node.
59 * @internal
60 */
61 mountComponent: function (transaction, hostParent, hostContainerInfo, context) {
62 if (process.env.NODE_ENV !== 'production') {
63 var parentInfo;
64 if (hostParent != null) {
65 parentInfo = hostParent._ancestorInfo;
66 } else if (hostContainerInfo != null) {
67 parentInfo = hostContainerInfo._ancestorInfo;
68 }
69 if (parentInfo) {
70 // parentInfo should always be present except for the top-level
71 // component when server rendering
72 validateDOMNesting(null, this._stringText, this, parentInfo);
73 }
74 }
75
76 var domID = hostContainerInfo._idCounter++;
77 var openingValue = ' react-text: ' + domID + ' ';
78 var closingValue = ' /react-text ';
79 this._domID = domID;
80 this._hostParent = hostParent;
81 if (transaction.useCreateElement) {
82 var ownerDocument = hostContainerInfo._ownerDocument;
83 var openingComment = ownerDocument.createComment(openingValue);
84 var closingComment = ownerDocument.createComment(closingValue);
85 var lazyTree = DOMLazyTree(ownerDocument.createDocumentFragment());
86 DOMLazyTree.queueChild(lazyTree, DOMLazyTree(openingComment));
87 if (this._stringText) {
88 DOMLazyTree.queueChild(lazyTree, DOMLazyTree(ownerDocument.createTextNode(this._stringText)));
89 }
90 DOMLazyTree.queueChild(lazyTree, DOMLazyTree(closingComment));
91 ReactDOMComponentTree.precacheNode(this, openingComment);
92 this._closingComment = closingComment;
93 return lazyTree;
94 } else {
95 var escapedText = escapeTextContentForBrowser(this._stringText);
96
97 if (transaction.renderToStaticMarkup) {
98 // Normally we'd wrap this between comment nodes for the reasons stated
99 // above, but since this is a situation where React won't take over
100 // (static pages), we can simply return the text as it is.
101 return escapedText;
102 }
103
104 return '<!--' + openingValue + '-->' + escapedText + '<!--' + closingValue + '-->';
105 }
106 },
107
108 /**
109 * Updates this component by updating the text content.
110 *
111 * @param {ReactText} nextText The next text content
112 * @param {ReactReconcileTransaction} transaction
113 * @internal
114 */
115 receiveComponent: function (nextText, transaction) {
116 if (nextText !== this._currentElement) {
117 this._currentElement = nextText;
118 var nextStringText = '' + nextText;
119 if (nextStringText !== this._stringText) {
120 // TODO: Save this as pending props and use performUpdateIfNecessary
121 // and/or updateComponent to do the actual update for consistency with
122 // other component types?
123 this._stringText = nextStringText;
124 var commentNodes = this.getHostNode();
125 DOMChildrenOperations.replaceDelimitedText(commentNodes[0], commentNodes[1], nextStringText);
126 }
127 }
128 },
129
130 getHostNode: function () {
131 var hostNode = this._commentNodes;
132 if (hostNode) {
133 return hostNode;
134 }
135 if (!this._closingComment) {
136 var openingComment = ReactDOMComponentTree.getNodeFromInstance(this);
137 var node = openingComment.nextSibling;
138 while (true) {
139 !(node != null) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Missing closing comment for text component %s', this._domID) : _prodInvariant('67', this._domID) : void 0;
140 if (node.nodeType === 8 && node.nodeValue === ' /react-text ') {
141 this._closingComment = node;
142 break;
143 }
144 node = node.nextSibling;
145 }
146 }
147 hostNode = [this._hostNode, this._closingComment];
148 this._commentNodes = hostNode;
149 return hostNode;
150 },
151
152 unmountComponent: function () {
153 this._closingComment = null;
154 this._commentNodes = null;
155 ReactDOMComponentTree.uncacheNode(this);
156 }
157});
158
159module.exports = ReactDOMTextComponent;
\No newline at end of file