Fix slow performance of PropTypes.oneOfType() on misses (#7510)

It used to be slow whenever a type miss occurred because expensive `Error` objects were being created. For example, with `oneOfType([number, data])`, passing a date would create an `Error` object in `number` typechecker for every item.

The savings depend on how much commonly you used `oneOfType()`, and how often it had “misses”. If you used it heavily, you might see 1.5x to 2x performance improvements in `__DEV__` after this fix.
(cherry picked from commit 680685bec4)
This commit is contained in:
Dan Abramov
2016-08-19 10:44:58 -07:00
committed by Paul O’Shannessy
parent 9e88467485
commit 536f826b56
2 changed files with 26 additions and 15 deletions
+26 -12
View File
@@ -105,6 +105,20 @@ function is(x, y) {
}
/*eslint-enable no-self-compare*/
/**
* We use an Error-like object for backward compatibility as people may call
* PropTypes directly and inspect their output. However we don't use real
* Errors anymore. We don't inspect their stack anyway, and creating them
* is prohibitively expensive if they are created too often, such as what
* happens in oneOfType() for any type before the one that matched.
*/
function PropTypeError(message) {
this.message = message;
this.stack = '';
}
// Make `instanceof Error` still work for returned errors.
PropTypeError.prototype = Error.prototype;
function createChainableTypeChecker(validate) {
if (__DEV__) {
var manualPropTypeCallCache = {};
@@ -144,7 +158,7 @@ function createChainableTypeChecker(validate) {
if (props[propName] == null) {
var locationName = ReactPropTypeLocationNames[location];
if (isRequired) {
return new Error(
return new PropTypeError(
`Required ${locationName} \`${propFullName}\` was not specified in ` +
`\`${componentName}\`.`
);
@@ -185,7 +199,7 @@ function createPrimitiveTypeChecker(expectedType) {
// 'of type `object`'.
var preciseType = getPreciseType(propValue);
return new Error(
return new PropTypeError(
`Invalid ${locationName} \`${propFullName}\` of type ` +
`\`${preciseType}\` supplied to \`${componentName}\`, expected ` +
`\`${expectedType}\`.`
@@ -203,7 +217,7 @@ function createAnyTypeChecker() {
function createArrayOfTypeChecker(typeChecker) {
function validate(props, propName, componentName, location, propFullName) {
if (typeof typeChecker !== 'function') {
return new Error(
return new PropTypeError(
`Property \`${propFullName}\` of component \`${componentName}\` has invalid PropType notation inside arrayOf.`
);
}
@@ -211,7 +225,7 @@ function createArrayOfTypeChecker(typeChecker) {
if (!Array.isArray(propValue)) {
var locationName = ReactPropTypeLocationNames[location];
var propType = getPropType(propValue);
return new Error(
return new PropTypeError(
`Invalid ${locationName} \`${propFullName}\` of type ` +
`\`${propType}\` supplied to \`${componentName}\`, expected an array.`
);
@@ -240,7 +254,7 @@ function createElementTypeChecker() {
if (!ReactElement.isValidElement(propValue)) {
var locationName = ReactPropTypeLocationNames[location];
var propType = getPropType(propValue);
return new Error(
return new PropTypeError(
`Invalid ${locationName} \`${propFullName}\` of type ` +
`\`${propType}\` supplied to \`${componentName}\`, expected a single ReactElement.`
);
@@ -256,7 +270,7 @@ function createInstanceTypeChecker(expectedClass) {
var locationName = ReactPropTypeLocationNames[location];
var expectedClassName = expectedClass.name || ANONYMOUS;
var actualClassName = getClassName(props[propName]);
return new Error(
return new PropTypeError(
`Invalid ${locationName} \`${propFullName}\` of type ` +
`\`${actualClassName}\` supplied to \`${componentName}\`, expected ` +
`instance of \`${expectedClassName}\`.`
@@ -283,7 +297,7 @@ function createEnumTypeChecker(expectedValues) {
var locationName = ReactPropTypeLocationNames[location];
var valuesString = JSON.stringify(expectedValues);
return new Error(
return new PropTypeError(
`Invalid ${locationName} \`${propFullName}\` of value \`${propValue}\` ` +
`supplied to \`${componentName}\`, expected one of ${valuesString}.`
);
@@ -294,7 +308,7 @@ function createEnumTypeChecker(expectedValues) {
function createObjectOfTypeChecker(typeChecker) {
function validate(props, propName, componentName, location, propFullName) {
if (typeof typeChecker !== 'function') {
return new Error(
return new PropTypeError(
`Property \`${propFullName}\` of component \`${componentName}\` has invalid PropType notation inside objectOf.`
);
}
@@ -302,7 +316,7 @@ function createObjectOfTypeChecker(typeChecker) {
var propType = getPropType(propValue);
if (propType !== 'object') {
var locationName = ReactPropTypeLocationNames[location];
return new Error(
return new PropTypeError(
`Invalid ${locationName} \`${propFullName}\` of type ` +
`\`${propType}\` supplied to \`${componentName}\`, expected an object.`
);
@@ -351,7 +365,7 @@ function createUnionTypeChecker(arrayOfTypeCheckers) {
}
var locationName = ReactPropTypeLocationNames[location];
return new Error(
return new PropTypeError(
`Invalid ${locationName} \`${propFullName}\` supplied to ` +
`\`${componentName}\`.`
);
@@ -363,7 +377,7 @@ function createNodeChecker() {
function validate(props, propName, componentName, location, propFullName) {
if (!isNode(props[propName])) {
var locationName = ReactPropTypeLocationNames[location];
return new Error(
return new PropTypeError(
`Invalid ${locationName} \`${propFullName}\` supplied to ` +
`\`${componentName}\`, expected a ReactNode.`
);
@@ -379,7 +393,7 @@ function createShapeTypeChecker(shapeTypes) {
var propType = getPropType(propValue);
if (propType !== 'object') {
var locationName = ReactPropTypeLocationNames[location];
return new Error(
return new PropTypeError(
`Invalid ${locationName} \`${propFullName}\` of type \`${propType}\` ` +
`supplied to \`${componentName}\`, expected \`object\`.`
);
@@ -195,8 +195,6 @@ describe('ReactPropTypes', function() {
expectWarningInDevelopment(PropTypes.object.isRequired, null);
expectWarningInDevelopment(PropTypes.object.isRequired, undefined);
});
});
describe('Any type', function() {
@@ -223,7 +221,6 @@ describe('ReactPropTypes', function() {
expectWarningInDevelopment(PropTypes.any.isRequired, null);
expectWarningInDevelopment(PropTypes.any.isRequired, undefined);
});
});
describe('ArrayOf Type', function() {