Merge pull request #2546 from sebmarkbage/cleanupinternals

Move more stuff out of ReactComponent
This commit is contained in:
Sebastian Markbåge
2014-11-17 14:47:34 -08:00
9 changed files with 182 additions and 245 deletions
@@ -14,20 +14,9 @@
"use strict";
var ReactDOMIDOperations = require('ReactDOMIDOperations');
var ReactMarkupChecksum = require('ReactMarkupChecksum');
var ReactMount = require('ReactMount');
var ReactPerf = require('ReactPerf');
var ReactReconcileTransaction = require('ReactReconcileTransaction');
var getReactRootElementInContainer = require('getReactRootElementInContainer');
var invariant = require('invariant');
var setInnerHTML = require('setInnerHTML');
var ELEMENT_NODE_TYPE = 1;
var DOC_NODE_TYPE = 9;
/**
* Abstracts away all functionality of `ReactComponent` requires knowledge of
* the browser context.
@@ -46,70 +35,8 @@ var ReactComponentBrowserEnvironment = {
*/
unmountIDFromEnvironment: function(rootNodeID) {
ReactMount.purgeID(rootNodeID);
},
}
/**
* @param {string} markup Markup string to place into the DOM Element.
* @param {DOMElement} container DOM Element to insert markup into.
* @param {boolean} shouldReuseMarkup Should reuse the existing markup in the
* container if possible.
*/
mountImageIntoNode: ReactPerf.measure(
'ReactComponentBrowserEnvironment',
'mountImageIntoNode',
function(markup, container, shouldReuseMarkup) {
invariant(
container && (
container.nodeType === ELEMENT_NODE_TYPE ||
container.nodeType === DOC_NODE_TYPE
),
'mountComponentIntoNode(...): Target container is not valid.'
);
if (shouldReuseMarkup) {
if (ReactMarkupChecksum.canReuseMarkup(
markup,
getReactRootElementInContainer(container))) {
return;
} else {
invariant(
container.nodeType !== DOC_NODE_TYPE,
'You\'re trying to render a component to the document using ' +
'server rendering but the checksum was invalid. This usually ' +
'means you rendered a different component type or props on ' +
'the client from the one on the server, or your render() ' +
'methods are impure. React cannot handle this case due to ' +
'cross-browser quirks by rendering at the document root. You ' +
'should look for environment dependent code in your components ' +
'and ensure the props are the same client and server side.'
);
if (__DEV__) {
console.warn(
'React attempted to use reuse markup in a container but the ' +
'checksum was invalid. This generally means that you are ' +
'using server rendering and the markup generated on the ' +
'server was not what the client was expecting. React injected ' +
'new markup to compensate which works but you have lost many ' +
'of the benefits of server rendering. Instead, figure out ' +
'why the markup being generated is different on the client ' +
'or server.'
);
}
}
}
invariant(
container.nodeType !== DOC_NODE_TYPE,
'You\'re trying to render a component to the document but ' +
'you didn\'t use server rendering. We can\'t do this ' +
'without using server rendering due to cross-browser quirks. ' +
'See renderComponentToString() for server rendering.'
);
setInnerHTML(container, markup);
}
)
};
module.exports = ReactComponentBrowserEnvironment;
+5 -5
View File
@@ -147,6 +147,8 @@ function validateDangerousTag(tag) {
function ReactDOMComponent(tag) {
validateDangerousTag(tag);
this._tag = tag;
this._renderedChildren = null;
this._previousStyleCopy = null;
}
ReactDOMComponent.displayName = 'ReactDOMComponent';
@@ -291,11 +293,9 @@ ReactDOMComponent.Mixin = {
return;
}
ReactComponent.Mixin.receiveComponent.call(
this,
nextElement,
transaction
);
var prevElement = this._currentElement;
this._currentElement = nextElement;
this.updateComponent(transaction, prevElement, nextElement);
},
/**
+4
View File
@@ -50,6 +50,10 @@ assign(ReactDOMTextComponent.prototype, {
// TODO: This is really a ReactText (ReactNode), not a ReactElement
this._currentElement = text;
this._stringText = '' + text;
// Properties
this._rootNodeID = null;
this._mountIndex = 0;
},
/**
+83 -1
View File
@@ -18,13 +18,16 @@ var ReactElement = require('ReactElement');
var ReactEmptyComponent = require('ReactEmptyComponent');
var ReactInstanceHandles = require('ReactInstanceHandles');
var ReactInstanceMap = require('ReactInstanceMap');
var ReactMarkupChecksum = require('ReactMarkupChecksum');
var ReactPerf = require('ReactPerf');
var ReactUpdates = require('ReactUpdates');
var containsNode = require('containsNode');
var deprecated = require('deprecated');
var getReactRootElementInContainer = require('getReactRootElementInContainer');
var instantiateReactComponent = require('instantiateReactComponent');
var invariant = require('invariant');
var setInnerHTML = require('setInnerHTML');
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
var warning = require('warning');
@@ -208,6 +211,23 @@ function findDeepestCachedAncestor(targetID) {
return foundNode;
}
/**
* Mounts this component and inserts it into the DOM.
*
* @param {string} rootID DOM ID of the root node.
* @param {DOMElement} container DOM element to mount into.
* @param {ReactReconcileTransaction} transaction
* @param {boolean} shouldReuseMarkup If true, do not insert markup
*/
function mountComponentIntoNode(
rootID,
container,
transaction,
shouldReuseMarkup) {
var markup = this.mountComponent(rootID, transaction, 0);
ReactMount._mountImageIntoNode(markup, container, shouldReuseMarkup);
}
/**
* Mounting is the process of initializing a React component by creatings its
* representative DOM elements and inserting them into a supplied `container`.
@@ -321,11 +341,17 @@ var ReactMount = {
componentInstance,
container
);
componentInstance.mountComponentIntoNode(
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled();
transaction.perform(
mountComponentIntoNode,
componentInstance,
reactRootID,
container,
transaction,
shouldReuseMarkup
);
ReactUpdates.ReactReconcileTransaction.release(transaction);
if (__DEV__) {
// Record the root element in case it later gets transplanted.
@@ -684,6 +710,62 @@ var ReactMount = {
);
},
_mountImageIntoNode: ReactPerf.measure(
'ReactMount',
'_mountImageIntoNode',
function(markup, container, shouldReuseMarkup) {
invariant(
container && (
container.nodeType === ELEMENT_NODE_TYPE ||
container.nodeType === DOC_NODE_TYPE
),
'mountComponentIntoNode(...): Target container is not valid.'
);
if (shouldReuseMarkup) {
if (ReactMarkupChecksum.canReuseMarkup(
markup,
getReactRootElementInContainer(container))) {
return;
} else {
invariant(
container.nodeType !== DOC_NODE_TYPE,
'You\'re trying to render a component to the document using ' +
'server rendering but the checksum was invalid. This usually ' +
'means you rendered a different component type or props on ' +
'the client from the one on the server, or your render() ' +
'methods are impure. React cannot handle this case due to ' +
'cross-browser quirks by rendering at the document root. You ' +
'should look for environment dependent code in your components ' +
'and ensure the props are the same client and server side.'
);
if (__DEV__) {
console.warn(
'React attempted to use reuse markup in a container but the ' +
'checksum was invalid. This generally means that you are ' +
'using server rendering and the markup generated on the ' +
'server was not what the client was expecting. React injected ' +
'new markup to compensate which works but you have lost many ' +
'of the benefits of server rendering. Instead, figure out ' +
'why the markup being generated is different on the client ' +
'or server.'
);
}
}
}
invariant(
container.nodeType !== DOC_NODE_TYPE,
'You\'re trying to render a component to the document but ' +
'you didn\'t use server rendering. We can\'t do this ' +
'without using server rendering due to cross-browser quirks. ' +
'See renderComponentToString() for server rendering.'
);
setInnerHTML(container, markup);
}
),
/**
* React ID utilities.
+4 -158
View File
@@ -11,14 +11,10 @@
"use strict";
var ReactElement = require('ReactElement');
var ReactOwner = require('ReactOwner');
var ReactRef = require('ReactRef');
var ReactUpdates = require('ReactUpdates');
var assign = require('Object.assign');
var invariant = require('invariant');
var keyMirror = require('keyMirror');
var injected = false;
@@ -31,17 +27,6 @@ var injected = false;
*/
var unmountIDFromEnvironment = null;
/**
* The "image" of a component tree, is the platform specific (typically
* serialized) data that represents a tree of lower level UI building blocks.
* On the web, this "image" is HTML markup which describes a construction of
* low level `div` and `span` nodes. Other platforms may have different
* encoding of this "image". This must be injected.
*
* @private
*/
var mountImageIntoNode = null;
function attachRef(ref, component, owner) {
if (ref instanceof ReactRef) {
ReactRef.attachRef(ref, component);
@@ -91,7 +76,6 @@ var ReactComponent = {
!injected,
'ReactComponent: injectEnvironment() can only be called once.'
);
mountImageIntoNode = ReactComponentEnvironment.mountImageIntoNode;
unmountIDFromEnvironment =
ReactComponentEnvironment.unmountIDFromEnvironment;
ReactComponent.BackendIDOperations =
@@ -117,69 +101,6 @@ var ReactComponent = {
*/
Mixin: {
/**
* Sets a subset of the props.
*
* @param {object} partialProps Subset of the next props.
* @param {?function} callback Called after props are updated.
* @final
* @public
*/
setProps: function(partialProps, callback) {
// Merge with the pending element if it exists, otherwise with existing
// element props.
var element = this._pendingElement || this._currentElement;
this.replaceProps(
assign({}, element.props, partialProps),
callback
);
},
/**
* Replaces all of the props.
*
* @param {object} props New props.
* @param {?function} callback Called after props are updated.
* @final
* @public
*/
replaceProps: function(props, callback) {
invariant(
this._mountDepth === 0,
'replaceProps(...): You called `setProps` or `replaceProps` on a ' +
'component with a parent. This is an anti-pattern since props will ' +
'get reactively updated when rendered. Instead, change the owner\'s ' +
'`render` method to pass the correct value as props to the component ' +
'where it is created.'
);
// This is a deoptimized path. We optimize for always having an element.
// This creates an extra internal element.
this._pendingElement = ReactElement.cloneAndReplaceProps(
this._pendingElement || this._currentElement,
props
);
ReactUpdates.enqueueUpdate(this, callback);
},
/**
* Schedule a partial update to the props. Only used for internal testing.
*
* @param {object} partialProps Subset of the next props.
* @param {?function} callback Called after props are updated.
* @final
* @internal
*/
_setPropsInternal: function(partialProps, callback) {
// This is a deoptimized path. We optimize for always having an element.
// This creates an extra internal element.
var element = this._pendingElement || this._currentElement;
this._pendingElement = ReactElement.cloneAndReplaceProps(
element,
assign({}, element.props, partialProps)
);
ReactUpdates.enqueueUpdate(this, callback);
},
/**
* Base constructor for all React components.
*
@@ -190,13 +111,13 @@ var ReactComponent = {
* @internal
*/
construct: function(element) {
// See ReactUpdates.
this._pendingCallbacks = null;
// We keep the old element and a reference to the pending element
// to track updates.
this._currentElement = element;
this._pendingElement = null;
this._rootNodeID = null;
this._mountIndex = 0;
this._mountDepth = 0;
},
/**
@@ -242,41 +163,6 @@ var ReactComponent = {
unmountIDFromEnvironment(this._rootNodeID);
// Reset all fields
this._rootNodeID = null;
this._pendingCallbacks = null;
this._pendingElement = null;
},
/**
* Given a new instance of this component, updates the rendered DOM nodes
* as if that instance was rendered instead.
*
* Subclasses that override this method should make sure to invoke
* `ReactComponent.Mixin.receiveComponent.call(this, ...)`.
*
* @param {object} nextComponent Next set of properties.
* @param {ReactReconcileTransaction} transaction
* @internal
*/
receiveComponent: function(nextElement, transaction) {
this._pendingElement = nextElement;
this.performUpdateIfNecessary(transaction);
},
/**
* If `_pendingElement` is set, update the component.
*
* @param {ReactReconcileTransaction} transaction
* @internal
*/
performUpdateIfNecessary: function(transaction) {
if (this._pendingElement == null) {
return;
}
var prevElement = this._currentElement;
var nextElement = this._pendingElement;
this._currentElement = nextElement;
this._pendingElement = null;
this.updateComponent(transaction, prevElement, nextElement);
},
/**
@@ -312,46 +198,6 @@ var ReactComponent = {
}
},
/**
* Mounts this component and inserts it into the DOM.
*
* @param {string} rootID DOM ID of the root node.
* @param {DOMElement} container DOM element to mount into.
* @param {boolean} shouldReuseMarkup If true, do not insert markup
* @final
* @internal
* @see {ReactMount.render}
*/
mountComponentIntoNode: function(rootID, container, shouldReuseMarkup) {
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled();
transaction.perform(
this._mountComponentIntoNode,
this,
rootID,
container,
transaction,
shouldReuseMarkup
);
ReactUpdates.ReactReconcileTransaction.release(transaction);
},
/**
* @param {string} rootID DOM ID of the root node.
* @param {DOMElement} container DOM element to mount into.
* @param {ReactReconcileTransaction} transaction
* @param {boolean} shouldReuseMarkup If true, do not insert markup
* @final
* @private
*/
_mountComponentIntoNode: function(
rootID,
container,
transaction,
shouldReuseMarkup) {
var markup = this.mountComponent(rootID, transaction, 0);
mountImageIntoNode(markup, container, shouldReuseMarkup);
},
/**
* Get the publicly accessible representation of this component - i.e. what
* is exposed by refs and renderComponent. Can be null for stateless
+75 -5
View File
@@ -115,11 +115,18 @@ var ReactCompositeComponentMixin = assign({},
this._instance.context = null;
this._instance.refs = emptyObject;
this._pendingElement = null;
this._pendingState = null;
this._pendingForceUpdate = false;
this._compositeLifeCycleState = null;
this._renderedComponent = null;
// Children can be either an array or more than one argument
ReactComponent.Mixin.construct.apply(this, arguments);
// See ReactUpdates.
this._pendingCallbacks = null;
},
/**
@@ -234,6 +241,9 @@ var ReactCompositeComponentMixin = assign({},
// Reset pending fields
this._pendingState = null;
this._pendingForceUpdate = false;
this._pendingCallbacks = null;
this._pendingElement = null;
ReactComponent.Mixin.unmountComponent.call(this);
// Delete the reference from the instance to this internal representation
@@ -248,6 +258,69 @@ var ReactCompositeComponentMixin = assign({},
// TODO: inst.context = null;
},
/**
* Sets a subset of the props.
*
* @param {object} partialProps Subset of the next props.
* @param {?function} callback Called after props are updated.
* @final
* @public
*/
setProps: function(partialProps, callback) {
// Merge with the pending element if it exists, otherwise with existing
// element props.
var element = this._pendingElement || this._currentElement;
this.replaceProps(
assign({}, element.props, partialProps),
callback
);
},
/**
* Replaces all of the props.
*
* @param {object} props New props.
* @param {?function} callback Called after props are updated.
* @final
* @public
*/
replaceProps: function(props, callback) {
invariant(
this._mountDepth === 0,
'replaceProps(...): You called `setProps` or `replaceProps` on a ' +
'component with a parent. This is an anti-pattern since props will ' +
'get reactively updated when rendered. Instead, change the owner\'s ' +
'`render` method to pass the correct value as props to the component ' +
'where it is created.'
);
// This is a deoptimized path. We optimize for always having an element.
// This creates an extra internal element.
this._pendingElement = ReactElement.cloneAndReplaceProps(
this._pendingElement || this._currentElement,
props
);
ReactUpdates.enqueueUpdate(this, callback);
},
/**
* Schedule a partial update to the props. Only used for internal testing.
*
* @param {object} partialProps Subset of the next props.
* @param {?function} callback Called after props are updated.
* @final
* @internal
*/
_setPropsInternal: function(partialProps, callback) {
// This is a deoptimized path. We optimize for always having an element.
// This creates an extra internal element.
var element = this._pendingElement || this._currentElement;
this._pendingElement = ReactElement.cloneAndReplaceProps(
element,
assign({}, element.props, partialProps)
);
ReactUpdates.enqueueUpdate(this, callback);
},
/**
* Sets a subset of the state. This only exists because _pendingState is
* internal. This provides a merging strategy that is not available to deep
@@ -442,11 +515,8 @@ var ReactCompositeComponentMixin = assign({},
return;
}
ReactComponent.Mixin.receiveComponent.call(
this,
nextElement,
transaction
);
this._pendingElement = nextElement;
this.performUpdateIfNecessary(transaction);
},
/**
+8
View File
@@ -118,6 +118,14 @@ function instantiateReactComponent(node, parentCompositeType) {
// Sets up the instance. This can probably just move into the constructor now.
instance.construct(node);
// Internal instances should fully constructed at this point, so they should
// not get any new fields added to them at this point.
if (__DEV__) {
if (Object.preventExtensions) {
Object.preventExtensions(instance);
}
}
return instance;
}
+1 -1
View File
@@ -169,7 +169,7 @@ var ReactDefaultPerf = {
rv = func.apply(this, args);
totalTime = performanceNow() - start;
if (fnName === 'mountImageIntoNode') {
if (fnName === '_mountImageIntoNode') {
var mountID = ReactMount.getID(args[1]);
ReactDefaultPerf._recordWrite(mountID, fnName, totalTime, args[0]);
} else if (fnName === 'dangerouslyProcessChildrenUpdates') {
+1 -1
View File
@@ -14,7 +14,7 @@ var assign = require('Object.assign');
// Don't try to save users less than 1.2ms (a number I made up)
var DONT_CARE_THRESHOLD = 1.2;
var DOM_OPERATION_TYPES = {
'mountImageIntoNode': 'set innerHTML',
'_mountImageIntoNode': 'set innerHTML',
INSERT_MARKUP: 'set innerHTML',
MOVE_EXISTING: 'move',
REMOVE_NODE: 'remove',