Thread native-parent pointers through everything
Now we don't repurpose context for our own secret needs (hi Dan). In this diff I avoid storing the native parent on native (DOM) components and store it only on composites, but we'll probably want to store it on native components too soon for event bubbling.
Ben Alpert committed
Sep 30, 2015 at 14:42 UTC
1dca72ebfa97030292aed360a30cf59ef8c4c065
16 files changed
+159
-113
src/renderers/dom/client/ReactMount.js
+7
-25
@@ -15,7 +15,7 @@ var ClientReactRootIndex = require('ClientReactRootIndex');
15
var DOMProperty = require('DOMProperty');
16
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
17
var ReactCurrentOwner = require('ReactCurrentOwner');
18
-var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
18
+var ReactDOMContainerInfo = require('ReactDOMContainerInfo');
19
var ReactElement = require('ReactElement');
20
var ReactEmptyComponentRegistry = require('ReactEmptyComponentRegistry');
21
var ReactInstanceHandles = require('ReactInstanceHandles');
@@ -26,14 +26,12 @@ var ReactReconciler = require('ReactReconciler');
26
var ReactUpdateQueue = require('ReactUpdateQueue');
27
var ReactUpdates = require('ReactUpdates');
28
29
-var assign = require('Object.assign');
29
var emptyObject = require('emptyObject');
30
var containsNode = require('containsNode');
31
var instantiateReactComponent = require('instantiateReactComponent');
32
var invariant = require('invariant');
33
var setInnerHTML = require('setInnerHTML');
34
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
36
-var validateDOMNesting = require('validateDOMNesting');
35
var warning = require('warning');
36
37
var ATTR_NAME = DOMProperty.ID_ATTRIBUTE_NAME;
@@ -43,9 +41,6 @@ var ELEMENT_NODE_TYPE = 1;
41
var DOC_NODE_TYPE = 9;
42
var DOCUMENT_FRAGMENT_NODE_TYPE = 11;
43
46
-var ownerDocumentContextKey =
47
- '__ReactMount_ownerDocument$' + Math.random().toString(36).slice(2);
48
-
44
45
/** Mapping from reactRootID to React component instance. */
46
var instancesByReactRootID = {};
@@ -279,24 +274,13 @@ function mountComponentIntoNode(
274
shouldReuseMarkup,
275
context
276
) {
282
- if (ReactDOMFeatureFlags.useCreateElement) {
283
- context = assign({}, context);
284
- if (container.nodeType === DOC_NODE_TYPE) {
285
- context[ownerDocumentContextKey] = container;
286
- } else {
287
- context[ownerDocumentContextKey] = container.ownerDocument;
288
- }
289
- }
290
- if (__DEV__) {
291
- if (context === emptyObject) {
292
- context = {};
293
- }
294
- var tag = container.nodeName.toLowerCase();
295
- context[validateDOMNesting.ancestorInfoContextKey] =
296
- validateDOMNesting.updatedAncestorInfo(null, tag, null);
297
- }
277
var markup = ReactReconciler.mountComponent(
299
- componentInstance, rootID, transaction, context
278
+ componentInstance,
279
+ rootID,
280
+ transaction,
281
+ null,
282
+ ReactDOMContainerInfo(container),
283
+ context
284
);
285
componentInstance._renderedComponent._topLevelWrapper = componentInstance;
286
ReactMount._mountImageIntoNode(
@@ -1081,8 +1065,6 @@ var ReactMount = {
1065
}
1066
},
1067
1084
- ownerDocumentContextKey: ownerDocumentContextKey,
1085
-
1068
/**
1069
* React ID utilities.
1070
*/
src/renderers/dom/client/validateDOMNesting.js
-3
@@ -412,9 +412,6 @@ if (__DEV__) {
412
}
413
};
414
415
- validateDOMNesting.ancestorInfoContextKey =
416
- '__validateDOMNesting_ancestorInfo$' + Math.random().toString(36).slice(2);
417
-
415
validateDOMNesting.updatedAncestorInfo = updatedAncestorInfo;
416
417
// For testing
src/renderers/dom/client/wrappers/ReactDOMButton.js
+1
-1
@@ -30,7 +30,7 @@ var mouseListenerNames = {
30
* when `disabled` is set.
31
*/
32
var ReactDOMButton = {
33
- getNativeProps: function(inst, props, context) {
33
+ getNativeProps: function(inst, props) {
34
if (!props.disabled) {
35
return props;
36
}
src/renderers/dom/client/wrappers/ReactDOMInput.js
+1
-1
@@ -49,7 +49,7 @@ function forceUpdateIfMounted() {
49
* @see http://www.w3.org/TR/2012/WD-html5-20121025/the-input-element.html
50
*/
51
var ReactDOMInput = {
52
- getNativeProps: function(inst, props, context) {
52
+ getNativeProps: function(inst, props) {
53
var value = LinkedValueUtils.getValue(props);
54
var checked = LinkedValueUtils.getChecked(props);
55
src/renderers/dom/client/wrappers/ReactDOMOption.js
+8
-7
@@ -17,13 +17,11 @@ var ReactDOMSelect = require('ReactDOMSelect');
17
var assign = require('Object.assign');
18
var warning = require('warning');
19
20
-var valueContextKey = ReactDOMSelect.valueContextKey;
21
-
20
/**
21
* Implements an <option> native component that warns when `selected` is set.
22
*/
23
var ReactDOMOption = {
26
- mountWrapper: function(inst, props, context) {
24
+ mountWrapper: function(inst, props, nativeParent) {
25
// TODO (yungsters): Remove support for `selected` in <option>.
26
if (__DEV__) {
27
warning(
@@ -33,10 +31,13 @@ var ReactDOMOption = {
31
);
32
}
33
36
- // Look up whether this option is 'selected' via context
37
- var selectValue = context[valueContextKey];
34
+ // Look up whether this option is 'selected'
35
+ var selectValue = null;
36
+ if (nativeParent != null && nativeParent._tag === 'select') {
37
+ selectValue = ReactDOMSelect.getSelectValueContext(nativeParent);
38
+ }
39
39
- // If context key is null (e.g., no specified value or after initial mount)
40
+ // If the value is null (e.g., no specified value or after initial mount)
41
// or missing (e.g., for <datalist>), we don't change props.selected
42
var selected = null;
43
if (selectValue != null) {
@@ -57,7 +58,7 @@ var ReactDOMOption = {
58
inst._wrapperState = {selected: selected};
59
},
60
60
- getNativeProps: function(inst, props, context) {
61
+ getNativeProps: function(inst, props) {
62
var nativeProps = assign({selected: undefined, children: undefined}, props);
63
64
// Read state only from initial mount because <select> updates value
src/renderers/dom/client/wrappers/ReactDOMSelect.js
+6
-13
@@ -20,9 +20,6 @@ var warning = require('warning');
20
21
var didWarnValueLink = false;
22
23
-var valueContextKey =
24
- '__ReactDOMSelect_value$' + Math.random().toString(36).slice(2);
25
-
23
function updateOptionsIfPendingUpdateAndMounted() {
24
if (this._rootNodeID && this._wrapperState.pendingUpdate) {
25
this._wrapperState.pendingUpdate = false;
@@ -146,9 +143,7 @@ function updateOptions(inst, multiple, propValue) {
143
* selected.
144
*/
145
var ReactDOMSelect = {
149
- valueContextKey: valueContextKey,
150
-
151
- getNativeProps: function(inst, props, context) {
146
+ getNativeProps: function(inst, props) {
147
return assign({}, props, {
148
onChange: inst._wrapperState.onChange,
149
value: undefined,
@@ -169,19 +164,17 @@ var ReactDOMSelect = {
164
};
165
},
166
172
- processChildContext: function(inst, props, context) {
173
- // Pass down initial value so initial generated markup has correct
174
- // `selected` attributes
175
- var childContext = assign({}, context);
176
- childContext[valueContextKey] = inst._wrapperState.initialValue;
177
- return childContext;
167
+ getSelectValueContext: function(inst) {
168
+ // ReactDOMOption looks at this initial value so the initial generated
169
+ // markup has correct `selected` attributes
170
+ return inst._wrapperState.initialValue;
171
},
172
173
postUpdateWrapper: function(inst) {
174
var props = inst._currentElement.props;
175
176
// After the initial mount, we control selected-ness manually so don't pass
184
- // the context value down
177
+ // this value down
178
inst._wrapperState.initialValue = undefined;
179
180
var wasMultiple = inst._wrapperState.wasMultiple;
src/renderers/dom/client/wrappers/ReactDOMTextarea.js
+1
-1
@@ -44,7 +44,7 @@ function forceUpdateIfMounted() {
44
* `defaultValue` if specified, or the children content (deprecated).
45
*/
46
var ReactDOMTextarea = {
47
- getNativeProps: function(inst, props, context) {
47
+ getNativeProps: function(inst, props) {
48
invariant(
49
props.dangerouslySetInnerHTML == null,
50
'`dangerouslySetInnerHTML` does not make sense on <textarea>.'
src/renderers/dom/server/ReactServerRendering.js
+7
-2
@@ -46,8 +46,13 @@ function renderToStringImpl(element, makeStaticMarkup) {
46
47
return transaction.perform(function() {
48
var componentInstance = instantiateReactComponent(element, null);
49
- var markup =
50
- componentInstance.mountComponent(id, transaction, emptyObject);
49
+ var markup = componentInstance.mountComponent(
50
+ id,
51
+ transaction,
52
+ null,
53
+ null,
54
+ emptyObject
55
+ );
56
if (!makeStaticMarkup) {
57
markup = ReactMarkupChecksum.addChecksumToMarkup(markup);
58
}
src/renderers/dom/shared/ReactDOMComponent.js
+34
-47
@@ -474,15 +474,6 @@ function validateDangerousTag(tag) {
474
}
475
}
476
477
-function processChildContextDev(context, inst) {
478
- // Pass down our tag name to child components for validation purposes
479
- context = assign({}, context);
480
- var info = context[validateDOMNesting.ancestorInfoContextKey];
481
- context[validateDOMNesting.ancestorInfoContextKey] =
482
- validateDOMNesting.updatedAncestorInfo(info, inst._tag, inst);
483
- return context;
484
-}
485
-
477
function isCustomComponent(tagName, props) {
478
return tagName.indexOf('-') >= 0 || props.is != null;
479
}
@@ -517,12 +508,12 @@ function ReactDOMComponent(tag) {
508
this._previousStyleCopy = null;
509
this._nativeNode = null;
510
this._rootNodeID = null;
511
+ this._nativeContainerInfo = null;
512
this._wrapperState = null;
513
this._topLevelWrapper = null;
514
this._nodeHasLegacyProperties = false;
515
if (__DEV__) {
524
- this._unprocessedContextDev = null;
525
- this._processedContextDev = null;
516
+ this._ancestorInfo = null;
517
}
518
}
519
@@ -541,11 +532,20 @@ ReactDOMComponent.Mixin = {
532
* @internal
533
* @param {string} rootID The root DOM ID for this node.
534
* @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
535
+ * @param {?ReactDOMComponent} the containing DOM component instance
536
+ * @param {?object} info about the native container
537
* @param {object} context
538
* @return {string} The computed markup.
539
*/
547
- mountComponent: function(rootID, transaction, context) {
540
+ mountComponent: function(
541
+ rootID,
542
+ transaction,
543
+ nativeParent,
544
+ nativeContainerInfo,
545
+ context
546
+ ) {
547
this._rootNodeID = rootID;
548
+ this._nativeContainerInfo = nativeContainerInfo;
549
550
var props = this._currentElement.props;
551
@@ -561,47 +561,46 @@ ReactDOMComponent.Mixin = {
561
transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
562
break;
563
case 'button':
564
- props = ReactDOMButton.getNativeProps(this, props, context);
564
+ props = ReactDOMButton.getNativeProps(this, props, nativeParent);
565
break;
566
case 'input':
567
- ReactDOMInput.mountWrapper(this, props, context);
568
- props = ReactDOMInput.getNativeProps(this, props, context);
567
+ ReactDOMInput.mountWrapper(this, props, nativeParent);
568
+ props = ReactDOMInput.getNativeProps(this, props);
569
break;
570
case 'option':
571
- ReactDOMOption.mountWrapper(this, props, context);
572
- props = ReactDOMOption.getNativeProps(this, props, context);
571
+ ReactDOMOption.mountWrapper(this, props, nativeParent);
572
+ props = ReactDOMOption.getNativeProps(this, props);
573
break;
574
case 'select':
575
- ReactDOMSelect.mountWrapper(this, props, context);
576
- props = ReactDOMSelect.getNativeProps(this, props, context);
577
- context = ReactDOMSelect.processChildContext(this, props, context);
575
+ ReactDOMSelect.mountWrapper(this, props, nativeParent);
576
+ props = ReactDOMSelect.getNativeProps(this, props);
577
break;
578
case 'textarea':
580
- ReactDOMTextarea.mountWrapper(this, props, context);
581
- props = ReactDOMTextarea.getNativeProps(this, props, context);
579
+ ReactDOMTextarea.mountWrapper(this, props, nativeParent);
580
+ props = ReactDOMTextarea.getNativeProps(this, props);
581
break;
582
}
583
584
assertValidProps(this, props);
585
if (__DEV__) {
587
- if (context[validateDOMNesting.ancestorInfoContextKey]) {
588
- validateDOMNesting(
589
- this._tag,
590
- this,
591
- context[validateDOMNesting.ancestorInfoContextKey]
592
- );
586
+ var parentInfo;
587
+ if (nativeParent != null) {
588
+ parentInfo = nativeParent._ancestorInfo;
589
+ } else if (nativeContainerInfo != null) {
590
+ parentInfo = nativeContainerInfo._ancestorInfo;
591
}
594
- }
595
-
596
- if (__DEV__) {
597
- this._unprocessedContextDev = context;
598
- this._processedContextDev = processChildContextDev(context, this);
599
- context = this._processedContextDev;
592
+ if (parentInfo) {
593
+ // parentInfo should always be present except for the top-level
594
+ // component when server rendering
595
+ validateDOMNesting(this._tag, this, parentInfo);
596
+ }
597
+ this._ancestorInfo =
598
+ validateDOMNesting.updatedAncestorInfo(parentInfo, this._tag, this);
599
}
600
601
var mountImage;
602
if (transaction.useCreateElement) {
604
- var ownerDocument = context[ReactMount.ownerDocumentContextKey];
603
+ var ownerDocument = nativeContainerInfo._ownerDocument;
604
var el = ownerDocument.createElement(this._currentElement.type);
605
this._nativeNode = el;
606
DOMPropertyOperations.setAttributeForID(el, this._rootNodeID);
@@ -835,18 +834,6 @@ ReactDOMComponent.Mixin = {
834
break;
835
}
836
838
- if (__DEV__) {
839
- // If the context is reference-equal to the old one, pass down the same
840
- // processed object so the update bailout in ReactReconciler behaves
841
- // correctly (and identically in dev and prod). See #5005.
842
- if (this._unprocessedContextDev !== context) {
843
- this._unprocessedContextDev = context;
844
- this._processedContextDev = processChildContextDev(context, this);
845
- }
846
- context = this._processedContextDev;
847
- }
848
-
849
-
837
assertValidProps(this, nextProps);
838
this._updateDOMProperties(lastProps, nextProps, transaction);
839
this._updateDOMChildren(
src/renderers/dom/shared/ReactDOMContainerInfo.js
new
+30
@@ -0,0 +1,30 @@
1
+/**
2
+ * Copyright 2013-2015, 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 ReactDOMContainerInfo
10
+ */
11
+
12
+'use strict';
13
+
14
+var validateDOMNesting = require('validateDOMNesting');
15
+
16
+var DOC_NODE_TYPE = 9;
17
+
18
+function ReactDOMContainerInfo(node) {
19
+ var info = {
20
+ _ownerDocument: node.nodeType === DOC_NODE_TYPE ? node : node.ownerDocument,
21
+ };
22
+ if (__DEV__) {
23
+ var tag = node.nodeName.toLowerCase();
24
+ info._ancestorInfo =
25
+ validateDOMNesting.updatedAncestorInfo(null, tag, null);
26
+ }
27
+ return info;
28
+}
29
+
30
+module.exports = ReactDOMContainerInfo;
src/renderers/dom/shared/ReactDOMTextComponent.js
+18
-8
@@ -68,20 +68,30 @@ assign(ReactDOMTextComponent.prototype, {
68
* @return {string} Markup for this text node.
69
* @internal
70
*/
71
- mountComponent: function(rootID, transaction, context) {
71
+ mountComponent: function(
72
+ rootID,
73
+ transaction,
74
+ nativeParent,
75
+ nativeContainerInfo,
76
+ context
77
+ ) {
78
if (__DEV__) {
73
- if (context[validateDOMNesting.ancestorInfoContextKey]) {
74
- validateDOMNesting(
75
- 'span',
76
- null,
77
- context[validateDOMNesting.ancestorInfoContextKey]
78
- );
79
+ var parentInfo;
80
+ if (nativeParent != null) {
81
+ parentInfo = nativeParent._ancestorInfo;
82
+ } else if (nativeContainerInfo != null) {
83
+ parentInfo = nativeContainerInfo._ancestorInfo;
84
+ }
85
+ if (parentInfo) {
86
+ // parentInfo should always be present except for the top-level
87
+ // component when server rendering
88
+ validateDOMNesting(this._tag, this, parentInfo);
89
}
90
}
91
92
this._rootNodeID = rootID;
93
if (transaction.useCreateElement) {
84
- var ownerDocument = context[ReactMount.ownerDocumentContextKey];
94
+ var ownerDocument = nativeContainerInfo._ownerDocument;
95
var el = ownerDocument.createElement('span');
96
this._nativeNode = el;
97
DOMPropertyOperations.setAttributeForID(el, rootID);
src/renderers/shared/reconciler/ReactCompositeComponent.js
+15
-1
@@ -96,6 +96,8 @@ var ReactCompositeComponentMixin = {
96
this._currentElement = element;
97
this._rootNodeID = null;
98
this._instance = null;
99
+ this._nativeParent = null;
100
+ this._nativeContainerInfo = null;
101
102
// See ReactUpdateQueue
103
this._pendingElement = null;
@@ -122,10 +124,18 @@ var ReactCompositeComponentMixin = {
124
* @final
125
* @internal
126
*/
125
- mountComponent: function(rootID, transaction, context) {
127
+ mountComponent: function(
128
+ rootID,
129
+ transaction,
130
+ nativeParent,
131
+ nativeContainerInfo,
132
+ context
133
+ ) {
134
this._context = context;
135
this._mountOrder = nextMountID++;
136
this._rootNodeID = rootID;
137
+ this._nativeParent = nativeParent;
138
+ this._nativeContainerInfo = nativeContainerInfo;
139
140
var publicProps = this._processProps(this._currentElement.props);
141
var publicContext = this._processContext(context);
@@ -288,6 +298,8 @@ var ReactCompositeComponentMixin = {
298
this._renderedComponent,
299
rootID,
300
transaction,
301
+ nativeParent,
302
+ nativeContainerInfo,
303
this._processChildContext(context)
304
);
305
if (inst.componentDidMount) {
@@ -739,6 +751,8 @@ var ReactCompositeComponentMixin = {
751
this._renderedComponent,
752
this._rootNodeID,
753
transaction,
754
+ this._nativeParent,
755
+ this._nativeContainerInfo,
756
this._processChildContext(context)
757
);
758
this._replaceNodeWithMarkup(oldNativeNode, nextMarkup);
src/renderers/shared/reconciler/ReactEmptyComponent.js
+9
-1
@@ -33,13 +33,21 @@ var ReactEmptyComponent = function(instantiate) {
33
assign(ReactEmptyComponent.prototype, {
34
construct: function(element) {
35
},
36
- mountComponent: function(rootID, transaction, context) {
36
+ mountComponent: function(
37
+ rootID,
38
+ transaction,
39
+ nativeParent,
40
+ nativeContainerInfo,
41
+ context
42
+ ) {
43
ReactEmptyComponentRegistry.registerNullComponentID(rootID);
44
this._rootNodeID = rootID;
45
return ReactReconciler.mountComponent(
46
this._renderedComponent,
47
rootID,
48
transaction,
49
+ nativeParent,
50
+ nativeContainerInfo,
51
context
52
);
53
},
src/renderers/shared/reconciler/ReactMultiChild.js
+4
@@ -255,6 +255,8 @@ var ReactMultiChild = {
255
child,
256
rootID,
257
transaction,
258
+ this,
259
+ this._nativeContainerInfo,
260
context
261
);
262
child._mountIndex = index++;
@@ -503,6 +505,8 @@ var ReactMultiChild = {
505
child,
506
rootID,
507
transaction,
508
+ this,
509
+ this._nativeContainerInfo,
510
context
511
);
512
child._mountIndex = index;
src/renderers/shared/reconciler/ReactReconciler.js
+17
-2
@@ -29,12 +29,27 @@ var ReactReconciler = {
29
* @param {ReactComponent} internalInstance
30
* @param {string} rootID DOM ID of the root node.
31
* @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
32
+ * @param {?object} the containing native component instance
33
+ * @param {?object} info about the native container
34
* @return {?string} Rendered markup to be inserted into the DOM.
35
* @final
36
* @internal
37
*/
36
- mountComponent: function(internalInstance, rootID, transaction, context) {
37
- var markup = internalInstance.mountComponent(rootID, transaction, context);
38
+ mountComponent: function(
39
+ internalInstance,
40
+ rootID,
41
+ transaction,
42
+ nativeParent,
43
+ nativeContainerInfo,
44
+ context
45
+ ) {
46
+ var markup = internalInstance.mountComponent(
47
+ rootID,
48
+ transaction,
49
+ nativeParent,
50
+ nativeContainerInfo,
51
+ context
52
+ );
53
if (internalInstance._currentElement &&
54
internalInstance._currentElement.ref != null) {
55
transaction.getReactMountReady().enqueue(attachRefs, internalInstance);
src/test/ReactTestUtils.js
+1
-1
@@ -446,7 +446,7 @@ ReactShallowRenderer.prototype._render = function(element, transaction, context)
446
var instance = new ShallowComponentWrapper(element.type);
447
instance.construct(element);
448
449
- instance.mountComponent(rootID, transaction, context);
449
+ instance.mountComponent(rootID, transaction, null, null, context);
450
451
this._instance = instance;
452
}