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 ReactCompositeComponent
10
*/
11
12
"use strict";
13
14
var ReactComponent = require('ReactComponent');
15
var ReactContext = require('ReactContext');
16
var ReactCurrentOwner = require('ReactCurrentOwner');
17
var ReactElement = require('ReactElement');
18
var ReactElementValidator = require('ReactElementValidator');
19
var ReactEmptyComponent = require('ReactEmptyComponent');
20
var ReactErrorUtils = require('ReactErrorUtils');
21
var ReactLegacyElement = require('ReactLegacyElement');
22
var ReactOwner = require('ReactOwner');
23
var ReactPerf = require('ReactPerf');
24
var ReactPropTransferer = require('ReactPropTransferer');
25
var ReactPropTypeLocations = require('ReactPropTypeLocations');
26
var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
27
var ReactUpdates = require('ReactUpdates');
28
29
var assign = require('Object.assign');
30
var instantiateReactComponent = require('instantiateReactComponent');
31
var invariant = require('invariant');
32
var keyMirror = require('keyMirror');
33
var keyOf = require('keyOf');
34
var monitorCodeUse = require('monitorCodeUse');
35
var mapObject = require('mapObject');
36
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
37
var warning = require('warning');
38
39
var MIXINS_KEY = keyOf({mixins: null});
40
41
/**
42
* Policies that describe methods in `ReactCompositeComponentInterface`.
43
*/
44
var SpecPolicy = keyMirror({
45
/**
46
* These methods may be defined only once by the class specification or mixin.
47
*/
48
DEFINE_ONCE: null,
49
/**
50
* These methods may be defined by both the class specification and mixins.
51
* Subsequent definitions will be chained. These methods must return void.
52
*/
53
DEFINE_MANY: null,
54
/**
55
* These methods are overriding the base ReactCompositeComponent class.
56
*/
57
OVERRIDE_BASE: null,
58
/**
59
* These methods are similar to DEFINE_MANY, except we assume they return
60
* objects. We try to merge the keys of the return values of all the mixed in
61
* functions. If there is a key conflict we throw.
62
*/
63
DEFINE_MANY_MERGED: null
64
});
65
66
67
var injectedMixins = [];
68
69
/**
70
* Composite components are higher-level components that compose other composite
71
* or native components.
72
*
73
* To create a new type of `ReactCompositeComponent`, pass a specification of
74
* your new class to `React.createClass`. The only requirement of your class
75
* specification is that you implement a `render` method.
76
*
77
* var MyComponent = React.createClass({
78
* render: function() {
79
* return <div>Hello World</div>;
80
* }
81
* });
82
*
83
* The class specification supports a specific protocol of methods that have
84
* special meaning (e.g. `render`). See `ReactCompositeComponentInterface` for
85
* more the comprehensive protocol. Any other properties and methods in the
86
* class specification will available on the prototype.
87
*
88
* @interface ReactCompositeComponentInterface
89
* @internal
90
*/
91
var ReactCompositeComponentInterface = {
92
93
/**
94
* An array of Mixin objects to include when defining your component.
95
*
96
* @type {array}
97
* @optional
98
*/
99
mixins: SpecPolicy.DEFINE_MANY,
100
101
/**
102
* An object containing properties and methods that should be defined on
103
* the component's constructor instead of its prototype (static methods).
104
*
105
* @type {object}
106
* @optional
107
*/
108
statics: SpecPolicy.DEFINE_MANY,
109
110
/**
111
* Definition of prop types for this component.
112
*
113
* @type {object}
114
* @optional
115
*/
116
propTypes: SpecPolicy.DEFINE_MANY,
117
118
/**
119
* Definition of context types for this component.
120
*
121
* @type {object}
122
* @optional
123
*/
124
contextTypes: SpecPolicy.DEFINE_MANY,
125
126
/**
127
* Definition of context types this component sets for its children.
128
*
129
* @type {object}
130
* @optional
131
*/
132
childContextTypes: SpecPolicy.DEFINE_MANY,
133
134
// ==== Definition methods ====
135
136
/**
137
* Invoked when the component is mounted. Values in the mapping will be set on
138
* `this.props` if that prop is not specified (i.e. using an `in` check).
139
*
140
* This method is invoked before `getInitialState` and therefore cannot rely
141
* on `this.state` or use `this.setState`.
142
*
143
* @return {object}
144
* @optional
145
*/
146
getDefaultProps: SpecPolicy.DEFINE_MANY_MERGED,
147
148
/**
149
* Invoked once before the component is mounted. The return value will be used
150
* as the initial value of `this.state`.
151
*
152
* getInitialState: function() {
153
* return {
154
* isOn: false,
155
* fooBaz: new BazFoo()
156
* }
157
* }
158
*
159
* @return {object}
160
* @optional
161
*/
162
getInitialState: SpecPolicy.DEFINE_MANY_MERGED,
163
164
/**
165
* @return {object}
166
* @optional
167
*/
168
getChildContext: SpecPolicy.DEFINE_MANY_MERGED,
169
170
/**
171
* Uses props from `this.props` and state from `this.state` to render the
172
* structure of the component.
173
*
174
* No guarantees are made about when or how often this method is invoked, so
175
* it must not have side effects.
176
*
177
* render: function() {
178
* var name = this.props.name;
179
* return <div>Hello, {name}!</div>;
180
* }
181
*
182
* @return {ReactComponent}
183
* @nosideeffects
184
* @required
185
*/
186
render: SpecPolicy.DEFINE_ONCE,
187
188
189
190
// ==== Delegate methods ====
191
192
/**
193
* Invoked when the component is initially created and about to be mounted.
194
* This may have side effects, but any external subscriptions or data created
195
* by this method must be cleaned up in `componentWillUnmount`.
196
*
197
* @optional
198
*/
199
componentWillMount: SpecPolicy.DEFINE_MANY,
200
201
/**
202
* Invoked when the component has been mounted and has a DOM representation.
203
* However, there is no guarantee that the DOM node is in the document.
204
*
205
* Use this as an opportunity to operate on the DOM when the component has
206
* been mounted (initialized and rendered) for the first time.
207
*
208
* @param {DOMElement} rootNode DOM element representing the component.
209
* @optional
210
*/
211
componentDidMount: SpecPolicy.DEFINE_MANY,
212
213
/**
214
* Invoked before the component receives new props.
215
*
216
* Use this as an opportunity to react to a prop transition by updating the
217
* state using `this.setState`. Current props are accessed via `this.props`.
218
*
219
* componentWillReceiveProps: function(nextProps, nextContext) {
220
* this.setState({
221
* likesIncreasing: nextProps.likeCount > this.props.likeCount
222
* });
223
* }
224
*
225
* NOTE: There is no equivalent `componentWillReceiveState`. An incoming prop
226
* transition may cause a state change, but the opposite is not true. If you
227
* need it, you are probably looking for `componentWillUpdate`.
228
*
229
* @param {object} nextProps
230
* @optional
231
*/
232
componentWillReceiveProps: SpecPolicy.DEFINE_MANY,
233
234
/**
235
* Invoked while deciding if the component should be updated as a result of
236
* receiving new props, state and/or context.
237
*
238
* Use this as an opportunity to `return false` when you're certain that the
239
* transition to the new props/state/context will not require a component
240
* update.
241
*
242
* shouldComponentUpdate: function(nextProps, nextState, nextContext) {
243
* return !equal(nextProps, this.props) ||
244
* !equal(nextState, this.state) ||
245
* !equal(nextContext, this.context);
246
* }
247
*
248
* @param {object} nextProps
249
* @param {?object} nextState
250
* @param {?object} nextContext
251
* @return {boolean} True if the component should update.
252
* @optional
253
*/
254
shouldComponentUpdate: SpecPolicy.DEFINE_ONCE,
255
256
/**
257
* Invoked when the component is about to update due to a transition from
258
* `this.props`, `this.state` and `this.context` to `nextProps`, `nextState`
259
* and `nextContext`.
260
*
261
* Use this as an opportunity to perform preparation before an update occurs.
262
*
263
* NOTE: You **cannot** use `this.setState()` in this method.
264
*
265
* @param {object} nextProps
266
* @param {?object} nextState
267
* @param {?object} nextContext
268
* @param {ReactReconcileTransaction} transaction
269
* @optional
270
*/
271
componentWillUpdate: SpecPolicy.DEFINE_MANY,
272
273
/**
274
* Invoked when the component's DOM representation has been updated.
275
*
276
* Use this as an opportunity to operate on the DOM when the component has
277
* been updated.
278
*
279
* @param {object} prevProps
280
* @param {?object} prevState
281
* @param {?object} prevContext
282
* @param {DOMElement} rootNode DOM element representing the component.
283
* @optional
284
*/
285
componentDidUpdate: SpecPolicy.DEFINE_MANY,
286
287
/**
288
* Invoked when the component is about to be removed from its parent and have
289
* its DOM representation destroyed.
290
*
291
* Use this as an opportunity to deallocate any external resources.
292
*
293
* NOTE: There is no `componentDidUnmount` since your component will have been
294
* destroyed by that point.
295
*
296
* @optional
297
*/
298
componentWillUnmount: SpecPolicy.DEFINE_MANY,
299
300
301
302
// ==== Advanced methods ====
303
304
/**
305
* Updates the component's currently mounted DOM representation.
306
*
307
* By default, this implements React's rendering and reconciliation algorithm.
308
* Sophisticated clients may wish to override this.
309
*
310
* @param {ReactReconcileTransaction} transaction
311
* @internal
312
* @overridable
313
*/
314
updateComponent: SpecPolicy.OVERRIDE_BASE
315
316
};
317
318
/**
319
* Mapping from class specification keys to special processing functions.
320
*
321
* Although these are declared like instance properties in the specification
322
* when defining classes using `React.createClass`, they are actually static
323
* and are accessible on the constructor instead of the prototype. Despite
324
* being static, they must be defined outside of the "statics" key under
325
* which all other static methods are defined.
326
*/
327
var RESERVED_SPEC_KEYS = {
328
displayName: function(Constructor, displayName) {
329
Constructor.displayName = displayName;
330
},
331
mixins: function(Constructor, mixins) {
332
if (mixins) {
333
for (var i = 0; i < mixins.length; i++) {
334
mixSpecIntoComponent(Constructor, mixins[i]);
335
}
336
}
337
},
338
childContextTypes: function(Constructor, childContextTypes) {
339
validateTypeDef(
340
Constructor,
341
childContextTypes,
342
ReactPropTypeLocations.childContext
343
);
344
Constructor.childContextTypes = assign(
345
{},
346
Constructor.childContextTypes,
347
childContextTypes
348
);
349
},
350
contextTypes: function(Constructor, contextTypes) {
351
validateTypeDef(
352
Constructor,
353
contextTypes,
354
ReactPropTypeLocations.context
355
);
356
Constructor.contextTypes = assign(
357
{},
358
Constructor.contextTypes,
359
contextTypes
360
);
361
},
362
/**
363
* Special case getDefaultProps which should move into statics but requires
364
* automatic merging.
365
*/
366
getDefaultProps: function(Constructor, getDefaultProps) {
367
if (Constructor.getDefaultProps) {
368
Constructor.getDefaultProps = createMergedResultFunction(
369
Constructor.getDefaultProps,
370
getDefaultProps
371
);
372
} else {
373
Constructor.getDefaultProps = getDefaultProps;
374
}
375
},
376
propTypes: function(Constructor, propTypes) {
377
validateTypeDef(
378
Constructor,
379
propTypes,
380
ReactPropTypeLocations.prop
381
);
382
Constructor.propTypes = assign(
383
{},
384
Constructor.propTypes,
385
propTypes
386
);
387
},
388
statics: function(Constructor, statics) {
389
mixStaticSpecIntoComponent(Constructor, statics);
390
}
391
};
392
393
function getDeclarationErrorAddendum(component) {
394
var owner = component._owner || null;
395
if (owner && owner.constructor && owner.constructor.displayName) {
396
return ' Check the render method of `' + owner.constructor.displayName +
397
'`.';
398
}
399
return '';
400
}
401
402
function validateTypeDef(Constructor, typeDef, location) {
403
for (var propName in typeDef) {
404
if (typeDef.hasOwnProperty(propName)) {
405
invariant(
406
typeof typeDef[propName] == 'function',
407
'%s: %s type `%s` is invalid; it must be a function, usually from ' +
408
'React.PropTypes.',
409
Constructor.displayName || 'ReactCompositeComponent',
410
ReactPropTypeLocationNames[location],
411
propName
412
);
413
}
414
}
415
}
416
417
function validateMethodOverride(proto, name) {
418
var specPolicy = ReactCompositeComponentInterface.hasOwnProperty(name) ?
419
ReactCompositeComponentInterface[name] :
420
null;
421
422
// Disallow overriding of base class methods unless explicitly allowed.
423
if (ReactCompositeComponentMixin.hasOwnProperty(name)) {
424
invariant(
425
specPolicy === SpecPolicy.OVERRIDE_BASE,
426
'ReactCompositeComponentInterface: You are attempting to override ' +
427
'`%s` from your class specification. Ensure that your method names ' +
428
'do not overlap with React methods.',
429
name
430
);
431
}
432
433
// Disallow defining methods more than once unless explicitly allowed.
434
if (proto.hasOwnProperty(name)) {
435
invariant(
436
specPolicy === SpecPolicy.DEFINE_MANY ||
437
specPolicy === SpecPolicy.DEFINE_MANY_MERGED,
438
'ReactCompositeComponentInterface: You are attempting to define ' +
439
'`%s` on your component more than once. This conflict may be due ' +
440
'to a mixin.',
441
name
442
);
443
}
444
}
445
446
function validateLifeCycleOnReplaceState(instance) {
447
var compositeLifeCycleState = instance._compositeLifeCycleState;
448
invariant(
449
instance.isMounted() ||
450
compositeLifeCycleState === CompositeLifeCycle.MOUNTING,
451
'replaceState(...): Can only update a mounted or mounting component.'
452
);
453
invariant(
454
ReactCurrentOwner.current == null,
455
'replaceState(...): Cannot update during an existing state transition ' +
456
'(such as within `render`). Render methods should be a pure function ' +
457
'of props and state.'
458
);
459
invariant(compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING,
460
'replaceState(...): Cannot update while unmounting component. This ' +
461
'usually means you called setState() on an unmounted component.'
462
);
463
}
464
465
/**
466
* Mixin helper which handles policy validation and reserved
467
* specification keys when building `ReactCompositeComponent` classses.
468
*/
469
function mixSpecIntoComponent(Constructor, spec) {
470
if (!spec) {
471
return;
472
}
473
474
invariant(
475
!ReactLegacyElement.isValidFactory(spec),
476
'ReactCompositeComponent: You\'re attempting to ' +
477
'use a component class as a mixin. Instead, just use a regular object.'
478
);
479
invariant(
480
!ReactElement.isValidElement(spec),
481
'ReactCompositeComponent: You\'re attempting to ' +
482
'use a component as a mixin. Instead, just use a regular object.'
483
);
484
485
var proto = Constructor.prototype;
486
487
// By handling mixins before any other properties, we ensure the same
488
// chaining order is applied to methods with DEFINE_MANY policy, whether
489
// mixins are listed before or after these methods in the spec.
490
if (spec.hasOwnProperty(MIXINS_KEY)) {
491
RESERVED_SPEC_KEYS.mixins(Constructor, spec.mixins);
492
}
493
494
for (var name in spec) {
495
if (!spec.hasOwnProperty(name)) {
496
continue;
497
}
498
499
if (name === MIXINS_KEY) {
500
// We have already handled mixins in a special case above
501
continue;
502
}
503
504
var property = spec[name];
505
validateMethodOverride(proto, name);
506
507
if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {
508
RESERVED_SPEC_KEYS[name](Constructor, property);
509
} else {
510
// Setup methods on prototype:
511
// The following member methods should not be automatically bound:
512
// 1. Expected ReactCompositeComponent methods (in the "interface").
513
// 2. Overridden methods (that were mixed in).
514
var isCompositeComponentMethod =
515
ReactCompositeComponentInterface.hasOwnProperty(name);
516
var isAlreadyDefined = proto.hasOwnProperty(name);
517
var markedDontBind = property && property.__reactDontBind;
518
var isFunction = typeof property === 'function';
519
var shouldAutoBind =
520
isFunction &&
521
!isCompositeComponentMethod &&
522
!isAlreadyDefined &&
523
!markedDontBind;
524
525
if (shouldAutoBind) {
526
if (!proto.__reactAutoBindMap) {
527
proto.__reactAutoBindMap = {};
528
}
529
proto.__reactAutoBindMap[name] = property;
530
proto[name] = property;
531
} else {
532
if (isAlreadyDefined) {
533
var specPolicy = ReactCompositeComponentInterface[name];
534
535
// These cases should already be caught by validateMethodOverride
536
invariant(
537
isCompositeComponentMethod && (
538
specPolicy === SpecPolicy.DEFINE_MANY_MERGED ||
539
specPolicy === SpecPolicy.DEFINE_MANY
540
),
541
'ReactCompositeComponent: Unexpected spec policy %s for key %s ' +
542
'when mixing in component specs.',
543
specPolicy,
544
name
545
);
546
547
// For methods which are defined more than once, call the existing
548
// methods before calling the new property, merging if appropriate.
549
if (specPolicy === SpecPolicy.DEFINE_MANY_MERGED) {
550
proto[name] = createMergedResultFunction(proto[name], property);
551
} else if (specPolicy === SpecPolicy.DEFINE_MANY) {
552
proto[name] = createChainedFunction(proto[name], property);
553
}
554
} else {
555
proto[name] = property;
556
if (__DEV__) {
557
// Add verbose displayName to the function, which helps when looking
558
// at profiling tools.
559
if (typeof property === 'function' && spec.displayName) {
560
proto[name].displayName = spec.displayName + '_' + name;
561
}
562
}
563
}
564
}
565
}
566
}
567
}
568
569
function mixStaticSpecIntoComponent(Constructor, statics) {
570
if (!statics) {
571
return;
572
}
573
for (var name in statics) {
574
var property = statics[name];
575
if (!statics.hasOwnProperty(name)) {
576
continue;
577
}
578
579
var isReserved = name in RESERVED_SPEC_KEYS;
580
invariant(
581
!isReserved,
582
'ReactCompositeComponent: You are attempting to define a reserved ' +
583
'property, `%s`, that shouldn\'t be on the "statics" key. Define it ' +
584
'as an instance property instead; it will still be accessible on the ' +
585
'constructor.',
586
name
587
);
588
589
var isInherited = name in Constructor;
590
invariant(
591
!isInherited,
592
'ReactCompositeComponent: You are attempting to define ' +
593
'`%s` on your component more than once. This conflict may be ' +
594
'due to a mixin.',
595
name
596
);
597
Constructor[name] = property;
598
}
599
}
600
601
/**
602
* Merge two objects, but throw if both contain the same key.
603
*
604
* @param {object} one The first object, which is mutated.
605
* @param {object} two The second object
606
* @return {object} one after it has been mutated to contain everything in two.
607
*/
608
function mergeObjectsWithNoDuplicateKeys(one, two) {
609
invariant(
610
one && two && typeof one === 'object' && typeof two === 'object',
611
'mergeObjectsWithNoDuplicateKeys(): Cannot merge non-objects'
612
);
613
614
mapObject(two, function(value, key) {
615
invariant(
616
one[key] === undefined,
617
'mergeObjectsWithNoDuplicateKeys(): ' +
618
'Tried to merge two objects with the same key: `%s`. This conflict ' +
619
'may be due to a mixin; in particular, this may be caused by two ' +
620
'getInitialState() or getDefaultProps() methods returning objects ' +
621
'with clashing keys.',
622
key
623
);
624
one[key] = value;
625
});
626
return one;
627
}
628
629
/**
630
* Creates a function that invokes two functions and merges their return values.
631
*
632
* @param {function} one Function to invoke first.
633
* @param {function} two Function to invoke second.
634
* @return {function} Function that invokes the two argument functions.
635
* @private
636
*/
637
function createMergedResultFunction(one, two) {
638
return function mergedResult() {
639
var a = one.apply(this, arguments);
640
var b = two.apply(this, arguments);
641
if (a == null) {
642
return b;
643
} else if (b == null) {
644
return a;
645
}
646
return mergeObjectsWithNoDuplicateKeys(a, b);
647
};
648
}
649
650
/**
651
* Creates a function that invokes two functions and ignores their return vales.
652
*
653
* @param {function} one Function to invoke first.
654
* @param {function} two Function to invoke second.
655
* @return {function} Function that invokes the two argument functions.
656
* @private
657
*/
658
function createChainedFunction(one, two) {
659
return function chainedFunction() {
660
one.apply(this, arguments);
661
two.apply(this, arguments);
662
};
663
}
664
665
/**
666
* `ReactCompositeComponent` maintains an auxiliary life cycle state in
667
* `this._compositeLifeCycleState` (which can be null).
668
*
669
* This is different from the life cycle state maintained by `ReactComponent` in
670
* `this._lifeCycleState`. The following diagram shows how the states overlap in
671
* time. There are times when the CompositeLifeCycle is null - at those times it
672
* is only meaningful to look at ComponentLifeCycle alone.
673
*
674
* Top Row: ReactComponent.ComponentLifeCycle
675
* Low Row: ReactComponent.CompositeLifeCycle
676
*
677
* +-------+---------------------------------+--------+
678
* | UN | MOUNTED | UN |
679
* |MOUNTED| | MOUNTED|
680
* +-------+---------------------------------+--------+
681
* | ^--------+ +-------+ +--------^ |
682
* | | | | | | | |
683
* | 0--|MOUNTING|-0-|RECEIVE|-0-| UN |--->0 |
684
* | | | |PROPS | |MOUNTING| |
685
* | | | | | | | |
686
* | | | | | | | |
687
* | +--------+ +-------+ +--------+ |
688
* | | | |
689
* +-------+---------------------------------+--------+
690
*/
691
var CompositeLifeCycle = keyMirror({
692
/**
693
* Components in the process of being mounted respond to state changes
694
* differently.
695
*/
696
MOUNTING: null,
697
/**
698
* Components in the process of being unmounted are guarded against state
699
* changes.
700
*/
701
UNMOUNTING: null,
702
/**
703
* Components that are mounted and receiving new props respond to state
704
* changes differently.
705
*/
706
RECEIVING_PROPS: null
707
});
708
709
/**
710
* @lends {ReactCompositeComponent.prototype}
711
*/
712
var ReactCompositeComponentMixin = {
713
714
/**
715
* Base constructor for all composite component.
716
*
717
* @param {ReactElement} element
718
* @final
719
* @internal
720
*/
721
construct: function(element) {
722
// Children can be either an array or more than one argument
723
ReactComponent.Mixin.construct.apply(this, arguments);
724
ReactOwner.Mixin.construct.apply(this, arguments);
725
726
this.state = null;
727
this._pendingState = null;
728
729
// This is the public post-processed context. The real context and pending
730
// context lives on the element.
731
this.context = null;
732
733
this._compositeLifeCycleState = null;
734
},
735
736
/**
737
* Checks whether or not this composite component is mounted.
738
* @return {boolean} True if mounted, false otherwise.
739
* @protected
740
* @final
741
*/
742
isMounted: function() {
743
return ReactComponent.Mixin.isMounted.call(this) &&
744
this._compositeLifeCycleState !== CompositeLifeCycle.MOUNTING;
745
},
746
747
/**
748
* Initializes the component, renders markup, and registers event listeners.
749
*
750
* @param {string} rootID DOM ID of the root node.
751
* @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
752
* @param {number} mountDepth number of components in the owner hierarchy
753
* @return {?string} Rendered markup to be inserted into the DOM.
754
* @final
755
* @internal
756
*/
757
mountComponent: ReactPerf.measure(
758
'ReactCompositeComponent',
759
'mountComponent',
760
function(rootID, transaction, mountDepth) {
761
ReactComponent.Mixin.mountComponent.call(
762
this,
763
rootID,
764
transaction,
765
mountDepth
766
);
767
this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;
768
769
if (this.__reactAutoBindMap) {
770
this._bindAutoBindMethods();
771
}
772
773
this.context = this._processContext(this._currentElement._context);
774
this.props = this._processProps(this.props);
775
776
this.state = this.getInitialState ? this.getInitialState() : null;
777
invariant(
778
typeof this.state === 'object' && !Array.isArray(this.state),
779
'%s.getInitialState(): must return an object or null',
780
this.constructor.displayName || 'ReactCompositeComponent'
781
);
782
783
this._pendingState = null;
784
this._pendingForceUpdate = false;
785
786
if (this.componentWillMount) {
787
this.componentWillMount();
788
// When mounting, calls to `setState` by `componentWillMount` will set
789
// `this._pendingState` without triggering a re-render.
790
if (this._pendingState) {
791
this.state = this._pendingState;
792
this._pendingState = null;
793
}
794
}
795
796
this._renderedComponent = instantiateReactComponent(
797
this._renderValidatedComponent(),
798
this._currentElement.type // The wrapping type
799
);
800
801
// Done with mounting, `setState` will now trigger UI changes.
802
this._compositeLifeCycleState = null;
803
var markup = this._renderedComponent.mountComponent(
804
rootID,
805
transaction,
806
mountDepth + 1
807
);
808
if (this.componentDidMount) {
809
transaction.getReactMountReady().enqueue(this.componentDidMount, this);
810
}
811
return markup;
812
}
813
),
814
815
/**
816
* Releases any resources allocated by `mountComponent`.
817
*
818
* @final
819
* @internal
820
*/
821
unmountComponent: function() {
822
this._compositeLifeCycleState = CompositeLifeCycle.UNMOUNTING;
823
if (this.componentWillUnmount) {
824
this.componentWillUnmount();
825
}
826
this._compositeLifeCycleState = null;
827
828
this._renderedComponent.unmountComponent();
829
this._renderedComponent = null;
830
831
ReactComponent.Mixin.unmountComponent.call(this);
832
833
// Some existing components rely on this.props even after they've been
834
// destroyed (in event handlers).
835
// TODO: this.props = null;
836
// TODO: this.state = null;
837
},
838
839
/**
840
* Sets a subset of the state. Always use this or `replaceState` to mutate
841
* state. You should treat `this.state` as immutable.
842
*
843
* There is no guarantee that `this.state` will be immediately updated, so
844
* accessing `this.state` after calling this method may return the old value.
845
*
846
* There is no guarantee that calls to `setState` will run synchronously,
847
* as they may eventually be batched together. You can provide an optional
848
* callback that will be executed when the call to setState is actually
849
* completed.
850
*
851
* @param {object} partialState Next partial state to be merged with state.
852
* @param {?function} callback Called after state is updated.
853
* @final
854
* @protected
855
*/
856
setState: function(partialState, callback) {
857
invariant(
858
typeof partialState === 'object' || partialState == null,
859
'setState(...): takes an object of state variables to update.'
860
);
861
if (__DEV__){
862
warning(
863
partialState != null,
864
'setState(...): You passed an undefined or null state object; ' +
865
'instead, use forceUpdate().'
866
);
867
}
868
// Merge with `_pendingState` if it exists, otherwise with existing state.
869
this.replaceState(
870
assign({}, this._pendingState || this.state, partialState),
871
callback
872
);
873
},
874
875
/**
876
* Replaces all of the state. Always use this or `setState` to mutate state.
877
* You should treat `this.state` as immutable.
878
*
879
* There is no guarantee that `this.state` will be immediately updated, so
880
* accessing `this.state` after calling this method may return the old value.
881
*
882
* @param {object} completeState Next state.
883
* @param {?function} callback Called after state is updated.
884
* @final
885
* @protected
886
*/
887
replaceState: function(completeState, callback) {
888
validateLifeCycleOnReplaceState(this);
889
this._pendingState = completeState;
890
if (this._compositeLifeCycleState !== CompositeLifeCycle.MOUNTING) {
891
// If we're in a componentWillMount handler, don't enqueue a rerender
892
// because ReactUpdates assumes we're in a browser context (which is wrong
893
// for server rendering) and we're about to do a render anyway.
894
// TODO: The callback here is ignored when setState is called from
895
// componentWillMount. Either fix it or disallow doing so completely in
896
// favor of getInitialState.
897
ReactUpdates.enqueueUpdate(this, callback);
898
}
899
},
900
901
/**
902
* Filters the context object to only contain keys specified in
903
* `contextTypes`, and asserts that they are valid.
904
*
905
* @param {object} context
906
* @return {?object}
907
* @private
908
*/
909
_processContext: function(context) {
910
var maskedContext = null;
911
var contextTypes = this.constructor.contextTypes;
912
if (contextTypes) {
913
maskedContext = {};
914
for (var contextName in contextTypes) {
915
maskedContext[contextName] = context[contextName];
916
}
917
if (__DEV__) {
918
this._checkPropTypes(
919
contextTypes,
920
maskedContext,
921
ReactPropTypeLocations.context
922
);
923
}
924
}
925
return maskedContext;
926
},
927
928
/**
929
* @param {object} currentContext
930
* @return {object}
931
* @private
932
*/
933
_processChildContext: function(currentContext) {
934
var childContext = this.getChildContext && this.getChildContext();
935
var displayName = this.constructor.displayName || 'ReactCompositeComponent';
936
if (childContext) {
937
invariant(
938
typeof this.constructor.childContextTypes === 'object',
939
'%s.getChildContext(): childContextTypes must be defined in order to ' +
940
'use getChildContext().',
941
displayName
942
);
943
if (__DEV__) {
944
this._checkPropTypes(
945
this.constructor.childContextTypes,
946
childContext,
947
ReactPropTypeLocations.childContext
948
);
949
}
950
for (var name in childContext) {
951
invariant(
952
name in this.constructor.childContextTypes,
953
'%s.getChildContext(): key "%s" is not defined in childContextTypes.',
954
displayName,
955
name
956
);
957
}
958
return assign({}, currentContext, childContext);
959
}
960
return currentContext;
961
},
962
963
/**
964
* Processes props by setting default values for unspecified props and
965
* asserting that the props are valid. Does not mutate its argument; returns
966
* a new props object with defaults merged in.
967
*
968
* @param {object} newProps
969
* @return {object}
970
* @private
971
*/
972
_processProps: function(newProps) {
973
if (__DEV__) {
974
var propTypes = this.constructor.propTypes;
975
if (propTypes) {
976
this._checkPropTypes(propTypes, newProps, ReactPropTypeLocations.prop);
977
}
978
}
979
return newProps;
980
},
981
982
/**
983
* Assert that the props are valid
984
*
985
* @param {object} propTypes Map of prop name to a ReactPropType
986
* @param {object} props
987
* @param {string} location e.g. "prop", "context", "child context"
988
* @private
989
*/
990
_checkPropTypes: function(propTypes, props, location) {
991
// TODO: Stop validating prop types here and only use the element
992
// validation.
993
var componentName = this.constructor.displayName;
994
for (var propName in propTypes) {
995
if (propTypes.hasOwnProperty(propName)) {
996
var error =
997
propTypes[propName](props, propName, componentName, location);
998
if (error instanceof Error) {
999
// We may want to extend this logic for similar errors in
1000
// renderComponent calls, so I'm abstracting it away into
1001
// a function to minimize refactoring in the future
1002
var addendum = getDeclarationErrorAddendum(this);
1003
warning(false, error.message + addendum);
1004
}
1005
}
1006
}
1007
},
1008
1009
/**
1010
* If any of `_pendingElement`, `_pendingState`, or `_pendingForceUpdate`
1011
* is set, update the component.
1012
*
1013
* @param {ReactReconcileTransaction} transaction
1014
* @internal
1015
*/
1016
performUpdateIfNecessary: function(transaction) {
1017
var compositeLifeCycleState = this._compositeLifeCycleState;
1018
// Do not trigger a state transition if we are in the middle of mounting or
1019
// receiving props because both of those will already be doing this.
1020
if (compositeLifeCycleState === CompositeLifeCycle.MOUNTING ||
1021
compositeLifeCycleState === CompositeLifeCycle.RECEIVING_PROPS) {
1022
return;
1023
}
1024
1025
if (this._pendingElement == null &&
1026
this._pendingState == null &&
1027
!this._pendingForceUpdate) {
1028
return;
1029
}
1030
1031
var nextContext = this.context;
1032
var nextProps = this.props;
1033
var nextElement = this._currentElement;
1034
if (this._pendingElement != null) {
1035
nextElement = this._pendingElement;
1036
nextContext = this._processContext(nextElement._context);
1037
nextProps = this._processProps(nextElement.props);
1038
this._pendingElement = null;
1039
1040
this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS;
1041
if (this.componentWillReceiveProps) {
1042
this.componentWillReceiveProps(nextProps, nextContext);
1043
}
1044
}
1045
1046
this._compositeLifeCycleState = null;
1047
1048
var nextState = this._pendingState || this.state;
1049
this._pendingState = null;
1050
1051
var shouldUpdate =
1052
this._pendingForceUpdate ||
1053
!this.shouldComponentUpdate ||
1054
this.shouldComponentUpdate(nextProps, nextState, nextContext);
1055
1056
if (__DEV__) {
1057
if (typeof shouldUpdate === "undefined") {
1058
console.warn(
1059
(this.constructor.displayName || 'ReactCompositeComponent') +
1060
'.shouldComponentUpdate(): Returned undefined instead of a ' +
1061
'boolean value. Make sure to return true or false.'
1062
);
1063
}
1064
}
1065
1066
if (shouldUpdate) {
1067
this._pendingForceUpdate = false;
1068
// Will set `this.props`, `this.state` and `this.context`.
1069
this._performComponentUpdate(
1070
nextElement,
1071
nextProps,
1072
nextState,
1073
nextContext,
1074
transaction
1075
);
1076
} else {
1077
// If it's determined that a component should not update, we still want
1078
// to set props and state.
1079
this._currentElement = nextElement;
1080
this.props = nextProps;
1081
this.state = nextState;
1082
this.context = nextContext;
1083
1084
// Owner cannot change because shouldUpdateReactComponent doesn't allow
1085
// it. TODO: Remove this._owner completely.
1086
this._owner = nextElement._owner;
1087
}
1088
},
1089
1090
/**
1091
* Merges new props and state, notifies delegate methods of update and
1092
* performs update.
1093
*
1094
* @param {ReactElement} nextElement Next element
1095
* @param {object} nextProps Next public object to set as properties.
1096
* @param {?object} nextState Next object to set as state.
1097
* @param {?object} nextContext Next public object to set as context.
1098
* @param {ReactReconcileTransaction} transaction
1099
* @private
1100
*/
1101
_performComponentUpdate: function(
1102
nextElement,
1103
nextProps,
1104
nextState,
1105
nextContext,
1106
transaction
1107
) {
1108
var prevElement = this._currentElement;
1109
var prevProps = this.props;
1110
var prevState = this.state;
1111
var prevContext = this.context;
1112
1113
if (this.componentWillUpdate) {
1114
this.componentWillUpdate(nextProps, nextState, nextContext);
1115
}
1116
1117
this._currentElement = nextElement;
1118
this.props = nextProps;
1119
this.state = nextState;
1120
this.context = nextContext;
1121
1122
// Owner cannot change because shouldUpdateReactComponent doesn't allow
1123
// it. TODO: Remove this._owner completely.
1124
this._owner = nextElement._owner;
1125
1126
this.updateComponent(
1127
transaction,
1128
prevElement
1129
);
1130
1131
if (this.componentDidUpdate) {
1132
transaction.getReactMountReady().enqueue(
1133
this.componentDidUpdate.bind(this, prevProps, prevState, prevContext),
1134
this
1135
);
1136
}
1137
},
1138
1139
receiveComponent: function(nextElement, transaction) {
1140
if (nextElement === this._currentElement &&
1141
nextElement._owner != null) {
1142
// Since elements are immutable after the owner is rendered,
1143
// we can do a cheap identity compare here to determine if this is a
1144
// superfluous reconcile. It's possible for state to be mutable but such
1145
// change should trigger an update of the owner which would recreate
1146
// the element. We explicitly check for the existence of an owner since
1147
// it's possible for a element created outside a composite to be
1148
// deeply mutated and reused.
1149
return;
1150
}
1151
1152
ReactComponent.Mixin.receiveComponent.call(
1153
this,
1154
nextElement,
1155
transaction
1156
);
1157
},
1158
1159
/**
1160
* Updates the component's currently mounted DOM representation.
1161
*
1162
* By default, this implements React's rendering and reconciliation algorithm.
1163
* Sophisticated clients may wish to override this.
1164
*
1165
* @param {ReactReconcileTransaction} transaction
1166
* @param {ReactElement} prevElement
1167
* @internal
1168
* @overridable
1169
*/
1170
updateComponent: ReactPerf.measure(
1171
'ReactCompositeComponent',
1172
'updateComponent',
1173
function(transaction, prevParentElement) {
1174
ReactComponent.Mixin.updateComponent.call(
1175
this,
1176
transaction,
1177
prevParentElement
1178
);
1179
1180
var prevComponentInstance = this._renderedComponent;
1181
var prevElement = prevComponentInstance._currentElement;
1182
var nextElement = this._renderValidatedComponent();
1183
if (shouldUpdateReactComponent(prevElement, nextElement)) {
1184
prevComponentInstance.receiveComponent(nextElement, transaction);
1185
} else {
1186
// These two IDs are actually the same! But nothing should rely on that.
1187
var thisID = this._rootNodeID;
1188
var prevComponentID = prevComponentInstance._rootNodeID;
1189
prevComponentInstance.unmountComponent();
1190
this._renderedComponent = instantiateReactComponent(
1191
nextElement,
1192
this._currentElement.type
1193
);
1194
var nextMarkup = this._renderedComponent.mountComponent(
1195
thisID,
1196
transaction,
1197
this._mountDepth + 1
1198
);
1199
ReactComponent.BackendIDOperations.dangerouslyReplaceNodeWithMarkupByID(
1200
prevComponentID,
1201
nextMarkup
1202
);
1203
}
1204
}
1205
),
1206
1207
/**
1208
* Forces an update. This should only be invoked when it is known with
1209
* certainty that we are **not** in a DOM transaction.
1210
*
1211
* You may want to call this when you know that some deeper aspect of the
1212
* component's state has changed but `setState` was not called.
1213
*
1214
* This will not invoke `shouldUpdateComponent`, but it will invoke
1215
* `componentWillUpdate` and `componentDidUpdate`.
1216
*
1217
* @param {?function} callback Called after update is complete.
1218
* @final
1219
* @protected
1220
*/
1221
forceUpdate: function(callback) {
1222
var compositeLifeCycleState = this._compositeLifeCycleState;
1223
invariant(
1224
this.isMounted() ||
1225
compositeLifeCycleState === CompositeLifeCycle.MOUNTING,
1226
'forceUpdate(...): Can only force an update on mounted or mounting ' +
1227
'components.'
1228
);
1229
invariant(
1230
compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING &&
1231
ReactCurrentOwner.current == null,
1232
'forceUpdate(...): Cannot force an update while unmounting component ' +
1233
'or within a `render` function.'
1234
);
1235
this._pendingForceUpdate = true;
1236
ReactUpdates.enqueueUpdate(this, callback);
1237
},
1238
1239
/**
1240
* @private
1241
*/
1242
_renderValidatedComponent: ReactPerf.measure(
1243
'ReactCompositeComponent',
1244
'_renderValidatedComponent',
1245
function() {
1246
var renderedComponent;
1247
var previousContext = ReactContext.current;
1248
ReactContext.current = this._processChildContext(
1249
this._currentElement._context
1250
);
1251
ReactCurrentOwner.current = this;
1252
try {
1253
renderedComponent = this.render();
1254
if (renderedComponent === null || renderedComponent === false) {
1255
renderedComponent = ReactEmptyComponent.getEmptyComponent();
1256
ReactEmptyComponent.registerNullComponentID(this._rootNodeID);
1257
} else {
1258
ReactEmptyComponent.deregisterNullComponentID(this._rootNodeID);
1259
}
1260
} finally {
1261
ReactContext.current = previousContext;
1262
ReactCurrentOwner.current = null;
1263
}
1264
invariant(
1265
ReactElement.isValidElement(renderedComponent),
1266
'%s.render(): A valid ReactComponent must be returned. You may have ' +
1267
'returned undefined, an array or some other invalid object.',
1268
this.constructor.displayName || 'ReactCompositeComponent'
1269
);
1270
return renderedComponent;
1271
}
1272
),
1273
1274
/**
1275
* @private
1276
*/
1277
_bindAutoBindMethods: function() {
1278
for (var autoBindKey in this.__reactAutoBindMap) {
1279
if (!this.__reactAutoBindMap.hasOwnProperty(autoBindKey)) {
1280
continue;
1281
}
1282
var method = this.__reactAutoBindMap[autoBindKey];
1283
this[autoBindKey] = this._bindAutoBindMethod(ReactErrorUtils.guard(
1284
method,
1285
this.constructor.displayName + '.' + autoBindKey
1286
));
1287
}
1288
},
1289
1290
/**
1291
* Binds a method to the component.
1292
*
1293
* @param {function} method Method to be bound.
1294
* @private
1295
*/
1296
_bindAutoBindMethod: function(method) {
1297
var component = this;
1298
var boundMethod = method.bind(component);
1299
if (__DEV__) {
1300
boundMethod.__reactBoundContext = component;
1301
boundMethod.__reactBoundMethod = method;
1302
boundMethod.__reactBoundArguments = null;
1303
var componentName = component.constructor.displayName;
1304
var _bind = boundMethod.bind;
1305
boundMethod.bind = function(newThis, ...args) {
1306
// User is trying to bind() an autobound method; we effectively will
1307
// ignore the value of "this" that the user is trying to use, so
1308
// let's warn.
1309
if (newThis !== component && newThis !== null) {
1310
monitorCodeUse('react_bind_warning', { component: componentName });
1311
console.warn(
1312
'bind(): React component methods may only be bound to the ' +
1313
'component instance. See ' + componentName
1314
);
1315
} else if (!args.length) {
1316
monitorCodeUse('react_bind_warning', { component: componentName });
1317
console.warn(
1318
'bind(): You are binding a component method to the component. ' +
1319
'React does this for you automatically in a high-performance ' +
1320
'way, so you can safely remove this call. See ' + componentName
1321
);
1322
return boundMethod;
1323
}
1324
var reboundMethod = _bind.apply(boundMethod, arguments);
1325
reboundMethod.__reactBoundContext = component;
1326
reboundMethod.__reactBoundMethod = method;
1327
reboundMethod.__reactBoundArguments = args;
1328
return reboundMethod;
1329
};
1330
}
1331
return boundMethod;
1332
}
1333
};
1334
1335
var ReactCompositeComponentBase = function() {};
1336
assign(
1337
ReactCompositeComponentBase.prototype,
1338
ReactComponent.Mixin,
1339
ReactOwner.Mixin,
1340
ReactPropTransferer.Mixin,
1341
ReactCompositeComponentMixin
1342
);
1343
1344
/**
1345
* Module for creating composite components.
1346
*
1347
* @class ReactCompositeComponent
1348
* @extends ReactComponent
1349
* @extends ReactOwner
1350
* @extends ReactPropTransferer
1351
*/
1352
var ReactCompositeComponent = {
1353
1354
LifeCycle: CompositeLifeCycle,
1355
1356
Base: ReactCompositeComponentBase,
1357
1358
/**
1359
* Creates a composite component class given a class specification.
1360
*
1361
* @param {object} spec Class specification (which must define `render`).
1362
* @return {function} Component constructor function.
1363
* @public
1364
*/
1365
createClass: function(spec) {
1366
var Constructor = function(props) {
1367
// This constructor is overridden by mocks. The argument is used
1368
// by mocks to assert on what gets mounted. This will later be used
1369
// by the stand-alone class implementation.
1370
};
1371
Constructor.prototype = new ReactCompositeComponentBase();
1372
Constructor.prototype.constructor = Constructor;
1373
1374
injectedMixins.forEach(
1375
mixSpecIntoComponent.bind(null, Constructor)
1376
);
1377
1378
mixSpecIntoComponent(Constructor, spec);
1379
1380
// Initialize the defaultProps property after all mixins have been merged
1381
if (Constructor.getDefaultProps) {
1382
Constructor.defaultProps = Constructor.getDefaultProps();
1383
}
1384
1385
invariant(
1386
Constructor.prototype.render,
1387
'createClass(...): Class specification must implement a `render` method.'
1388
);
1389
1390
if (__DEV__) {
1391
if (Constructor.prototype.componentShouldUpdate) {
1392
monitorCodeUse(
1393
'react_component_should_update_warning',
1394
{ component: spec.displayName }
1395
);
1396
console.warn(
1397
(spec.displayName || 'A component') + ' has a method called ' +
1398
'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' +
1399
'The name is phrased as a question because the function is ' +
1400
'expected to return a value.'
1401
);
1402
}
1403
}
1404
1405
// Reduce time spent doing lookups by setting these on the prototype.
1406
for (var methodName in ReactCompositeComponentInterface) {
1407
if (!Constructor.prototype[methodName]) {
1408
Constructor.prototype[methodName] = null;
1409
}
1410
}
1411
1412
if (__DEV__) {
1413
return ReactLegacyElement.wrapFactory(
1414
ReactElementValidator.createFactory(Constructor)
1415
);
1416
}
1417
return ReactLegacyElement.wrapFactory(
1418
ReactElement.createFactory(Constructor)
1419
);
1420
},
1421
1422
injection: {
1423
injectMixin: function(mixin) {
1424
injectedMixins.push(mixin);
1425
}
1426
}
1427
};
1428
1429
module.exports = ReactCompositeComponent;
1430
1431