Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81152 views
1
/**
2
* Copyright 2013-2014, Facebook, Inc.
3
* All rights reserved.
4
*
5
* This source code is licensed under the BSD-style license found in the
6
* LICENSE file in the root directory of this source tree. An additional grant
7
* of patent rights can be found in the PATENTS file in the same directory.
8
*
9
* @providesModule ReactComponent
10
*/
11
12
"use strict";
13
14
var ReactElement = require('ReactElement');
15
var ReactOwner = require('ReactOwner');
16
var ReactUpdates = require('ReactUpdates');
17
18
var assign = require('Object.assign');
19
var invariant = require('invariant');
20
var keyMirror = require('keyMirror');
21
22
/**
23
* Every React component is in one of these life cycles.
24
*/
25
var ComponentLifeCycle = keyMirror({
26
/**
27
* Mounted components have a DOM node representation and are capable of
28
* receiving new props.
29
*/
30
MOUNTED: null,
31
/**
32
* Unmounted components are inactive and cannot receive new props.
33
*/
34
UNMOUNTED: null
35
});
36
37
var injected = false;
38
39
/**
40
* Optionally injectable environment dependent cleanup hook. (server vs.
41
* browser etc). Example: A browser system caches DOM nodes based on component
42
* ID and must remove that cache entry when this instance is unmounted.
43
*
44
* @private
45
*/
46
var unmountIDFromEnvironment = null;
47
48
/**
49
* The "image" of a component tree, is the platform specific (typically
50
* serialized) data that represents a tree of lower level UI building blocks.
51
* On the web, this "image" is HTML markup which describes a construction of
52
* low level `div` and `span` nodes. Other platforms may have different
53
* encoding of this "image". This must be injected.
54
*
55
* @private
56
*/
57
var mountImageIntoNode = null;
58
59
/**
60
* Components are the basic units of composition in React.
61
*
62
* Every component accepts a set of keyed input parameters known as "props" that
63
* are initialized by the constructor. Once a component is mounted, the props
64
* can be mutated using `setProps` or `replaceProps`.
65
*
66
* Every component is capable of the following operations:
67
*
68
* `mountComponent`
69
* Initializes the component, renders markup, and registers event listeners.
70
*
71
* `receiveComponent`
72
* Updates the rendered DOM nodes to match the given component.
73
*
74
* `unmountComponent`
75
* Releases any resources allocated by this component.
76
*
77
* Components can also be "owned" by other components. Being owned by another
78
* component means being constructed by that component. This is different from
79
* being the child of a component, which means having a DOM representation that
80
* is a child of the DOM representation of that component.
81
*
82
* @class ReactComponent
83
*/
84
var ReactComponent = {
85
86
injection: {
87
injectEnvironment: function(ReactComponentEnvironment) {
88
invariant(
89
!injected,
90
'ReactComponent: injectEnvironment() can only be called once.'
91
);
92
mountImageIntoNode = ReactComponentEnvironment.mountImageIntoNode;
93
unmountIDFromEnvironment =
94
ReactComponentEnvironment.unmountIDFromEnvironment;
95
ReactComponent.BackendIDOperations =
96
ReactComponentEnvironment.BackendIDOperations;
97
injected = true;
98
}
99
},
100
101
/**
102
* @internal
103
*/
104
LifeCycle: ComponentLifeCycle,
105
106
/**
107
* Injected module that provides ability to mutate individual properties.
108
* Injected into the base class because many different subclasses need access
109
* to this.
110
*
111
* @internal
112
*/
113
BackendIDOperations: null,
114
115
/**
116
* Base functionality for every ReactComponent constructor. Mixed into the
117
* `ReactComponent` prototype, but exposed statically for easy access.
118
*
119
* @lends {ReactComponent.prototype}
120
*/
121
Mixin: {
122
123
/**
124
* Checks whether or not this component is mounted.
125
*
126
* @return {boolean} True if mounted, false otherwise.
127
* @final
128
* @protected
129
*/
130
isMounted: function() {
131
return this._lifeCycleState === ComponentLifeCycle.MOUNTED;
132
},
133
134
/**
135
* Sets a subset of the props.
136
*
137
* @param {object} partialProps Subset of the next props.
138
* @param {?function} callback Called after props are updated.
139
* @final
140
* @public
141
*/
142
setProps: function(partialProps, callback) {
143
// Merge with the pending element if it exists, otherwise with existing
144
// element props.
145
var element = this._pendingElement || this._currentElement;
146
this.replaceProps(
147
assign({}, element.props, partialProps),
148
callback
149
);
150
},
151
152
/**
153
* Replaces all of the props.
154
*
155
* @param {object} props New props.
156
* @param {?function} callback Called after props are updated.
157
* @final
158
* @public
159
*/
160
replaceProps: function(props, callback) {
161
invariant(
162
this.isMounted(),
163
'replaceProps(...): Can only update a mounted component.'
164
);
165
invariant(
166
this._mountDepth === 0,
167
'replaceProps(...): You called `setProps` or `replaceProps` on a ' +
168
'component with a parent. This is an anti-pattern since props will ' +
169
'get reactively updated when rendered. Instead, change the owner\'s ' +
170
'`render` method to pass the correct value as props to the component ' +
171
'where it is created.'
172
);
173
// This is a deoptimized path. We optimize for always having a element.
174
// This creates an extra internal element.
175
this._pendingElement = ReactElement.cloneAndReplaceProps(
176
this._pendingElement || this._currentElement,
177
props
178
);
179
ReactUpdates.enqueueUpdate(this, callback);
180
},
181
182
/**
183
* Schedule a partial update to the props. Only used for internal testing.
184
*
185
* @param {object} partialProps Subset of the next props.
186
* @param {?function} callback Called after props are updated.
187
* @final
188
* @internal
189
*/
190
_setPropsInternal: function(partialProps, callback) {
191
// This is a deoptimized path. We optimize for always having a element.
192
// This creates an extra internal element.
193
var element = this._pendingElement || this._currentElement;
194
this._pendingElement = ReactElement.cloneAndReplaceProps(
195
element,
196
assign({}, element.props, partialProps)
197
);
198
ReactUpdates.enqueueUpdate(this, callback);
199
},
200
201
/**
202
* Base constructor for all React components.
203
*
204
* Subclasses that override this method should make sure to invoke
205
* `ReactComponent.Mixin.construct.call(this, ...)`.
206
*
207
* @param {ReactElement} element
208
* @internal
209
*/
210
construct: function(element) {
211
// This is the public exposed props object after it has been processed
212
// with default props. The element's props represents the true internal
213
// state of the props.
214
this.props = element.props;
215
// Record the component responsible for creating this component.
216
// This is accessible through the element but we maintain an extra
217
// field for compatibility with devtools and as a way to make an
218
// incremental update. TODO: Consider deprecating this field.
219
this._owner = element._owner;
220
221
// All components start unmounted.
222
this._lifeCycleState = ComponentLifeCycle.UNMOUNTED;
223
224
// See ReactUpdates.
225
this._pendingCallbacks = null;
226
227
// We keep the old element and a reference to the pending element
228
// to track updates.
229
this._currentElement = element;
230
this._pendingElement = null;
231
},
232
233
/**
234
* Initializes the component, renders markup, and registers event listeners.
235
*
236
* NOTE: This does not insert any nodes into the DOM.
237
*
238
* Subclasses that override this method should make sure to invoke
239
* `ReactComponent.Mixin.mountComponent.call(this, ...)`.
240
*
241
* @param {string} rootID DOM ID of the root node.
242
* @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
243
* @param {number} mountDepth number of components in the owner hierarchy.
244
* @return {?string} Rendered markup to be inserted into the DOM.
245
* @internal
246
*/
247
mountComponent: function(rootID, transaction, mountDepth) {
248
invariant(
249
!this.isMounted(),
250
'mountComponent(%s, ...): Can only mount an unmounted component. ' +
251
'Make sure to avoid storing components between renders or reusing a ' +
252
'single component instance in multiple places.',
253
rootID
254
);
255
var ref = this._currentElement.ref;
256
if (ref != null) {
257
var owner = this._currentElement._owner;
258
ReactOwner.addComponentAsRefTo(this, ref, owner);
259
}
260
this._rootNodeID = rootID;
261
this._lifeCycleState = ComponentLifeCycle.MOUNTED;
262
this._mountDepth = mountDepth;
263
// Effectively: return '';
264
},
265
266
/**
267
* Releases any resources allocated by `mountComponent`.
268
*
269
* NOTE: This does not remove any nodes from the DOM.
270
*
271
* Subclasses that override this method should make sure to invoke
272
* `ReactComponent.Mixin.unmountComponent.call(this)`.
273
*
274
* @internal
275
*/
276
unmountComponent: function() {
277
invariant(
278
this.isMounted(),
279
'unmountComponent(): Can only unmount a mounted component.'
280
);
281
var ref = this._currentElement.ref;
282
if (ref != null) {
283
ReactOwner.removeComponentAsRefFrom(this, ref, this._owner);
284
}
285
unmountIDFromEnvironment(this._rootNodeID);
286
this._rootNodeID = null;
287
this._lifeCycleState = ComponentLifeCycle.UNMOUNTED;
288
},
289
290
/**
291
* Given a new instance of this component, updates the rendered DOM nodes
292
* as if that instance was rendered instead.
293
*
294
* Subclasses that override this method should make sure to invoke
295
* `ReactComponent.Mixin.receiveComponent.call(this, ...)`.
296
*
297
* @param {object} nextComponent Next set of properties.
298
* @param {ReactReconcileTransaction} transaction
299
* @internal
300
*/
301
receiveComponent: function(nextElement, transaction) {
302
invariant(
303
this.isMounted(),
304
'receiveComponent(...): Can only update a mounted component.'
305
);
306
this._pendingElement = nextElement;
307
this.performUpdateIfNecessary(transaction);
308
},
309
310
/**
311
* If `_pendingElement` is set, update the component.
312
*
313
* @param {ReactReconcileTransaction} transaction
314
* @internal
315
*/
316
performUpdateIfNecessary: function(transaction) {
317
if (this._pendingElement == null) {
318
return;
319
}
320
var prevElement = this._currentElement;
321
var nextElement = this._pendingElement;
322
this._currentElement = nextElement;
323
this.props = nextElement.props;
324
this._owner = nextElement._owner;
325
this._pendingElement = null;
326
this.updateComponent(transaction, prevElement);
327
},
328
329
/**
330
* Updates the component's currently mounted representation.
331
*
332
* @param {ReactReconcileTransaction} transaction
333
* @param {object} prevElement
334
* @internal
335
*/
336
updateComponent: function(transaction, prevElement) {
337
var nextElement = this._currentElement;
338
339
// If either the owner or a `ref` has changed, make sure the newest owner
340
// has stored a reference to `this`, and the previous owner (if different)
341
// has forgotten the reference to `this`. We use the element instead
342
// of the public this.props because the post processing cannot determine
343
// a ref. The ref conceptually lives on the element.
344
345
// TODO: Should this even be possible? The owner cannot change because
346
// it's forbidden by shouldUpdateReactComponent. The ref can change
347
// if you swap the keys of but not the refs. Reconsider where this check
348
// is made. It probably belongs where the key checking and
349
// instantiateReactComponent is done.
350
351
if (nextElement._owner !== prevElement._owner ||
352
nextElement.ref !== prevElement.ref) {
353
if (prevElement.ref != null) {
354
ReactOwner.removeComponentAsRefFrom(
355
this, prevElement.ref, prevElement._owner
356
);
357
}
358
// Correct, even if the owner is the same, and only the ref has changed.
359
if (nextElement.ref != null) {
360
ReactOwner.addComponentAsRefTo(
361
this,
362
nextElement.ref,
363
nextElement._owner
364
);
365
}
366
}
367
},
368
369
/**
370
* Mounts this component and inserts it into the DOM.
371
*
372
* @param {string} rootID DOM ID of the root node.
373
* @param {DOMElement} container DOM element to mount into.
374
* @param {boolean} shouldReuseMarkup If true, do not insert markup
375
* @final
376
* @internal
377
* @see {ReactMount.render}
378
*/
379
mountComponentIntoNode: function(rootID, container, shouldReuseMarkup) {
380
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled();
381
transaction.perform(
382
this._mountComponentIntoNode,
383
this,
384
rootID,
385
container,
386
transaction,
387
shouldReuseMarkup
388
);
389
ReactUpdates.ReactReconcileTransaction.release(transaction);
390
},
391
392
/**
393
* @param {string} rootID DOM ID of the root node.
394
* @param {DOMElement} container DOM element to mount into.
395
* @param {ReactReconcileTransaction} transaction
396
* @param {boolean} shouldReuseMarkup If true, do not insert markup
397
* @final
398
* @private
399
*/
400
_mountComponentIntoNode: function(
401
rootID,
402
container,
403
transaction,
404
shouldReuseMarkup) {
405
var markup = this.mountComponent(rootID, transaction, 0);
406
mountImageIntoNode(markup, container, shouldReuseMarkup);
407
},
408
409
/**
410
* Checks if this component is owned by the supplied `owner` component.
411
*
412
* @param {ReactComponent} owner Component to check.
413
* @return {boolean} True if `owners` owns this component.
414
* @final
415
* @internal
416
*/
417
isOwnedBy: function(owner) {
418
return this._owner === owner;
419
},
420
421
/**
422
* Gets another component, that shares the same owner as this one, by ref.
423
*
424
* @param {string} ref of a sibling Component.
425
* @return {?ReactComponent} the actual sibling Component.
426
* @final
427
* @internal
428
*/
429
getSiblingByRef: function(ref) {
430
var owner = this._owner;
431
if (!owner || !owner.refs) {
432
return null;
433
}
434
return owner.refs[ref];
435
}
436
}
437
};
438
439
module.exports = ReactComponent;
440
441