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.
This commit is contained in:
Ben Alpert
2015-09-30 14:42:46 -07:00
parent a907da9430
commit 1dca72ebfa
16 changed files with 159 additions and 113 deletions
+7 -25
View File
@@ -15,7 +15,7 @@ var ClientReactRootIndex = require('ClientReactRootIndex');
var DOMProperty = require('DOMProperty');
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
var ReactCurrentOwner = require('ReactCurrentOwner');
var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
var ReactDOMContainerInfo = require('ReactDOMContainerInfo');
var ReactElement = require('ReactElement');
var ReactEmptyComponentRegistry = require('ReactEmptyComponentRegistry');
var ReactInstanceHandles = require('ReactInstanceHandles');
@@ -26,14 +26,12 @@ var ReactReconciler = require('ReactReconciler');
var ReactUpdateQueue = require('ReactUpdateQueue');
var ReactUpdates = require('ReactUpdates');
var assign = require('Object.assign');
var emptyObject = require('emptyObject');
var containsNode = require('containsNode');
var instantiateReactComponent = require('instantiateReactComponent');
var invariant = require('invariant');
var setInnerHTML = require('setInnerHTML');
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
var validateDOMNesting = require('validateDOMNesting');
var warning = require('warning');
var ATTR_NAME = DOMProperty.ID_ATTRIBUTE_NAME;
@@ -43,9 +41,6 @@ var ELEMENT_NODE_TYPE = 1;
var DOC_NODE_TYPE = 9;
var DOCUMENT_FRAGMENT_NODE_TYPE = 11;
var ownerDocumentContextKey =
'__ReactMount_ownerDocument$' + Math.random().toString(36).slice(2);
/** Mapping from reactRootID to React component instance. */
var instancesByReactRootID = {};
@@ -279,24 +274,13 @@ function mountComponentIntoNode(
shouldReuseMarkup,
context
) {
if (ReactDOMFeatureFlags.useCreateElement) {
context = assign({}, context);
if (container.nodeType === DOC_NODE_TYPE) {
context[ownerDocumentContextKey] = container;
} else {
context[ownerDocumentContextKey] = container.ownerDocument;
}
}
if (__DEV__) {
if (context === emptyObject) {
context = {};
}
var tag = container.nodeName.toLowerCase();
context[validateDOMNesting.ancestorInfoContextKey] =
validateDOMNesting.updatedAncestorInfo(null, tag, null);
}
var markup = ReactReconciler.mountComponent(
componentInstance, rootID, transaction, context
componentInstance,
rootID,
transaction,
null,
ReactDOMContainerInfo(container),
context
);
componentInstance._renderedComponent._topLevelWrapper = componentInstance;
ReactMount._mountImageIntoNode(
@@ -1081,8 +1065,6 @@ var ReactMount = {
}
},
ownerDocumentContextKey: ownerDocumentContextKey,
/**
* React ID utilities.
*/
@@ -412,9 +412,6 @@ if (__DEV__) {
}
};
validateDOMNesting.ancestorInfoContextKey =
'__validateDOMNesting_ancestorInfo$' + Math.random().toString(36).slice(2);
validateDOMNesting.updatedAncestorInfo = updatedAncestorInfo;
// For testing
@@ -30,7 +30,7 @@ var mouseListenerNames = {
* when `disabled` is set.
*/
var ReactDOMButton = {
getNativeProps: function(inst, props, context) {
getNativeProps: function(inst, props) {
if (!props.disabled) {
return props;
}
@@ -49,7 +49,7 @@ function forceUpdateIfMounted() {
* @see http://www.w3.org/TR/2012/WD-html5-20121025/the-input-element.html
*/
var ReactDOMInput = {
getNativeProps: function(inst, props, context) {
getNativeProps: function(inst, props) {
var value = LinkedValueUtils.getValue(props);
var checked = LinkedValueUtils.getChecked(props);
@@ -17,13 +17,11 @@ var ReactDOMSelect = require('ReactDOMSelect');
var assign = require('Object.assign');
var warning = require('warning');
var valueContextKey = ReactDOMSelect.valueContextKey;
/**
* Implements an <option> native component that warns when `selected` is set.
*/
var ReactDOMOption = {
mountWrapper: function(inst, props, context) {
mountWrapper: function(inst, props, nativeParent) {
// TODO (yungsters): Remove support for `selected` in <option>.
if (__DEV__) {
warning(
@@ -33,10 +31,13 @@ var ReactDOMOption = {
);
}
// Look up whether this option is 'selected' via context
var selectValue = context[valueContextKey];
// Look up whether this option is 'selected'
var selectValue = null;
if (nativeParent != null && nativeParent._tag === 'select') {
selectValue = ReactDOMSelect.getSelectValueContext(nativeParent);
}
// If context key is null (e.g., no specified value or after initial mount)
// If the value is null (e.g., no specified value or after initial mount)
// or missing (e.g., for <datalist>), we don't change props.selected
var selected = null;
if (selectValue != null) {
@@ -57,7 +58,7 @@ var ReactDOMOption = {
inst._wrapperState = {selected: selected};
},
getNativeProps: function(inst, props, context) {
getNativeProps: function(inst, props) {
var nativeProps = assign({selected: undefined, children: undefined}, props);
// Read state only from initial mount because <select> updates value
@@ -20,9 +20,6 @@ var warning = require('warning');
var didWarnValueLink = false;
var valueContextKey =
'__ReactDOMSelect_value$' + Math.random().toString(36).slice(2);
function updateOptionsIfPendingUpdateAndMounted() {
if (this._rootNodeID && this._wrapperState.pendingUpdate) {
this._wrapperState.pendingUpdate = false;
@@ -146,9 +143,7 @@ function updateOptions(inst, multiple, propValue) {
* selected.
*/
var ReactDOMSelect = {
valueContextKey: valueContextKey,
getNativeProps: function(inst, props, context) {
getNativeProps: function(inst, props) {
return assign({}, props, {
onChange: inst._wrapperState.onChange,
value: undefined,
@@ -169,19 +164,17 @@ var ReactDOMSelect = {
};
},
processChildContext: function(inst, props, context) {
// Pass down initial value so initial generated markup has correct
// `selected` attributes
var childContext = assign({}, context);
childContext[valueContextKey] = inst._wrapperState.initialValue;
return childContext;
getSelectValueContext: function(inst) {
// ReactDOMOption looks at this initial value so the initial generated
// markup has correct `selected` attributes
return inst._wrapperState.initialValue;
},
postUpdateWrapper: function(inst) {
var props = inst._currentElement.props;
// After the initial mount, we control selected-ness manually so don't pass
// the context value down
// this value down
inst._wrapperState.initialValue = undefined;
var wasMultiple = inst._wrapperState.wasMultiple;
@@ -44,7 +44,7 @@ function forceUpdateIfMounted() {
* `defaultValue` if specified, or the children content (deprecated).
*/
var ReactDOMTextarea = {
getNativeProps: function(inst, props, context) {
getNativeProps: function(inst, props) {
invariant(
props.dangerouslySetInnerHTML == null,
'`dangerouslySetInnerHTML` does not make sense on <textarea>.'
@@ -46,8 +46,13 @@ function renderToStringImpl(element, makeStaticMarkup) {
return transaction.perform(function() {
var componentInstance = instantiateReactComponent(element, null);
var markup =
componentInstance.mountComponent(id, transaction, emptyObject);
var markup = componentInstance.mountComponent(
id,
transaction,
null,
null,
emptyObject
);
if (!makeStaticMarkup) {
markup = ReactMarkupChecksum.addChecksumToMarkup(markup);
}
+34 -47
View File
@@ -474,15 +474,6 @@ function validateDangerousTag(tag) {
}
}
function processChildContextDev(context, inst) {
// Pass down our tag name to child components for validation purposes
context = assign({}, context);
var info = context[validateDOMNesting.ancestorInfoContextKey];
context[validateDOMNesting.ancestorInfoContextKey] =
validateDOMNesting.updatedAncestorInfo(info, inst._tag, inst);
return context;
}
function isCustomComponent(tagName, props) {
return tagName.indexOf('-') >= 0 || props.is != null;
}
@@ -517,12 +508,12 @@ function ReactDOMComponent(tag) {
this._previousStyleCopy = null;
this._nativeNode = null;
this._rootNodeID = null;
this._nativeContainerInfo = null;
this._wrapperState = null;
this._topLevelWrapper = null;
this._nodeHasLegacyProperties = false;
if (__DEV__) {
this._unprocessedContextDev = null;
this._processedContextDev = null;
this._ancestorInfo = null;
}
}
@@ -541,11 +532,20 @@ ReactDOMComponent.Mixin = {
* @internal
* @param {string} rootID The root DOM ID for this node.
* @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
* @param {?ReactDOMComponent} the containing DOM component instance
* @param {?object} info about the native container
* @param {object} context
* @return {string} The computed markup.
*/
mountComponent: function(rootID, transaction, context) {
mountComponent: function(
rootID,
transaction,
nativeParent,
nativeContainerInfo,
context
) {
this._rootNodeID = rootID;
this._nativeContainerInfo = nativeContainerInfo;
var props = this._currentElement.props;
@@ -561,47 +561,46 @@ ReactDOMComponent.Mixin = {
transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
break;
case 'button':
props = ReactDOMButton.getNativeProps(this, props, context);
props = ReactDOMButton.getNativeProps(this, props, nativeParent);
break;
case 'input':
ReactDOMInput.mountWrapper(this, props, context);
props = ReactDOMInput.getNativeProps(this, props, context);
ReactDOMInput.mountWrapper(this, props, nativeParent);
props = ReactDOMInput.getNativeProps(this, props);
break;
case 'option':
ReactDOMOption.mountWrapper(this, props, context);
props = ReactDOMOption.getNativeProps(this, props, context);
ReactDOMOption.mountWrapper(this, props, nativeParent);
props = ReactDOMOption.getNativeProps(this, props);
break;
case 'select':
ReactDOMSelect.mountWrapper(this, props, context);
props = ReactDOMSelect.getNativeProps(this, props, context);
context = ReactDOMSelect.processChildContext(this, props, context);
ReactDOMSelect.mountWrapper(this, props, nativeParent);
props = ReactDOMSelect.getNativeProps(this, props);
break;
case 'textarea':
ReactDOMTextarea.mountWrapper(this, props, context);
props = ReactDOMTextarea.getNativeProps(this, props, context);
ReactDOMTextarea.mountWrapper(this, props, nativeParent);
props = ReactDOMTextarea.getNativeProps(this, props);
break;
}
assertValidProps(this, props);
if (__DEV__) {
if (context[validateDOMNesting.ancestorInfoContextKey]) {
validateDOMNesting(
this._tag,
this,
context[validateDOMNesting.ancestorInfoContextKey]
);
var parentInfo;
if (nativeParent != null) {
parentInfo = nativeParent._ancestorInfo;
} else if (nativeContainerInfo != null) {
parentInfo = nativeContainerInfo._ancestorInfo;
}
}
if (__DEV__) {
this._unprocessedContextDev = context;
this._processedContextDev = processChildContextDev(context, this);
context = this._processedContextDev;
if (parentInfo) {
// parentInfo should always be present except for the top-level
// component when server rendering
validateDOMNesting(this._tag, this, parentInfo);
}
this._ancestorInfo =
validateDOMNesting.updatedAncestorInfo(parentInfo, this._tag, this);
}
var mountImage;
if (transaction.useCreateElement) {
var ownerDocument = context[ReactMount.ownerDocumentContextKey];
var ownerDocument = nativeContainerInfo._ownerDocument;
var el = ownerDocument.createElement(this._currentElement.type);
this._nativeNode = el;
DOMPropertyOperations.setAttributeForID(el, this._rootNodeID);
@@ -835,18 +834,6 @@ ReactDOMComponent.Mixin = {
break;
}
if (__DEV__) {
// If the context is reference-equal to the old one, pass down the same
// processed object so the update bailout in ReactReconciler behaves
// correctly (and identically in dev and prod). See #5005.
if (this._unprocessedContextDev !== context) {
this._unprocessedContextDev = context;
this._processedContextDev = processChildContextDev(context, this);
}
context = this._processedContextDev;
}
assertValidProps(this, nextProps);
this._updateDOMProperties(lastProps, nextProps, transaction);
this._updateDOMChildren(
@@ -0,0 +1,30 @@
/**
* Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactDOMContainerInfo
*/
'use strict';
var validateDOMNesting = require('validateDOMNesting');
var DOC_NODE_TYPE = 9;
function ReactDOMContainerInfo(node) {
var info = {
_ownerDocument: node.nodeType === DOC_NODE_TYPE ? node : node.ownerDocument,
};
if (__DEV__) {
var tag = node.nodeName.toLowerCase();
info._ancestorInfo =
validateDOMNesting.updatedAncestorInfo(null, tag, null);
}
return info;
}
module.exports = ReactDOMContainerInfo;
@@ -68,20 +68,30 @@ assign(ReactDOMTextComponent.prototype, {
* @return {string} Markup for this text node.
* @internal
*/
mountComponent: function(rootID, transaction, context) {
mountComponent: function(
rootID,
transaction,
nativeParent,
nativeContainerInfo,
context
) {
if (__DEV__) {
if (context[validateDOMNesting.ancestorInfoContextKey]) {
validateDOMNesting(
'span',
null,
context[validateDOMNesting.ancestorInfoContextKey]
);
var parentInfo;
if (nativeParent != null) {
parentInfo = nativeParent._ancestorInfo;
} else if (nativeContainerInfo != null) {
parentInfo = nativeContainerInfo._ancestorInfo;
}
if (parentInfo) {
// parentInfo should always be present except for the top-level
// component when server rendering
validateDOMNesting(this._tag, this, parentInfo);
}
}
this._rootNodeID = rootID;
if (transaction.useCreateElement) {
var ownerDocument = context[ReactMount.ownerDocumentContextKey];
var ownerDocument = nativeContainerInfo._ownerDocument;
var el = ownerDocument.createElement('span');
this._nativeNode = el;
DOMPropertyOperations.setAttributeForID(el, rootID);
@@ -96,6 +96,8 @@ var ReactCompositeComponentMixin = {
this._currentElement = element;
this._rootNodeID = null;
this._instance = null;
this._nativeParent = null;
this._nativeContainerInfo = null;
// See ReactUpdateQueue
this._pendingElement = null;
@@ -122,10 +124,18 @@ var ReactCompositeComponentMixin = {
* @final
* @internal
*/
mountComponent: function(rootID, transaction, context) {
mountComponent: function(
rootID,
transaction,
nativeParent,
nativeContainerInfo,
context
) {
this._context = context;
this._mountOrder = nextMountID++;
this._rootNodeID = rootID;
this._nativeParent = nativeParent;
this._nativeContainerInfo = nativeContainerInfo;
var publicProps = this._processProps(this._currentElement.props);
var publicContext = this._processContext(context);
@@ -288,6 +298,8 @@ var ReactCompositeComponentMixin = {
this._renderedComponent,
rootID,
transaction,
nativeParent,
nativeContainerInfo,
this._processChildContext(context)
);
if (inst.componentDidMount) {
@@ -739,6 +751,8 @@ var ReactCompositeComponentMixin = {
this._renderedComponent,
this._rootNodeID,
transaction,
this._nativeParent,
this._nativeContainerInfo,
this._processChildContext(context)
);
this._replaceNodeWithMarkup(oldNativeNode, nextMarkup);
@@ -33,13 +33,21 @@ var ReactEmptyComponent = function(instantiate) {
assign(ReactEmptyComponent.prototype, {
construct: function(element) {
},
mountComponent: function(rootID, transaction, context) {
mountComponent: function(
rootID,
transaction,
nativeParent,
nativeContainerInfo,
context
) {
ReactEmptyComponentRegistry.registerNullComponentID(rootID);
this._rootNodeID = rootID;
return ReactReconciler.mountComponent(
this._renderedComponent,
rootID,
transaction,
nativeParent,
nativeContainerInfo,
context
);
},
@@ -255,6 +255,8 @@ var ReactMultiChild = {
child,
rootID,
transaction,
this,
this._nativeContainerInfo,
context
);
child._mountIndex = index++;
@@ -503,6 +505,8 @@ var ReactMultiChild = {
child,
rootID,
transaction,
this,
this._nativeContainerInfo,
context
);
child._mountIndex = index;
@@ -29,12 +29,27 @@ var ReactReconciler = {
* @param {ReactComponent} internalInstance
* @param {string} rootID DOM ID of the root node.
* @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
* @param {?object} the containing native component instance
* @param {?object} info about the native container
* @return {?string} Rendered markup to be inserted into the DOM.
* @final
* @internal
*/
mountComponent: function(internalInstance, rootID, transaction, context) {
var markup = internalInstance.mountComponent(rootID, transaction, context);
mountComponent: function(
internalInstance,
rootID,
transaction,
nativeParent,
nativeContainerInfo,
context
) {
var markup = internalInstance.mountComponent(
rootID,
transaction,
nativeParent,
nativeContainerInfo,
context
);
if (internalInstance._currentElement &&
internalInstance._currentElement.ref != null) {
transaction.getReactMountReady().enqueue(attachRefs, internalInstance);
+1 -1
View File
@@ -446,7 +446,7 @@ ReactShallowRenderer.prototype._render = function(element, transaction, context)
var instance = new ShallowComponentWrapper(element.type);
instance.construct(element);
instance.mountComponent(rootID, transaction, context);
instance.mountComponent(rootID, transaction, null, null, context);
this._instance = instance;
}