mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Add legacy methods to DOM components for compatibility
This commit is contained in:
@@ -274,11 +274,7 @@ function mountComponentIntoNode(
|
||||
var markup = ReactReconciler.mountComponent(
|
||||
componentInstance, rootID, transaction, context
|
||||
);
|
||||
if (typeof componentInstance._renderedComponent._currentElement.type ===
|
||||
'function') {
|
||||
// hax
|
||||
componentInstance._renderedComponent._isTopLevel = true;
|
||||
}
|
||||
componentInstance._renderedComponent._topLevelWrapper = componentInstance;
|
||||
ReactMount._mountImageIntoNode(markup, container, shouldReuseMarkup);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ var ReactDOMTextarea = require('ReactDOMTextarea');
|
||||
var ReactMount = require('ReactMount');
|
||||
var ReactMultiChild = require('ReactMultiChild');
|
||||
var ReactPerf = require('ReactPerf');
|
||||
var ReactUpdateQueue = require('ReactUpdateQueue');
|
||||
|
||||
var assign = require('Object.assign');
|
||||
var escapeTextContentForBrowser = require('escapeTextContentForBrowser');
|
||||
@@ -51,6 +52,122 @@ var STYLE = keyOf({style: null});
|
||||
|
||||
var ELEMENT_NODE_TYPE = 1;
|
||||
|
||||
var canDefineProperty = false;
|
||||
try {
|
||||
Object.defineProperty({}, 'test', {get: function() {}});
|
||||
canDefineProperty = true;
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
function getDeclarationErrorAddendum(internalInstance) {
|
||||
if (internalInstance) {
|
||||
var owner = internalInstance._currentElement._owner || null;
|
||||
if (owner) {
|
||||
var name = owner.getName();
|
||||
if (name) {
|
||||
return ' This DOM node was rendered by `' + name + '`.';
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
var legacyPropsDescriptor;
|
||||
if (__DEV__) {
|
||||
legacyPropsDescriptor = {
|
||||
props: {
|
||||
enumerable: false,
|
||||
get: function() {
|
||||
var component = this._reactInternalComponent;
|
||||
warning(
|
||||
false,
|
||||
'ReactDOMComponent: Do not access .props of a DOM node; instead, ' +
|
||||
'recreate the props as `render` did originally or read the DOM ' +
|
||||
'properties/attributes directly from this node (e.g., ' +
|
||||
'this.refs.box.className).%s',
|
||||
getDeclarationErrorAddendum(component)
|
||||
);
|
||||
return component._currentElement.props;
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function legacyGetDOMNode() {
|
||||
if (__DEV__) {
|
||||
var component = this._reactInternalComponent;
|
||||
warning(
|
||||
false,
|
||||
'ReactDOMComponent: Do not access .getDOMNode() of a DOM node; ' +
|
||||
'instead, use the node directly.%s',
|
||||
getDeclarationErrorAddendum(component)
|
||||
);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
function legacyIsMounted() {
|
||||
var component = this._reactInternalComponent;
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
false,
|
||||
'ReactDOMComponent: Do not access .isMounted() of a DOM node.%s',
|
||||
getDeclarationErrorAddendum(component)
|
||||
);
|
||||
}
|
||||
return !!component;
|
||||
}
|
||||
|
||||
function legacySetStateEtc() {
|
||||
if (__DEV__) {
|
||||
var component = this._reactInternalComponent;
|
||||
warning(
|
||||
false,
|
||||
'ReactDOMComponent: Do not access .setState(), .replaceState(), or ' +
|
||||
'.forceUpdate() of a DOM node. This is a no-op.%s',
|
||||
getDeclarationErrorAddendum(component)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function legacySetProps(partialProps, callback) {
|
||||
var component = this._reactInternalComponent;
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
false,
|
||||
'ReactDOMComponent: Do not access .setProps() of a DOM node. ' +
|
||||
'Instead, call React.render again at the top level.%s',
|
||||
getDeclarationErrorAddendum(component)
|
||||
);
|
||||
}
|
||||
if (!component) {
|
||||
return;
|
||||
}
|
||||
ReactUpdateQueue.enqueueSetPropsInternal(component, partialProps);
|
||||
if (callback) {
|
||||
ReactUpdateQueue.enqueueCallbackInternal(component, callback);
|
||||
}
|
||||
}
|
||||
|
||||
function legacyReplaceProps(partialProps, callback) {
|
||||
var component = this._reactInternalComponent;
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
false,
|
||||
'ReactDOMComponent: Do not access .replaceProps() of a DOM node. ' +
|
||||
'Instead, call React.render again at the top level.%s',
|
||||
getDeclarationErrorAddendum(component)
|
||||
);
|
||||
}
|
||||
if (!component) {
|
||||
return;
|
||||
}
|
||||
ReactUpdateQueue.enqueueReplacePropsInternal(component, partialProps);
|
||||
if (callback) {
|
||||
ReactUpdateQueue.enqueueCallbackInternal(component, callback);
|
||||
}
|
||||
}
|
||||
|
||||
var styleMutationWarning = {};
|
||||
|
||||
function checkAndWarnForMutatedStyle(style1, style2, component) {
|
||||
@@ -327,6 +444,8 @@ function ReactDOMComponent(tag) {
|
||||
this._previousStyleCopy = null;
|
||||
this._rootNodeID = null;
|
||||
this._wrapperState = null;
|
||||
this._topLevelWrapper = null;
|
||||
this._nodeWithLegacyProperties = null;
|
||||
}
|
||||
|
||||
ReactDOMComponent.displayName = 'ReactDOMComponent';
|
||||
@@ -589,6 +708,10 @@ ReactDOMComponent.Mixin = {
|
||||
processChildContext(context, this)
|
||||
);
|
||||
|
||||
if (!canDefineProperty && this._nodeWithLegacyProperties) {
|
||||
this._nodeWithLegacyProperties.props = nextProps;
|
||||
}
|
||||
|
||||
if (this._tag === 'select') {
|
||||
// <select> value update needs to occur after <option> children
|
||||
// reconciliation
|
||||
@@ -818,10 +941,41 @@ ReactDOMComponent.Mixin = {
|
||||
ReactComponentBrowserEnvironment.unmountIDFromEnvironment(this._rootNodeID);
|
||||
this._rootNodeID = null;
|
||||
this._wrapperState = null;
|
||||
if (this._nodeWithLegacyProperties) {
|
||||
var node = this._nodeWithLegacyProperties;
|
||||
node._reactInternalComponent = null;
|
||||
this._nodeWithLegacyProperties = null;
|
||||
}
|
||||
},
|
||||
|
||||
getPublicInstance: function() {
|
||||
return ReactMount.getNode(this._rootNodeID);
|
||||
if (!this._nodeWithLegacyProperties) {
|
||||
var node = ReactMount.getNode(this._rootNodeID);
|
||||
|
||||
node._reactInternalComponent = this;
|
||||
node.getDOMNode = legacyGetDOMNode;
|
||||
node.isMounted = legacyIsMounted;
|
||||
node.setState = legacySetStateEtc;
|
||||
node.replaceState = legacySetStateEtc;
|
||||
node.forceUpdate = legacySetStateEtc;
|
||||
node.setProps = legacySetProps;
|
||||
node.replaceProps = legacyReplaceProps;
|
||||
|
||||
if (__DEV__) {
|
||||
if (canDefineProperty) {
|
||||
Object.defineProperties(node, legacyPropsDescriptor);
|
||||
} else {
|
||||
// updateComponent will update this property on subsequent renders
|
||||
node.props = this._currentElement.props;
|
||||
}
|
||||
} else {
|
||||
// updateComponent will update this property on subsequent renders
|
||||
node.props = this._currentElement.props;
|
||||
}
|
||||
|
||||
this._nodeWithLegacyProperties = node;
|
||||
}
|
||||
return this._nodeWithLegacyProperties;
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
@@ -921,30 +921,77 @@ describe('ReactDOMComponent', function() {
|
||||
|
||||
it('warns when accessing properties on DOM components', function() {
|
||||
spyOn(console, 'error');
|
||||
var innerDiv;
|
||||
var Animal = React.createClass({
|
||||
render: function() {
|
||||
return <div ref="div">iguana</div>;
|
||||
},
|
||||
componentDidMount: function() {
|
||||
innerDiv = this.refs.div;
|
||||
|
||||
void this.refs.div.props;
|
||||
void this.refs.div.setProps;
|
||||
this.refs.div.setState();
|
||||
expect(this.refs.div.getDOMNode()).toBe(this.refs.div);
|
||||
expect(this.refs.div.isMounted()).toBe(true);
|
||||
},
|
||||
});
|
||||
ReactTestUtils.renderIntoDocument(<Animal />);
|
||||
var container = document.createElement('div');
|
||||
React.render(<Animal />, container);
|
||||
React.unmountComponentAtNode(container);
|
||||
expect(innerDiv.isMounted()).toBe(false);
|
||||
|
||||
expect(console.error.calls.length).toBe(5);
|
||||
expect(console.error.calls[0].args[0]).toBe(
|
||||
'Warning: ReactDOMComponent: Do not access .props of a DOM ' +
|
||||
'node; instead, recreate the props as `render` did originally or ' +
|
||||
'read the DOM properties/attributes directly from this node (e.g., ' +
|
||||
'this.refs.box.className). This DOM node was rendered by `Animal`.'
|
||||
);
|
||||
expect(console.error.calls[1].args[0]).toBe(
|
||||
'Warning: ReactDOMComponent: Do not access .setState(), ' +
|
||||
'.replaceState(), or .forceUpdate() of a DOM node. This is a no-op. ' +
|
||||
'This DOM node was rendered by `Animal`.'
|
||||
);
|
||||
expect(console.error.calls[2].args[0]).toBe(
|
||||
'Warning: ReactDOMComponent: Do not access .getDOMNode() of a DOM ' +
|
||||
'node; instead, use the node directly. This DOM node was ' +
|
||||
'rendered by `Animal`.'
|
||||
);
|
||||
expect(console.error.calls[3].args[0]).toBe(
|
||||
'Warning: ReactDOMComponent: Do not access .isMounted() of a DOM ' +
|
||||
'node. This DOM node was rendered by `Animal`.'
|
||||
);
|
||||
expect(console.error.calls[4].args[0]).toContain('isMounted');
|
||||
});
|
||||
|
||||
it('handles legacy setProps and replaceProps', function() {
|
||||
spyOn(console, 'error');
|
||||
var node = ReactTestUtils.renderIntoDocument(<div>rhinoceros</div>);
|
||||
|
||||
node.setProps({className: 'herbiverous'});
|
||||
expect(node.className).toBe('herbiverous');
|
||||
expect(node.textContent).toBe('rhinoceros');
|
||||
|
||||
node.replaceProps({className: 'invisible rhino'});
|
||||
expect(node.className).toBe('invisible rhino');
|
||||
expect(node.textContent).toBe('');
|
||||
|
||||
expect(console.error.calls.length).toBe(2);
|
||||
expect(console.error.calls[0].args[0]).toBe(
|
||||
'Warning: ReactDOMComponent.props: Do not access .props of a DOM ' +
|
||||
'component directly; instead, recreate the props as `render` did ' +
|
||||
'originally or use React.findDOMNode and read the DOM ' +
|
||||
'properties/attributes directly. This DOM component was rendered ' +
|
||||
'by `Animal`.'
|
||||
'Warning: ReactDOMComponent: Do not access .setProps() of a DOM node. ' +
|
||||
'Instead, call React.render again at the top level.'
|
||||
);
|
||||
expect(console.error.calls[1].args[0]).toBe(
|
||||
'Warning: ReactDOMComponent.setProps(): Do not access .setProps() of ' +
|
||||
'a DOM component. This DOM component was rendered by `Animal`.'
|
||||
'Warning: ReactDOMComponent: Do not access .replaceProps() of a DOM ' +
|
||||
'node. Instead, call React.render again at the top level.'
|
||||
);
|
||||
});
|
||||
|
||||
it('does not touch ref-less nodes', function() {
|
||||
var node = ReactTestUtils.renderIntoDocument(<div><span /></div>);
|
||||
expect(typeof node.getDOMNode).toBe('function');
|
||||
expect(typeof node.firstChild.getDOMNode).toBe('undefined');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -99,7 +99,7 @@ var ReactCompositeComponentMixin = {
|
||||
|
||||
this._context = null;
|
||||
this._mountOrder = 0;
|
||||
this._isTopLevel = false;
|
||||
this._topLevelWrapper = null;
|
||||
|
||||
// See ReactUpdates and ReactUpdateQueue.
|
||||
this._pendingCallbacks = null;
|
||||
@@ -277,6 +277,7 @@ var ReactCompositeComponentMixin = {
|
||||
// longer accessible.
|
||||
this._context = null;
|
||||
this._rootNodeID = null;
|
||||
this._topLevelWrapper = null;
|
||||
|
||||
// Delete the reference from the instance to this internal representation
|
||||
// which allow the internals to be properly cleaned up even if the user
|
||||
|
||||
@@ -245,13 +245,16 @@ var ReactUpdateQueue = {
|
||||
publicInstance,
|
||||
'setProps'
|
||||
);
|
||||
|
||||
if (!internalInstance) {
|
||||
return;
|
||||
}
|
||||
ReactUpdateQueue.enqueueSetPropsInternal(internalInstance, partialProps);
|
||||
},
|
||||
|
||||
enqueueSetPropsInternal: function(internalInstance, partialProps) {
|
||||
var topLevelWrapper = internalInstance._topLevelWrapper;
|
||||
invariant(
|
||||
internalInstance._isTopLevel,
|
||||
topLevelWrapper,
|
||||
'setProps(...): You called `setProps` on a ' +
|
||||
'component with a parent. This is an anti-pattern since props will ' +
|
||||
'get reactively updated when rendered. Instead, change the owner\'s ' +
|
||||
@@ -261,15 +264,16 @@ var ReactUpdateQueue = {
|
||||
|
||||
// Merge with the pending element if it exists, otherwise with existing
|
||||
// element props.
|
||||
var element = internalInstance._pendingElement ||
|
||||
internalInstance._currentElement;
|
||||
var wrapElement = topLevelWrapper._pendingElement ||
|
||||
topLevelWrapper._currentElement;
|
||||
var element = wrapElement.props;
|
||||
var props = assign({}, element.props, partialProps);
|
||||
internalInstance._pendingElement = ReactElement.cloneAndReplaceProps(
|
||||
element,
|
||||
props
|
||||
topLevelWrapper._pendingElement = ReactElement.cloneAndReplaceProps(
|
||||
wrapElement,
|
||||
ReactElement.cloneAndReplaceProps(element, props)
|
||||
);
|
||||
|
||||
enqueueUpdate(internalInstance);
|
||||
enqueueUpdate(topLevelWrapper);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -284,13 +288,16 @@ var ReactUpdateQueue = {
|
||||
publicInstance,
|
||||
'replaceProps'
|
||||
);
|
||||
|
||||
if (!internalInstance) {
|
||||
return;
|
||||
}
|
||||
ReactUpdateQueue.enqueueReplacePropsInternal(internalInstance, props);
|
||||
},
|
||||
|
||||
enqueueReplacePropsInternal: function(internalInstance, props) {
|
||||
var topLevelWrapper = internalInstance._topLevelWrapper;
|
||||
invariant(
|
||||
internalInstance._isTopLevel,
|
||||
topLevelWrapper,
|
||||
'replaceProps(...): You called `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 ' +
|
||||
@@ -300,14 +307,15 @@ var ReactUpdateQueue = {
|
||||
|
||||
// Merge with the pending element if it exists, otherwise with existing
|
||||
// element props.
|
||||
var element = internalInstance._pendingElement ||
|
||||
internalInstance._currentElement;
|
||||
internalInstance._pendingElement = ReactElement.cloneAndReplaceProps(
|
||||
element,
|
||||
props
|
||||
var wrapElement = topLevelWrapper._pendingElement ||
|
||||
topLevelWrapper._currentElement;
|
||||
var element = wrapElement.props;
|
||||
topLevelWrapper._pendingElement = ReactElement.cloneAndReplaceProps(
|
||||
wrapElement,
|
||||
ReactElement.cloneAndReplaceProps(element, props)
|
||||
);
|
||||
|
||||
enqueueUpdate(internalInstance);
|
||||
enqueueUpdate(topLevelWrapper);
|
||||
},
|
||||
|
||||
enqueueElementInternal: function(internalInstance, newElement) {
|
||||
|
||||
@@ -267,14 +267,19 @@ describe('ReactComponent', function() {
|
||||
it('warns when calling getDOMNode', function() {
|
||||
spyOn(console, 'error');
|
||||
|
||||
var Potato = React.createClass({
|
||||
render: function() {
|
||||
return <div />;
|
||||
},
|
||||
});
|
||||
var container = document.createElement('div');
|
||||
var instance = React.render(<div />, container);
|
||||
var instance = React.render(<Potato />, container);
|
||||
|
||||
instance.getDOMNode();
|
||||
|
||||
expect(console.error.calls.length).toBe(1);
|
||||
expect(console.error.calls[0].args[0]).toContain(
|
||||
'DIV.getDOMNode(...) is deprecated. Please use ' +
|
||||
'Potato.getDOMNode(...) is deprecated. Please use ' +
|
||||
'React.findDOMNode(instance) instead.'
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user