DOM components as refs

Still missing: .props/.getDOMNode warnings.
This commit is contained in:
Ben Alpert
2015-06-15 19:16:30 -07:00
parent 643651b8e5
commit ffd527f593
12 changed files with 35 additions and 155 deletions
@@ -23,7 +23,6 @@ var ReactFragment = require('ReactFragment');
var ReactPropTypeLocations = require('ReactPropTypeLocations');
var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
var ReactCurrentOwner = require('ReactCurrentOwner');
var ReactNativeComponent = require('ReactNativeComponent');
var getIteratorFn = require('getIteratorFn');
var invariant = require('invariant');
@@ -293,18 +292,10 @@ function checkPropTypes(componentName, propTypes, props, location) {
* @param {ReactElement} element
*/
function validatePropTypes(element) {
if (!(typeof element.type === 'string' ||
typeof element.type === 'function')) {
// This has already warned. Don't throw.
var componentClass = element.type;
if (typeof componentClass !== 'function') {
return;
}
// Extract the component class from the element. Converts string types
// to a composite class which may have propTypes.
// TODO: Validating a string's propTypes is not decoupled from the
// rendering target which is problematic.
var componentClass = ReactNativeComponent.getComponentClassForElement(
element
);
var name = componentClass.displayName || componentClass.name;
if (componentClass.propTypes) {
checkPropTypes(
+1 -2
View File
@@ -17,7 +17,6 @@ var ReactInstanceMap = require('ReactInstanceMap');
var ReactMount = require('ReactMount');
var invariant = require('invariant');
var isNode = require('isNode');
var warning = require('warning');
/**
@@ -45,7 +44,7 @@ function findDOMNode(componentOrElement) {
if (componentOrElement == null) {
return null;
}
if (isNode(componentOrElement)) {
if (componentOrElement.nodeType === 1) {
return componentOrElement;
}
if (ReactInstanceMap.has(componentOrElement)) {
@@ -820,6 +820,10 @@ ReactDOMComponent.Mixin = {
this._wrapperState = null;
},
getPublicInstance: function() {
return ReactMount.getNode(this._rootNodeID);
},
};
ReactPerf.measureMethods(ReactDOMComponent, 'ReactDOMComponent', {
@@ -19,18 +19,15 @@ var EnterLeaveEventPlugin = require('EnterLeaveEventPlugin');
var ExecutionEnvironment = require('ExecutionEnvironment');
var HTMLDOMPropertyConfig = require('HTMLDOMPropertyConfig');
var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
var ReactClass = require('ReactClass');
var ReactComponentBrowserEnvironment =
require('ReactComponentBrowserEnvironment');
var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy');
var ReactDOMComponent = require('ReactDOMComponent');
var ReactDOMIDOperations = require('ReactDOMIDOperations');
var ReactDOMTextComponent = require('ReactDOMTextComponent');
var ReactElement = require('ReactElement');
var ReactEventListener = require('ReactEventListener');
var ReactInjection = require('ReactInjection');
var ReactInstanceHandles = require('ReactInstanceHandles');
var ReactInstanceMap = require('ReactInstanceMap');
var ReactMount = require('ReactMount');
var ReactReconcileTransaction = require('ReactReconcileTransaction');
var SelectEventPlugin = require('SelectEventPlugin');
@@ -38,91 +35,6 @@ var ServerReactRootIndex = require('ServerReactRootIndex');
var SimpleEventPlugin = require('SimpleEventPlugin');
var SVGDOMPropertyConfig = require('SVGDOMPropertyConfig');
var warning = require('warning');
var canDefineProperty = false;
try {
Object.defineProperty({}, 'test', {get: function() {}});
canDefineProperty = true;
} catch (e) {
}
var deprecatedDOMMethods = [
'isMounted', 'replaceProps', 'replaceState', 'setProps', 'setState',
'forceUpdate',
];
function getDeclarationErrorAddendum(domWrapperClass) {
var internalInstance = ReactInstanceMap.get(domWrapperClass);
var owner = internalInstance._currentElement._owner || null;
if (owner) {
var name = owner.getName();
if (name) {
return ' This DOM component was rendered by `' + name + '`.';
}
}
return '';
}
function autoGenerateWrapperClass(type) {
var wrapperClass = ReactClass.createClass({
tagName: type.toUpperCase(),
render: function() {
// Copy owner down for debugging info
var internalInstance = ReactInstanceMap.get(this);
return new ReactElement(
type,
null, // key
null, // ref
internalInstance._currentElement._owner, // owner
this._internalProps || this.props
);
},
});
if (__DEV__) {
if (canDefineProperty) {
Object.defineProperty(wrapperClass.prototype, 'props', {
enumerable: true,
set: function(props) {
this._internalProps = props;
},
get: function() {
warning(
false,
'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.%s',
getDeclarationErrorAddendum(this)
);
return this._internalProps;
},
});
deprecatedDOMMethods.forEach(function(method) {
var old = wrapperClass.prototype[method];
Object.defineProperty(wrapperClass.prototype, method, {
enumerable: true,
get: function() {
warning(
false,
'ReactDOMComponent.%s(): Do not access .%s() of a DOM ' +
'component.%s',
method,
method,
getDeclarationErrorAddendum(this)
);
return old;
},
});
});
}
}
return wrapperClass;
}
var alreadyInjected = false;
function inject() {
@@ -165,10 +77,6 @@ function inject() {
ReactDOMTextComponent
);
ReactInjection.NativeComponent.injectAutoWrapper(
autoGenerateWrapperClass
);
ReactInjection.Class.injectMixin(ReactBrowserComponentMixin);
ReactInjection.DOMProperty.injectDOMPropertyConfig(HTMLDOMPropertyConfig);
@@ -805,12 +805,12 @@ describe('ReactDOMComponent', function() {
it('warns on invalid nesting at root', () => {
spyOn(console, 'error');
var p = document.createElement('p');
React.render(<tr />, p);
React.render(<span><p /></span>, p);
expect(console.error.calls.length).toBe(1);
expect(console.error.calls[0].args[0]).toBe(
'Warning: validateDOMNesting(...): <tr> cannot appear as a child of ' +
'<p>. See p > tr.'
'Warning: validateDOMNesting(...): <p> cannot appear as a descendant ' +
'of <p>. See p > ... > p.'
);
});
@@ -15,7 +15,6 @@ var ReactComponentEnvironment = require('ReactComponentEnvironment');
var ReactCurrentOwner = require('ReactCurrentOwner');
var ReactElement = require('ReactElement');
var ReactInstanceMap = require('ReactInstanceMap');
var ReactNativeComponent = require('ReactNativeComponent');
var ReactPerf = require('ReactPerf');
var ReactPropTypeLocations = require('ReactPropTypeLocations');
var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
@@ -123,9 +122,7 @@ var ReactCompositeComponentMixin = {
var publicProps = this._processProps(this._currentElement.props);
var publicContext = this._processContext(context);
var Component = ReactNativeComponent.getComponentClassForElement(
this._currentElement
);
var Component = this._currentElement.type;
// Initialize the public class
var inst = new Component(publicProps, publicContext);
@@ -235,8 +232,7 @@ var ReactCompositeComponentMixin = {
var renderedElement = this._renderValidatedComponent();
this._renderedComponent = this._instantiateReactComponent(
renderedElement,
this._currentElement.type // The wrapping type
renderedElement
);
var markup = ReactReconciler.mountComponent(
@@ -304,9 +300,7 @@ var ReactCompositeComponentMixin = {
*/
_maskContext: function(context) {
var maskedContext = null;
var Component = ReactNativeComponent.getComponentClassForElement(
this._currentElement
);
var Component = this._currentElement.type;
var contextTypes = Component.contextTypes;
if (!contextTypes) {
return emptyObject;
@@ -329,9 +323,7 @@ var ReactCompositeComponentMixin = {
_processContext: function(context) {
var maskedContext = this._maskContext(context);
if (__DEV__) {
var Component = ReactNativeComponent.getComponentClassForElement(
this._currentElement
);
var Component = this._currentElement.type;
if (Component.contextTypes) {
this._checkPropTypes(
Component.contextTypes,
@@ -349,9 +341,7 @@ var ReactCompositeComponentMixin = {
* @private
*/
_processChildContext: function(currentContext) {
var Component = ReactNativeComponent.getComponentClassForElement(
this._currentElement
);
var Component = this._currentElement.type;
var inst = this._instance;
var childContext = inst.getChildContext && inst.getChildContext();
if (childContext) {
@@ -392,9 +382,7 @@ var ReactCompositeComponentMixin = {
*/
_processProps: function(newProps) {
if (__DEV__) {
var Component = ReactNativeComponent.getComponentClassForElement(
this._currentElement
);
var Component = this._currentElement.type;
if (Component.propTypes) {
this._checkPropTypes(
Component.propTypes,
@@ -695,8 +683,7 @@ var ReactCompositeComponentMixin = {
ReactReconciler.unmountComponent(prevComponentInstance);
this._renderedComponent = this._instantiateReactComponent(
nextRenderedElement,
this._currentElement.type
nextRenderedElement
);
var nextMarkup = ReactReconciler.mountComponent(
this._renderedComponent,
@@ -36,11 +36,6 @@ var ReactNativeComponentInjection = {
injectComponentClasses: function(componentClasses) {
assign(tagToComponentClass, componentClasses);
},
// Temporary hack since we expect DOM refs to behave like composites,
// for this release.
injectAutoWrapper: function(wrapperFactory) {
autoGenerateWrapperClass = wrapperFactory;
},
};
/**
@@ -12,7 +12,6 @@
'use strict';
var React = require('React');
var ReactInstanceMap = require('ReactInstanceMap');
var ReactTestUtils = require('ReactTestUtils');
var ReactMount = require('ReactMount');
@@ -63,11 +62,11 @@ describe('ReactInstanceHandles', function() {
});
}
function getNodeID(instance) {
if (instance === null) {
function getNodeID(el) {
if (el === null) {
return '';
}
return ReactInstanceMap.get(instance)._rootNodeID;
return el.getAttribute('data-reactid');
}
beforeEach(function() {
@@ -389,8 +388,8 @@ describe('ReactInstanceHandles', function() {
// Common ancestor with grandparent is the grandparent.
{
one: parent.refs.P_P1_C1.refs.DIV_1,
two: parent.refs.P_P1_C1,
com: parent.refs.P_P1_C1,
two: parent.refs.P_P1,
com: parent.refs.P_P1,
},
// Grantparent across subcomponent boundaries.
{
@@ -89,7 +89,6 @@ var FriendsStatusDisplay = React.createClass({
var statusDisplays =
ReactInstanceMap.get(this)
._renderedComponent
._renderedComponent
._renderedChildren;
for (name in statusDisplays) {
var child = statusDisplays[name];
@@ -60,11 +60,10 @@ function isInternalComponentType(type) {
* Given a ReactNode, create an instance that will actually be mounted.
*
* @param {ReactNode} node
* @param {*} parentCompositeType The composite type that resolved this.
* @return {object} A new instance of the element's constructor.
* @protected
*/
function instantiateReactComponent(node, parentCompositeType) {
function instantiateReactComponent(node) {
var instance;
if (node === null || node === false) {
@@ -83,12 +82,8 @@ function instantiateReactComponent(node, parentCompositeType) {
);
// Special case string values
if (parentCompositeType === element.type &&
typeof element.type === 'string') {
// Avoid recursion if the wrapper renders itself.
if (typeof element.type === 'string') {
instance = ReactNativeComponent.createInternalComponent(element);
// All native components are currently wrapped in a composite so we're
// safe to assume that this is what we should instantiate.
} else if (isInternalComponentType(element.type)) {
// This is temporarily available for custom components that are not string
// represenations. I.e. ART. Once those are updated to use the string
+8 -5
View File
@@ -45,7 +45,7 @@ function findAllInRenderedTreeInternal(inst, test) {
var publicInst = inst.getPublicInstance()
var ret = test(publicInst) ? [publicInst] : [];
if (ReactTestUtils.isDOMComponent(publicInst)) {
var renderedChildren = inst._renderedComponent._renderedChildren;
var renderedChildren = inst._renderedChildren;
var key;
for (key in renderedChildren) {
if (!renderedChildren.hasOwnProperty(key)) {
@@ -96,7 +96,7 @@ var ReactTestUtils = {
isDOMComponent: function(inst) {
// TODO: Fix this heuristic. It's just here because composites can currently
// pretend to be DOM components.
return !!(inst && inst.tagName && inst.getDOMNode);
return !!(inst && inst.nodeType === 1 && inst.tagName);
},
isDOMComponentElement: function(inst) {
@@ -116,13 +116,15 @@ var ReactTestUtils = {
},
isCompositeComponentWithType: function(inst, type) {
if (!ReactTestUtils.isCompositeComponent(inst)) {
return false;
}
var internalInstance = ReactInstanceMap.get(inst);
var constructor = internalInstance
._currentElement
.type;
return !!(ReactTestUtils.isCompositeComponent(inst) &&
(constructor === type));
return (constructor === type);
},
isCompositeComponentElement: function(inst) {
@@ -256,7 +258,8 @@ var ReactTestUtils = {
);
if (all.length !== 1) {
throw new Error(
'Did not find exactly one match for componentType:' + componentType
'Did not find exactly one match for componentType:' + componentType +
' (found ' + all.length + ')'
);
}
return all[0];
+3 -3
View File
@@ -85,7 +85,7 @@ assign(reactComponentExpectInternal.prototype, {
// change soon.
this.toBeDOMComponent();
var renderedChildren =
this._instance._renderedComponent._renderedChildren || {};
this._instance._renderedChildren || {};
for (var name in renderedChildren) {
if (!renderedChildren.hasOwnProperty(name)) {
continue;
@@ -101,7 +101,7 @@ assign(reactComponentExpectInternal.prototype, {
toBeDOMComponentWithChildCount: function(count) {
this.toBeDOMComponent();
var renderedChildren = this._instance._renderedComponent._renderedChildren;
var renderedChildren = this._instance._renderedChildren;
expect(renderedChildren).toBeTruthy();
expect(Object.keys(renderedChildren).length).toBe(count);
return this;
@@ -109,7 +109,7 @@ assign(reactComponentExpectInternal.prototype, {
toBeDOMComponentWithNoChildren: function() {
this.toBeDOMComponent();
expect(this._instance._renderedComponent._renderedChildren).toBeFalsy();
expect(this._instance._renderedChildren).toBeFalsy();
return this;
},