mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Refactor ReactComponent to have no dependency on the DOM.
React is more than just a DOM app library, it is a component abstraction library. This enforces that.
This commit is contained in:
committed by
Paul O’Shannessy
parent
e6b216bdbb
commit
61c47e4cae
+33
-104
@@ -16,17 +16,11 @@
|
||||
* @providesModule ReactComponent
|
||||
*/
|
||||
|
||||
/*jslint evil: true */
|
||||
|
||||
"use strict";
|
||||
|
||||
var getReactRootElementInContainer = require('getReactRootElementInContainer');
|
||||
var ReactComponentEnvironment = require('ReactComponentEnvironment');
|
||||
var ReactCurrentOwner = require('ReactCurrentOwner');
|
||||
var ReactDOMIDOperations = require('ReactDOMIDOperations');
|
||||
var ReactMarkupChecksum = require('ReactMarkupChecksum');
|
||||
var ReactMount = require('ReactMount');
|
||||
var ReactOwner = require('ReactOwner');
|
||||
var ReactReconcileTransaction = require('ReactReconcileTransaction');
|
||||
var ReactUpdates = require('ReactUpdates');
|
||||
|
||||
var invariant = require('invariant');
|
||||
@@ -195,12 +189,33 @@ var ReactComponent = {
|
||||
LifeCycle: ComponentLifeCycle,
|
||||
|
||||
/**
|
||||
* React references `ReactDOMIDOperations` using this property in order to
|
||||
* allow dependency injection.
|
||||
* Injected module that provides ability to mutate individual properties.
|
||||
* Injected into the base class because many different subclasses need access
|
||||
* to this.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
DOMIDOperations: ReactDOMIDOperations,
|
||||
DOMIDOperations: ReactComponentEnvironment.DOMIDOperations,
|
||||
|
||||
/**
|
||||
* Optionally injectable environment dependent cleanup hook. (server vs.
|
||||
* browser etc). Example: A browser system caches DOM nodes based on component
|
||||
* ID and must remove that cache entry when this instance is unmounted.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
unmountIDFromEnvironment: ReactComponentEnvironment.unmountIDFromEnvironment,
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
mountImageIntoNode: ReactComponentEnvironment.mountImageIntoNode,
|
||||
|
||||
/**
|
||||
* React references `ReactReconcileTransaction` using this property in order
|
||||
@@ -208,30 +223,16 @@ var ReactComponent = {
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
ReactReconcileTransaction: ReactReconcileTransaction,
|
||||
ReactReconcileTransaction:
|
||||
ReactComponentEnvironment.ReactReconcileTransaction,
|
||||
|
||||
/**
|
||||
* @param {object} DOMIDOperations
|
||||
* @final
|
||||
*/
|
||||
setDOMOperations: function(DOMIDOperations) {
|
||||
ReactComponent.DOMIDOperations = DOMIDOperations;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Transaction} ReactReconcileTransaction
|
||||
* @final
|
||||
*/
|
||||
setReactReconcileTransaction: function(ReactReconcileTransaction) {
|
||||
ReactComponent.ReactReconcileTransaction = ReactReconcileTransaction;
|
||||
},
|
||||
|
||||
/**
|
||||
* Base functionality for every ReactComponent constructor.
|
||||
* Base functionality for every ReactComponent constructor. Mixed into the
|
||||
* `ReactComponent` prototype, but exposed statically for easy access.
|
||||
*
|
||||
* @lends {ReactComponent.prototype}
|
||||
*/
|
||||
Mixin: {
|
||||
Mixin: merge(ReactComponentEnvironment.Mixin, {
|
||||
|
||||
/**
|
||||
* Checks whether or not this component is mounted.
|
||||
@@ -244,21 +245,6 @@ var ReactComponent = {
|
||||
return this._lifeCycleState === ComponentLifeCycle.MOUNTED;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the DOM node rendered by this component.
|
||||
*
|
||||
* @return {DOMElement} The root node of this component.
|
||||
* @final
|
||||
* @protected
|
||||
*/
|
||||
getDOMNode: function() {
|
||||
invariant(
|
||||
this.isMounted(),
|
||||
'getDOMNode(): A component must be mounted to have a DOM node.'
|
||||
);
|
||||
return ReactMount.getNode(this._rootNodeID);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets a subset of the props.
|
||||
*
|
||||
@@ -382,7 +368,7 @@ var ReactComponent = {
|
||||
if (props.ref != null) {
|
||||
ReactOwner.removeComponentAsRefFrom(this, props.ref, props[OWNER]);
|
||||
}
|
||||
ReactMount.purgeID(this._rootNodeID);
|
||||
ReactComponent.unmountIDFromEnvironment(this._rootNodeID);
|
||||
this._rootNodeID = null;
|
||||
this._lifeCycleState = ComponentLifeCycle.UNMOUNTED;
|
||||
},
|
||||
@@ -495,63 +481,8 @@ var ReactComponent = {
|
||||
container,
|
||||
transaction,
|
||||
shouldReuseMarkup) {
|
||||
invariant(
|
||||
container && container.nodeType === 1,
|
||||
'mountComponentIntoNode(...): Target container is not a DOM element.'
|
||||
);
|
||||
var markup = this.mountComponent(rootID, transaction);
|
||||
|
||||
if (shouldReuseMarkup) {
|
||||
if (ReactMarkupChecksum.canReuseMarkup(
|
||||
markup,
|
||||
getReactRootElementInContainer(container))) {
|
||||
return;
|
||||
} else {
|
||||
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.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Asynchronously inject markup by ensuring that the container is not in
|
||||
// the document when settings its `innerHTML`.
|
||||
var parent = container.parentNode;
|
||||
if (parent) {
|
||||
var next = container.nextSibling;
|
||||
parent.removeChild(container);
|
||||
container.innerHTML = markup;
|
||||
if (next) {
|
||||
parent.insertBefore(container, next);
|
||||
} else {
|
||||
parent.appendChild(container);
|
||||
}
|
||||
} else {
|
||||
container.innerHTML = markup;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Unmounts this component and removes it from the DOM.
|
||||
*
|
||||
* @param {DOMElement} container DOM element to unmount from.
|
||||
* @final
|
||||
* @internal
|
||||
* @see {ReactMount.unmountAndReleaseReactRootNode}
|
||||
*/
|
||||
unmountComponentFromNode: function(container) {
|
||||
this.unmountComponent();
|
||||
// http://jsperf.com/emptying-a-node
|
||||
while (container.lastChild) {
|
||||
container.removeChild(container.lastChild);
|
||||
}
|
||||
ReactComponent.mountImageIntoNode(markup, container, shouldReuseMarkup);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -581,9 +512,7 @@ var ReactComponent = {
|
||||
}
|
||||
return owner.refs[ref];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
};
|
||||
|
||||
module.exports = ReactComponent;
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule ReactComponentBrowserEnvironment
|
||||
*/
|
||||
|
||||
/*jslint evil: true */
|
||||
|
||||
"use strict";
|
||||
|
||||
var ReactDOMIDOperations = require('ReactDOMIDOperations');
|
||||
var ReactMarkupChecksum = require('ReactMarkupChecksum');
|
||||
var ReactMount = require('ReactMount');
|
||||
var ReactReconcileTransaction = require('ReactReconcileTransaction');
|
||||
|
||||
var getReactRootElementInContainer = require('getReactRootElementInContainer');
|
||||
var invariant = require('invariant');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Abstracts away all functionality of `ReactComponent` requires knowledge of
|
||||
* the browser context.
|
||||
*/
|
||||
var ReactComponentBrowserEnvironment = {
|
||||
/**
|
||||
* Mixed into every component instance.
|
||||
*/
|
||||
Mixin: {
|
||||
/**
|
||||
* Returns the DOM node rendered by this component.
|
||||
*
|
||||
* @return {DOMElement} The root node of this component.
|
||||
* @final
|
||||
* @protected
|
||||
*/
|
||||
getDOMNode: function() {
|
||||
invariant(
|
||||
this.isMounted(),
|
||||
'getDOMNode(): A component must be mounted to have a DOM node.'
|
||||
);
|
||||
return ReactMount.getNode(this._rootNodeID);
|
||||
}
|
||||
},
|
||||
|
||||
ReactReconcileTransaction: ReactReconcileTransaction,
|
||||
|
||||
DOMIDOperations: ReactDOMIDOperations,
|
||||
|
||||
/**
|
||||
* If a particular environment requires that some resources be cleaned up,
|
||||
* specify this in the injected Mixin. In the DOM, we would likely want to
|
||||
* purge any cached node ID lookups.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
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: function(markup, container, shouldReuseMarkup) {
|
||||
invariant(
|
||||
container && container.nodeType === 1,
|
||||
'mountComponentIntoNode(...): Target container is not a DOM element.'
|
||||
);
|
||||
if (shouldReuseMarkup) {
|
||||
if (ReactMarkupChecksum.canReuseMarkup(
|
||||
markup,
|
||||
getReactRootElementInContainer(container))) {
|
||||
return;
|
||||
} else {
|
||||
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.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Asynchronously inject markup by ensuring that the container is not in
|
||||
// the document when settings its `innerHTML`.
|
||||
var parent = container.parentNode;
|
||||
if (parent) {
|
||||
var next = container.nextSibling;
|
||||
parent.removeChild(container);
|
||||
container.innerHTML = markup;
|
||||
if (next) {
|
||||
parent.insertBefore(container, next);
|
||||
} else {
|
||||
parent.appendChild(container);
|
||||
}
|
||||
} else {
|
||||
container.innerHTML = markup;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ReactComponentBrowserEnvironment;
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule ReactComponentEnvironment
|
||||
*/
|
||||
|
||||
var ReactComponentBrowserEnvironment =
|
||||
require('ReactComponentBrowserEnvironment');
|
||||
|
||||
var ReactComponentEnvironment = ReactComponentBrowserEnvironment;
|
||||
|
||||
module.exports = ReactComponentEnvironment;
|
||||
@@ -44,7 +44,7 @@ var ReactEventEmitterMixin = {
|
||||
* there's a touch top-level listeners, anchors don't receive clicks for some
|
||||
* reason, and only in some cases).
|
||||
*
|
||||
* @param {*} Configuration passed through to `listenAtTopLevel`.
|
||||
* @param {*} config Configuration passed through to `listenAtTopLevel`.
|
||||
*/
|
||||
ensureListening: function(config) {
|
||||
if (!this._isListening) {
|
||||
|
||||
+21
-3
@@ -225,7 +225,7 @@ var ReactMount = {
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
prepareTopLevelEvents: function() {
|
||||
prepareEnvironmentForDOM: function() {
|
||||
ReactEventEmitter.ensureListening(ReactMount.useTouchEvents);
|
||||
},
|
||||
|
||||
@@ -262,7 +262,7 @@ var ReactMount = {
|
||||
* @return {string} reactRoot ID prefix
|
||||
*/
|
||||
_registerComponent: function(nextComponent, container) {
|
||||
ReactMount.prepareTopLevelEvents();
|
||||
ReactMount.prepareEnvironmentForDOM();
|
||||
|
||||
var reactRootID = ReactMount.registerContainer(container);
|
||||
instanceByReactRootID[reactRootID] = nextComponent;
|
||||
@@ -400,7 +400,7 @@ var ReactMount = {
|
||||
if (!component) {
|
||||
return false;
|
||||
}
|
||||
component.unmountComponentFromNode(container);
|
||||
ReactMount.unmountComponentFromNode(component, container);
|
||||
delete instanceByReactRootID[reactRootID];
|
||||
delete containersByReactRootID[reactRootID];
|
||||
if (__DEV__) {
|
||||
@@ -409,6 +409,24 @@ var ReactMount = {
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Unmounts a component and removes it from the DOM.
|
||||
*
|
||||
* @param {ReactComponent} instance React component instance.
|
||||
* @param {DOMElement} container DOM element to unmount from.
|
||||
* @final
|
||||
* @internal
|
||||
* @see {ReactMount.unmountAndReleaseReactRootNode}
|
||||
*/
|
||||
unmountComponentFromNode: function(instance, container) {
|
||||
instance.unmountComponent();
|
||||
|
||||
// http://jsperf.com/emptying-a-node
|
||||
while (container.lastChild) {
|
||||
container.removeChild(container.lastChild);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds the container DOM element that contains React component to which the
|
||||
* supplied DOM `id` belongs.
|
||||
|
||||
Reference in New Issue
Block a user