mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
ReactDOMComponentTree
New module! With this, we have a new module that uses the component tree to store DOM nodes. Most of the logic in this file relates to markup adoption -- if we were to drop that (along with server rendering) this file could just be a `return inst._nativeNode;`. This works with useCreateElement only because we need to reference each node as it's created. Events is now the only thing using ReactMount.getNode -- I'll introduce pointers back from the DOM nodes (and a `ReactDOMComponentTree.getInstanceFromNode`) and make that work.
This commit is contained in:
1 parent
5a7c273a1b
commit
ce528457a4
15 files changed
+289
-137
No files matched your search
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* 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 ReactDOMComponentTree
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var DOMProperty = require('DOMProperty');
|
||||
var ReactDOMComponentFlags = require('ReactDOMComponentFlags');
|
||||
|
||||
var invariant = require('invariant');
|
||||
|
||||
var ATTR_NAME = DOMProperty.ID_ATTRIBUTE_NAME;
|
||||
var Flags = ReactDOMComponentFlags;
|
||||
|
||||
/**
|
||||
* Drill down (through composites and empty components) until we get a native or
|
||||
* native text component.
|
||||
*
|
||||
* This is pretty polymorphic but unavoidable with the current structure we have
|
||||
* for `_renderedChildren`.
|
||||
*/
|
||||
function getRenderedNativeOrTextFromComponent(component) {
|
||||
var rendered;
|
||||
while ((rendered = component._renderedComponent)) {
|
||||
component = rendered;
|
||||
}
|
||||
return component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate `_nativeNode` on the rendered native/text component with the given
|
||||
* DOM node. The passed `inst` can be a composite.
|
||||
*/
|
||||
function precacheNode(inst, node) {
|
||||
var nativeInst = getRenderedNativeOrTextFromComponent(inst);
|
||||
nativeInst._nativeNode = node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate `_nativeNode` on each child of `inst`, assuming that the children
|
||||
* match up with the DOM (element) children of `node`.
|
||||
*
|
||||
* We cache entire levels at once to avoid an n^2 problem where we access the
|
||||
* children of a node sequentially and have to walk from the start to our target
|
||||
* node every time.
|
||||
*
|
||||
* Since we update `_renderedChildren` and the actual DOM at (slightly)
|
||||
* different times, we could race here and not get the
|
||||
*/
|
||||
function precacheChildNodes(inst, node) {
|
||||
if (inst._flags & Flags.hasCachedChildNodes) {
|
||||
return;
|
||||
}
|
||||
var children = inst._renderedChildren;
|
||||
var childNode = node.firstChild;
|
||||
outer: for (var name in children) {
|
||||
if (!children.hasOwnProperty(name)) {
|
||||
continue;
|
||||
}
|
||||
var childInst = children[name];
|
||||
var childID = childInst._rootNodeID;
|
||||
if (childID == null) {
|
||||
// We're currently unmounting this child in ReactMultiChild; skip it.
|
||||
continue;
|
||||
}
|
||||
// We assume the child nodes are in the same order as the child instances.
|
||||
for (; childNode !== null; childNode = childNode.nextSibling) {
|
||||
if (childNode.nodeType === 1 &&
|
||||
childNode.getAttribute(ATTR_NAME) === childID) {
|
||||
precacheNode(childInst, childNode);
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
// We reached the end of the DOM children without finding an ID match.
|
||||
invariant(false, 'Unable to find element with ID %s.', childID);
|
||||
}
|
||||
inst._flags |= Flags.hasCachedChildNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a ReactDOMComponent or ReactDOMTextComponent, return the corresponding
|
||||
* DOM node.
|
||||
*/
|
||||
function getNodeFromInstance(inst) {
|
||||
if (inst._nativeNode) {
|
||||
return inst._nativeNode;
|
||||
}
|
||||
|
||||
// Walk up the tree until we find an ancestor whose DOM node we have cached.
|
||||
var parents = [];
|
||||
while (!inst._nativeNode) {
|
||||
parents.push(inst);
|
||||
invariant(
|
||||
inst._nativeParent,
|
||||
'React DOM tree root should always have a node reference.'
|
||||
);
|
||||
inst = inst._nativeParent;
|
||||
}
|
||||
|
||||
// Now parents contains each ancestor that does *not* have a cached native
|
||||
// node, and `inst` is the deepest ancestor that does.
|
||||
for (; parents.length; inst = parents.pop()) {
|
||||
precacheChildNodes(inst, inst._nativeNode);
|
||||
}
|
||||
|
||||
return inst._nativeNode;
|
||||
}
|
||||
|
||||
var ReactDOMComponentTree = {
|
||||
getNodeFromInstance: getNodeFromInstance,
|
||||
precacheChildNodes: precacheChildNodes,
|
||||
precacheNode: precacheNode,
|
||||
};
|
||||
|
||||
module.exports = ReactDOMComponentTree;
|
||||
@@ -13,56 +13,14 @@
|
||||
'use strict';
|
||||
|
||||
var DOMChildrenOperations = require('DOMChildrenOperations');
|
||||
var DOMPropertyOperations = require('DOMPropertyOperations');
|
||||
var ReactMount = require('ReactMount');
|
||||
var ReactDOMComponentTree = require('ReactDOMComponentTree');
|
||||
var ReactPerf = require('ReactPerf');
|
||||
|
||||
var invariant = require('invariant');
|
||||
|
||||
/**
|
||||
* Errors for properties that should not be updated with `updatePropertyByID()`.
|
||||
*
|
||||
* @type {object}
|
||||
* @private
|
||||
*/
|
||||
var INVALID_PROPERTY_ERRORS = {
|
||||
dangerouslySetInnerHTML:
|
||||
'`dangerouslySetInnerHTML` must be set using `updateInnerHTMLByID()`.',
|
||||
style: '`style` must be set using `updateStylesByID()`.',
|
||||
};
|
||||
|
||||
/**
|
||||
* Operations used to process updates to DOM nodes.
|
||||
*/
|
||||
var ReactDOMIDOperations = {
|
||||
|
||||
/**
|
||||
* Updates a DOM node with new property values. This should only be used to
|
||||
* update DOM properties in `DOMProperty`.
|
||||
*
|
||||
* @param {string} id ID of the node to update.
|
||||
* @param {string} name A valid property name, see `DOMProperty`.
|
||||
* @param {*} value New value of the property.
|
||||
* @internal
|
||||
*/
|
||||
updatePropertyByID: function(id, name, value) {
|
||||
var node = ReactMount.getNode(id);
|
||||
invariant(
|
||||
!INVALID_PROPERTY_ERRORS.hasOwnProperty(name),
|
||||
'updatePropertyByID(...): %s',
|
||||
INVALID_PROPERTY_ERRORS[name]
|
||||
);
|
||||
|
||||
// If we're updating to null or undefined, we should remove the property
|
||||
// from the DOM node instead of inadvertantly setting to a string. This
|
||||
// brings us in line with the same behavior we have on initial render.
|
||||
if (value != null) {
|
||||
DOMPropertyOperations.setValueForProperty(node, name, value);
|
||||
} else {
|
||||
DOMPropertyOperations.deleteValueForProperty(node, name);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates a component's children by processing a series of updates.
|
||||
*
|
||||
@@ -72,7 +30,9 @@ var ReactDOMIDOperations = {
|
||||
*/
|
||||
dangerouslyProcessChildrenUpdates: function(updates, markup) {
|
||||
for (var i = 0; i < updates.length; i++) {
|
||||
updates[i].parentNode = ReactMount.getNode(updates[i].parentID);
|
||||
var update = updates[i];
|
||||
var node = ReactDOMComponentTree.getNodeFromInstance(update.parentInst);
|
||||
update.parentNode = node;
|
||||
}
|
||||
DOMChildrenOperations.processUpdates(updates, markup);
|
||||
},
|
||||
|
||||
@@ -16,6 +16,7 @@ var DOMLazyTree = require('DOMLazyTree');
|
||||
var DOMProperty = require('DOMProperty');
|
||||
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
|
||||
var ReactCurrentOwner = require('ReactCurrentOwner');
|
||||
var ReactDOMComponentTree = require('ReactDOMComponentTree');
|
||||
var ReactDOMContainerInfo = require('ReactDOMContainerInfo');
|
||||
var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
|
||||
var ReactElement = require('ReactElement');
|
||||
@@ -267,6 +268,7 @@ function mountComponentIntoNode(
|
||||
ReactMount._mountImageIntoNode(
|
||||
markup,
|
||||
container,
|
||||
componentInstance,
|
||||
shouldReuseMarkup,
|
||||
transaction
|
||||
);
|
||||
@@ -951,6 +953,7 @@ var ReactMount = {
|
||||
_mountImageIntoNode: function(
|
||||
markup,
|
||||
container,
|
||||
instance,
|
||||
shouldReuseMarkup,
|
||||
transaction
|
||||
) {
|
||||
@@ -966,6 +969,7 @@ var ReactMount = {
|
||||
if (shouldReuseMarkup) {
|
||||
var rootElement = getReactRootElementInContainer(container);
|
||||
if (ReactMarkupChecksum.canReuseMarkup(markup, rootElement)) {
|
||||
ReactDOMComponentTree.precacheNode(instance, rootElement);
|
||||
return;
|
||||
} else {
|
||||
var checksum = rootElement.getAttribute(
|
||||
@@ -1049,6 +1053,7 @@ var ReactMount = {
|
||||
DOMLazyTree.insertTreeBefore(container, markup, null);
|
||||
} else {
|
||||
setInnerHTML(container, markup);
|
||||
ReactDOMComponentTree.precacheNode(instance, container.firstChild);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright 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.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
describe('ReactDOMComponentTree', function() {
|
||||
var React;
|
||||
var ReactDOM;
|
||||
var ReactDOMServer;
|
||||
|
||||
function renderMarkupIntoDocument(elt) {
|
||||
var container = document.createElement('div');
|
||||
// Force server-rendering path:
|
||||
container.innerHTML = ReactDOMServer.renderToString(elt);
|
||||
return ReactDOM.render(elt, container);
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
React = require('React');
|
||||
ReactDOM = require('ReactDOM');
|
||||
ReactDOMServer = require('ReactDOMServer');
|
||||
});
|
||||
|
||||
it('finds nodes for instances', function() {
|
||||
// This is a little hard to test directly. But refs rely on it -- so we
|
||||
// check that we can find a ref at arbitrary points in the tree, even if
|
||||
// other nodes don't have a ref.
|
||||
var Component = React.createClass({
|
||||
render: function() {
|
||||
var toRef = this.props.toRef;
|
||||
return (
|
||||
<div ref={toRef === 'div' ? 'target' : null}>
|
||||
<h1 ref={toRef === 'h1' ? 'target' : null}>hello</h1>
|
||||
<p ref={toRef === 'p' ? 'target' : null}>
|
||||
<input ref={toRef === 'input' ? 'target' : null} />
|
||||
</p>
|
||||
goodbye.
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
function renderAndGetRef(toRef) {
|
||||
var inst = renderMarkupIntoDocument(<Component toRef={toRef} />);
|
||||
return inst.refs.target.nodeName;
|
||||
}
|
||||
|
||||
expect(renderAndGetRef('div')).toBe('DIV');
|
||||
expect(renderAndGetRef('h1')).toBe('H1');
|
||||
expect(renderAndGetRef('p')).toBe('P');
|
||||
expect(renderAndGetRef('input')).toBe('INPUT');
|
||||
});
|
||||
|
||||
});
|
||||
@@ -12,42 +12,16 @@
|
||||
'use strict';
|
||||
|
||||
describe('ReactDOMIDOperations', function() {
|
||||
var DOMPropertyOperations = require('DOMPropertyOperations');
|
||||
var ReactDOMIDOperations = require('ReactDOMIDOperations');
|
||||
var ReactMount = require('ReactMount');
|
||||
var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes');
|
||||
var keyOf = require('keyOf');
|
||||
|
||||
it('should disallow updating special properties', function() {
|
||||
spyOn(ReactMount, 'getNode');
|
||||
spyOn(DOMPropertyOperations, 'setValueForProperty');
|
||||
|
||||
expect(function() {
|
||||
ReactDOMIDOperations.updatePropertyByID(
|
||||
'testID',
|
||||
keyOf({dangerouslySetInnerHTML: null}),
|
||||
{__html: 'testContent'}
|
||||
);
|
||||
}).toThrow();
|
||||
|
||||
expect(
|
||||
ReactMount.getNode.argsForCall[0][0]
|
||||
).toBe('testID');
|
||||
|
||||
expect(
|
||||
DOMPropertyOperations.setValueForProperty.calls.length
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
it('should update innerHTML and preserve whitespace', function() {
|
||||
var stubNode = document.createElement('div');
|
||||
spyOn(ReactMount, 'getNode').andReturn(stubNode);
|
||||
|
||||
var html = '\n \t <span> \n testContent \t </span> \n \t';
|
||||
|
||||
ReactDOMIDOperations.dangerouslyProcessChildrenUpdates(
|
||||
[{
|
||||
parentID: 'testID',
|
||||
parentInst: {_nativeNode: stubNode},
|
||||
parentNode: null,
|
||||
type: ReactMultiChildUpdateTypes.SET_MARKUP,
|
||||
markupIndex: null,
|
||||
@@ -58,10 +32,6 @@ describe('ReactDOMIDOperations', function() {
|
||||
[]
|
||||
);
|
||||
|
||||
expect(
|
||||
ReactMount.getNode.argsForCall[0][0]
|
||||
).toBe('testID');
|
||||
|
||||
expect(stubNode.innerHTML).toBe(html);
|
||||
});
|
||||
});
|
||||
@@ -13,7 +13,7 @@
|
||||
'use strict';
|
||||
|
||||
var ReactCurrentOwner = require('ReactCurrentOwner');
|
||||
var ReactDOMComponent = require('ReactDOMComponent');
|
||||
var ReactDOMComponentTree = require('ReactDOMComponentTree');
|
||||
var ReactInstanceMap = require('ReactInstanceMap');
|
||||
var ReactNodeTypes = require('ReactNodeTypes');
|
||||
|
||||
@@ -66,7 +66,7 @@ function findDOMNode(componentOrElement) {
|
||||
var inst = ReactInstanceMap.get(componentOrElement);
|
||||
if (inst) {
|
||||
inst = getNativeComponentFromComposite(inst);
|
||||
return inst ? ReactDOMComponent.getNodeFromInstance(inst) : null;
|
||||
return inst ? ReactDOMComponentTree.getNodeFromInstance(inst) : null;
|
||||
}
|
||||
|
||||
if (typeof componentOrElement.render === 'function') {
|
||||
|
||||
@@ -12,13 +12,13 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactMount = require('ReactMount');
|
||||
var ReactDOMComponentTree = require('ReactDOMComponentTree');
|
||||
|
||||
var focusNode = require('focusNode');
|
||||
|
||||
var AutoFocusUtils = {
|
||||
focusDOMComponent: function() {
|
||||
focusNode(ReactMount.getNode(this._rootNodeID));
|
||||
focusNode(ReactDOMComponentTree.getNodeFromInstance(this));
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -11,8 +11,9 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactDOMIDOperations = require('ReactDOMIDOperations');
|
||||
var DOMPropertyOperations = require('DOMPropertyOperations');
|
||||
var LinkedValueUtils = require('LinkedValueUtils');
|
||||
var ReactDOMComponentTree = require('ReactDOMComponentTree');
|
||||
var ReactMount = require('ReactMount');
|
||||
var ReactUpdates = require('ReactUpdates');
|
||||
|
||||
@@ -131,8 +132,8 @@ var ReactDOMInput = {
|
||||
// TODO: Shouldn't this be getChecked(props)?
|
||||
var checked = props.checked;
|
||||
if (checked != null) {
|
||||
ReactDOMIDOperations.updatePropertyByID(
|
||||
inst._rootNodeID,
|
||||
DOMPropertyOperations.setValueForProperty(
|
||||
ReactDOMComponentTree.getNodeFromInstance(inst),
|
||||
'checked',
|
||||
checked || false
|
||||
);
|
||||
@@ -142,8 +143,8 @@ var ReactDOMInput = {
|
||||
if (value != null) {
|
||||
// Cast `value` to a string to ensure the value is set correctly. While
|
||||
// browsers typically do this as necessary, jsdom doesn't.
|
||||
ReactDOMIDOperations.updatePropertyByID(
|
||||
inst._rootNodeID,
|
||||
DOMPropertyOperations.setValueForProperty(
|
||||
ReactDOMComponentTree.getNodeFromInstance(inst),
|
||||
'value',
|
||||
'' + value
|
||||
);
|
||||
@@ -163,7 +164,7 @@ function _handleChange(event) {
|
||||
|
||||
var name = props.name;
|
||||
if (props.type === 'radio' && name != null) {
|
||||
var rootNode = ReactMount.getNode(this._rootNodeID);
|
||||
var rootNode = ReactDOMComponentTree.getNodeFromInstance(this);
|
||||
var queryRoot = rootNode;
|
||||
|
||||
while (queryRoot.parentNode) {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
'use strict';
|
||||
|
||||
var LinkedValueUtils = require('LinkedValueUtils');
|
||||
var ReactMount = require('ReactMount');
|
||||
var ReactDOMComponentTree = require('ReactDOMComponentTree');
|
||||
var ReactUpdates = require('ReactUpdates');
|
||||
|
||||
var assign = require('Object.assign');
|
||||
@@ -112,7 +112,7 @@ function checkSelectPropTypes(inst, props) {
|
||||
*/
|
||||
function updateOptions(inst, multiple, propValue) {
|
||||
var selectedValue, i;
|
||||
var options = ReactMount.getNode(inst._rootNodeID).options;
|
||||
var options = ReactDOMComponentTree.getNodeFromInstance(inst).options;
|
||||
|
||||
if (multiple) {
|
||||
selectedValue = {};
|
||||
|
||||
@@ -11,8 +11,9 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var DOMPropertyOperations = require('DOMPropertyOperations');
|
||||
var LinkedValueUtils = require('LinkedValueUtils');
|
||||
var ReactDOMIDOperations = require('ReactDOMIDOperations');
|
||||
var ReactDOMComponentTree = require('ReactDOMComponentTree');
|
||||
var ReactUpdates = require('ReactUpdates');
|
||||
|
||||
var assign = require('Object.assign');
|
||||
@@ -144,8 +145,8 @@ var ReactDOMTextarea = {
|
||||
if (value != null) {
|
||||
// Cast `value` to a string to ensure the value is set correctly. While
|
||||
// browsers typically do this as necessary, jsdom doesn't.
|
||||
ReactDOMIDOperations.updatePropertyByID(
|
||||
inst._rootNodeID,
|
||||
DOMPropertyOperations.setValueForProperty(
|
||||
ReactDOMComponentTree.getNodeFromInstance(inst),
|
||||
'value',
|
||||
'' + value
|
||||
);
|
||||
|
||||
@@ -27,6 +27,8 @@ var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
|
||||
var ReactComponentBrowserEnvironment =
|
||||
require('ReactComponentBrowserEnvironment');
|
||||
var ReactDOMButton = require('ReactDOMButton');
|
||||
var ReactDOMComponentFlags = require('ReactDOMComponentFlags');
|
||||
var ReactDOMComponentTree = require('ReactDOMComponentTree');
|
||||
var ReactDOMInput = require('ReactDOMInput');
|
||||
var ReactDOMOption = require('ReactDOMOption');
|
||||
var ReactDOMSelect = require('ReactDOMSelect');
|
||||
@@ -46,7 +48,9 @@ var shallowEqual = require('shallowEqual');
|
||||
var validateDOMNesting = require('validateDOMNesting');
|
||||
var warning = require('warning');
|
||||
|
||||
var Flags = ReactDOMComponentFlags;
|
||||
var deleteListener = EventPluginHub.deleteListener;
|
||||
var getNode = ReactDOMComponentTree.getNodeFromInstance;
|
||||
var listenTo = ReactBrowserEventEmitter.listenTo;
|
||||
var registrationNameModules = EventPluginRegistry.registrationNameModules;
|
||||
|
||||
@@ -493,14 +497,6 @@ function isCustomComponent(tagName, props) {
|
||||
return tagName.indexOf('-') >= 0 || props.is != null;
|
||||
}
|
||||
|
||||
function getNode(inst) {
|
||||
if (inst._nativeNode) {
|
||||
return inst._nativeNode;
|
||||
} else {
|
||||
return inst._nativeNode = ReactMount.getNode(inst._rootNodeID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new React class that is idempotent and capable of containing other
|
||||
* React components. It accepts event listeners and DOM properties that are
|
||||
@@ -523,11 +519,12 @@ function ReactDOMComponent(tag) {
|
||||
this._previousStyle = null;
|
||||
this._previousStyleCopy = null;
|
||||
this._nativeNode = null;
|
||||
this._nativeParent = null;
|
||||
this._rootNodeID = null;
|
||||
this._nativeContainerInfo = null;
|
||||
this._wrapperState = null;
|
||||
this._topLevelWrapper = null;
|
||||
this._nodeHasLegacyProperties = false;
|
||||
this._flags = 0;
|
||||
if (__DEV__) {
|
||||
this._ancestorInfo = null;
|
||||
}
|
||||
@@ -561,6 +558,7 @@ ReactDOMComponent.Mixin = {
|
||||
context
|
||||
) {
|
||||
this._rootNodeID = rootID;
|
||||
this._nativeParent = nativeParent;
|
||||
this._nativeContainerInfo = nativeContainerInfo;
|
||||
|
||||
var props = this._currentElement.props;
|
||||
@@ -664,6 +662,7 @@ ReactDOMComponent.Mixin = {
|
||||
);
|
||||
}
|
||||
this._nativeNode = el;
|
||||
this._flags |= Flags.hasCachedChildNodes;
|
||||
DOMPropertyOperations.setAttributeForID(el, this._rootNodeID);
|
||||
// Populate node cache
|
||||
ReactMount.getID(el);
|
||||
@@ -907,7 +906,7 @@ ReactDOMComponent.Mixin = {
|
||||
context
|
||||
);
|
||||
|
||||
if (!canDefineProperty && this._nodeHasLegacyProperties) {
|
||||
if (!canDefineProperty && this._flags & Flags.nodeHasLegacyProperties) {
|
||||
this._nativeNode.props = nextProps;
|
||||
}
|
||||
|
||||
@@ -1146,7 +1145,7 @@ ReactDOMComponent.Mixin = {
|
||||
break;
|
||||
}
|
||||
|
||||
if (this._nodeHasLegacyProperties) {
|
||||
if (this._flags & Flags.nodeHasLegacyProperties) {
|
||||
this._nativeNode._reactInternalComponent = null;
|
||||
}
|
||||
this._nativeNode = null;
|
||||
@@ -1159,7 +1158,7 @@ ReactDOMComponent.Mixin = {
|
||||
},
|
||||
|
||||
getPublicInstance: function() {
|
||||
if (this._nodeHasLegacyProperties) {
|
||||
if (this._flags & Flags.nodeHasLegacyProperties) {
|
||||
return this._nativeNode;
|
||||
} else {
|
||||
var node = getNode(this);
|
||||
@@ -1185,7 +1184,7 @@ ReactDOMComponent.Mixin = {
|
||||
node.props = this._currentElement.props;
|
||||
}
|
||||
|
||||
this._nodeHasLegacyProperties = true;
|
||||
this._flags |= Flags.nodeHasLegacyProperties;
|
||||
return node;
|
||||
}
|
||||
},
|
||||
@@ -1200,11 +1199,15 @@ ReactPerf.measureMethods(ReactDOMComponent, 'ReactDOMComponent', {
|
||||
assign(
|
||||
ReactDOMComponent.prototype,
|
||||
ReactDOMComponent.Mixin,
|
||||
ReactMultiChild.Mixin
|
||||
ReactMultiChild.Mixin,
|
||||
{
|
||||
prepareToManageChildren: function() {
|
||||
// Before we add, remove, or reorder the children of a node, make sure
|
||||
// we have references to all of its children so we don't lose them, even
|
||||
// if nefarious browser plugins add extra nodes to our tree.
|
||||
ReactDOMComponentTree.precacheChildNodes(this, getNode(this));
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
assign(ReactDOMComponent, {
|
||||
getNodeFromInstance: getNode,
|
||||
});
|
||||
|
||||
module.exports = ReactDOMComponent;
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright 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 ReactDOMComponentFlags
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactDOMComponentFlags = {
|
||||
nodeHasLegacyProperties: 1 << 0,
|
||||
hasCachedChildNodes: 1 << 1,
|
||||
};
|
||||
|
||||
module.exports = ReactDOMComponentFlags;
|
||||
@@ -17,19 +17,14 @@ var DOMLazyTree = require('DOMLazyTree');
|
||||
var DOMPropertyOperations = require('DOMPropertyOperations');
|
||||
var ReactComponentBrowserEnvironment =
|
||||
require('ReactComponentBrowserEnvironment');
|
||||
var ReactDOMComponentTree = require('ReactDOMComponentTree');
|
||||
var ReactMount = require('ReactMount');
|
||||
|
||||
var assign = require('Object.assign');
|
||||
var escapeTextContentForBrowser = require('escapeTextContentForBrowser');
|
||||
var validateDOMNesting = require('validateDOMNesting');
|
||||
|
||||
function getNode(inst) {
|
||||
if (inst._nativeNode) {
|
||||
return inst._nativeNode;
|
||||
} else {
|
||||
return inst._nativeNode = ReactMount.getNode(inst._rootNodeID);
|
||||
}
|
||||
}
|
||||
var getNode = ReactDOMComponentTree.getNodeFromInstance;
|
||||
|
||||
/**
|
||||
* Text nodes violate a couple assumptions that React makes about components:
|
||||
@@ -61,6 +56,8 @@ assign(ReactDOMTextComponent.prototype, {
|
||||
this._currentElement = text;
|
||||
this._stringText = '' + text;
|
||||
this._nativeNode = null;
|
||||
// ReactDOMComponentTree uses this:
|
||||
this._nativeParent = null;
|
||||
|
||||
// Properties
|
||||
this._rootNodeID = null;
|
||||
@@ -98,6 +95,7 @@ assign(ReactDOMTextComponent.prototype, {
|
||||
}
|
||||
|
||||
this._rootNodeID = rootID;
|
||||
this._nativeParent = nativeParent;
|
||||
if (transaction.useCreateElement) {
|
||||
var ownerDocument = nativeContainerInfo._ownerDocument;
|
||||
var el = ownerDocument.createElement('span');
|
||||
|
||||
@@ -51,15 +51,15 @@ var markupQueue = [];
|
||||
/**
|
||||
* Enqueues markup to be rendered and inserted at a supplied index.
|
||||
*
|
||||
* @param {string} parentID ID of the parent component.
|
||||
* @param {object} parentInst parent component.
|
||||
* @param {string} markup Markup that renders into an element.
|
||||
* @param {number} toIndex Destination index.
|
||||
* @private
|
||||
*/
|
||||
function enqueueInsertMarkup(parentID, markup, toIndex) {
|
||||
function enqueueInsertMarkup(parentInst, markup, toIndex) {
|
||||
// NOTE: Null values reduce hidden classes.
|
||||
updateQueue.push({
|
||||
parentID: parentID,
|
||||
parentInst: parentInst,
|
||||
parentNode: null,
|
||||
type: ReactMultiChildUpdateTypes.INSERT_MARKUP,
|
||||
markupIndex: markupQueue.push(markup) - 1,
|
||||
@@ -72,15 +72,15 @@ function enqueueInsertMarkup(parentID, markup, toIndex) {
|
||||
/**
|
||||
* Enqueues moving an existing element to another index.
|
||||
*
|
||||
* @param {string} parentID ID of the parent component.
|
||||
* @param {object} parentInst parent component.
|
||||
* @param {number} fromIndex Source index of the existing element.
|
||||
* @param {number} toIndex Destination index of the element.
|
||||
* @private
|
||||
*/
|
||||
function enqueueMove(parentID, fromIndex, toIndex) {
|
||||
function enqueueMove(parentInst, fromIndex, toIndex) {
|
||||
// NOTE: Null values reduce hidden classes.
|
||||
updateQueue.push({
|
||||
parentID: parentID,
|
||||
parentInst: parentInst,
|
||||
parentNode: null,
|
||||
type: ReactMultiChildUpdateTypes.MOVE_EXISTING,
|
||||
markupIndex: null,
|
||||
@@ -93,14 +93,14 @@ function enqueueMove(parentID, fromIndex, toIndex) {
|
||||
/**
|
||||
* Enqueues removing an element at an index.
|
||||
*
|
||||
* @param {string} parentID ID of the parent component.
|
||||
* @param {object} parentInst parent component.
|
||||
* @param {number} fromIndex Index of the element to remove.
|
||||
* @private
|
||||
*/
|
||||
function enqueueRemove(parentID, fromIndex) {
|
||||
function enqueueRemove(parentInst, fromIndex) {
|
||||
// NOTE: Null values reduce hidden classes.
|
||||
updateQueue.push({
|
||||
parentID: parentID,
|
||||
parentInst: parentInst,
|
||||
parentNode: null,
|
||||
type: ReactMultiChildUpdateTypes.REMOVE_NODE,
|
||||
markupIndex: null,
|
||||
@@ -113,14 +113,14 @@ function enqueueRemove(parentID, fromIndex) {
|
||||
/**
|
||||
* Enqueues setting the markup of a node.
|
||||
*
|
||||
* @param {string} parentID ID of the parent component.
|
||||
* @param {object} parentInst parent component.
|
||||
* @param {string} markup Markup that renders into an element.
|
||||
* @private
|
||||
*/
|
||||
function enqueueSetMarkup(parentID, markup) {
|
||||
function enqueueSetMarkup(parentInst, markup) {
|
||||
// NOTE: Null values reduce hidden classes.
|
||||
updateQueue.push({
|
||||
parentID: parentID,
|
||||
parentInst: parentInst,
|
||||
parentNode: null,
|
||||
type: ReactMultiChildUpdateTypes.SET_MARKUP,
|
||||
markupIndex: null,
|
||||
@@ -133,14 +133,14 @@ function enqueueSetMarkup(parentID, markup) {
|
||||
/**
|
||||
* Enqueues setting the text content.
|
||||
*
|
||||
* @param {string} parentID ID of the parent component.
|
||||
* @param {object} parentInst parent component.
|
||||
* @param {string} textContent Text content to set.
|
||||
* @private
|
||||
*/
|
||||
function enqueueTextContent(parentID, textContent) {
|
||||
function enqueueTextContent(parentInst, textContent) {
|
||||
// NOTE: Null values reduce hidden classes.
|
||||
updateQueue.push({
|
||||
parentID: parentID,
|
||||
parentInst: parentInst,
|
||||
parentNode: null,
|
||||
type: ReactMultiChildUpdateTypes.TEXT_CONTENT,
|
||||
markupIndex: null,
|
||||
@@ -371,7 +371,6 @@ var ReactMultiChild = {
|
||||
var nextChildren = this._reconcilerUpdateChildren(
|
||||
prevChildren, nextNestedChildrenElements, transaction, context
|
||||
);
|
||||
this._renderedChildren = nextChildren;
|
||||
if (!nextChildren && !prevChildren) {
|
||||
return;
|
||||
}
|
||||
@@ -410,6 +409,7 @@ var ReactMultiChild = {
|
||||
this._unmountChild(prevChildren[name]);
|
||||
}
|
||||
}
|
||||
this._renderedChildren = nextChildren;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -424,6 +424,14 @@ var ReactMultiChild = {
|
||||
this._renderedChildren = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Hook used by the DOM implementation to precache the nodes before we apply
|
||||
* any reorders here.
|
||||
*/
|
||||
prepareToManageChildren: function() {
|
||||
// TODO: This sucks. Figure out a better design here.
|
||||
},
|
||||
|
||||
/**
|
||||
* Moves a child component to the supplied index.
|
||||
*
|
||||
@@ -437,7 +445,8 @@ var ReactMultiChild = {
|
||||
// be moved. Otherwise, we do not need to move it because a child will be
|
||||
// inserted or moved before `child`.
|
||||
if (child._mountIndex < lastIndex) {
|
||||
enqueueMove(this._rootNodeID, child._mountIndex, toIndex);
|
||||
this.prepareToManageChildren();
|
||||
enqueueMove(this, child._mountIndex, toIndex);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -449,7 +458,8 @@ var ReactMultiChild = {
|
||||
* @protected
|
||||
*/
|
||||
createChild: function(child, mountImage) {
|
||||
enqueueInsertMarkup(this._rootNodeID, mountImage, child._mountIndex);
|
||||
this.prepareToManageChildren();
|
||||
enqueueInsertMarkup(this, mountImage, child._mountIndex);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -459,7 +469,8 @@ var ReactMultiChild = {
|
||||
* @protected
|
||||
*/
|
||||
removeChild: function(child) {
|
||||
enqueueRemove(this._rootNodeID, child._mountIndex);
|
||||
this.prepareToManageChildren();
|
||||
enqueueRemove(this, child._mountIndex);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -469,7 +480,7 @@ var ReactMultiChild = {
|
||||
* @protected
|
||||
*/
|
||||
setTextContent: function(textContent) {
|
||||
enqueueTextContent(this._rootNodeID, textContent);
|
||||
enqueueTextContent(this, textContent);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -479,7 +490,7 @@ var ReactMultiChild = {
|
||||
* @protected
|
||||
*/
|
||||
setMarkup: function(markup) {
|
||||
enqueueSetMarkup(this._rootNodeID, markup);
|
||||
enqueueSetMarkup(this, markup);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -194,7 +194,7 @@ var ReactDefaultPerf = {
|
||||
writeArgs.markup = args[1][update.markupIndex];
|
||||
}
|
||||
ReactDefaultPerf._recordWrite(
|
||||
update.parentID,
|
||||
update.parentInst._rootNodeID,
|
||||
update.type,
|
||||
totalTime,
|
||||
writeArgs
|
||||
|
||||
Reference in new issue
Block a user