From e1f0d886531efbdbbbcc0ca17877b0e9249789ce Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 17 Aug 2016 00:22:31 +0100 Subject: [PATCH] Fix slow performance of PropTypes.oneOfType() on misses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../classic/types/ReactPropTypes.js | 40 +++++++++++++------ .../types/__tests__/ReactPropTypes-test.js | 3 -- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/isomorphic/classic/types/ReactPropTypes.js b/src/isomorphic/classic/types/ReactPropTypes.js index 51563fb7c8671..7b026a7b05950 100644 --- a/src/isomorphic/classic/types/ReactPropTypes.js +++ b/src/isomorphic/classic/types/ReactPropTypes.js @@ -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 = {}; @@ -145,12 +159,12 @@ function createChainableTypeChecker(validate) { var locationName = ReactPropTypeLocationNames[location]; if (isRequired) { if (props[propName] === null) { - return new Error( + return new PropTypeError( `The ${locationName} \`${propFullName}\` is marked as required ` + `in \`${componentName}\`, but its value is \`null\`.` ); } - return new Error( + return new PropTypeError( `The ${locationName} \`${propFullName}\` is marked as required in ` + `\`${componentName}\`, but its value is \`undefined\`.` ); @@ -191,7 +205,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}\`.` @@ -209,7 +223,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.` ); } @@ -217,7 +231,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.` ); @@ -246,7 +260,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.` ); @@ -262,7 +276,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}\`.` @@ -289,7 +303,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}.` ); @@ -300,7 +314,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.` ); } @@ -308,7 +322,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.` ); @@ -357,7 +371,7 @@ function createUnionTypeChecker(arrayOfTypeCheckers) { } var locationName = ReactPropTypeLocationNames[location]; - return new Error( + return new PropTypeError( `Invalid ${locationName} \`${propFullName}\` supplied to ` + `\`${componentName}\`.` ); @@ -369,7 +383,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.` ); @@ -385,7 +399,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\`.` ); diff --git a/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js b/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js index c7377e8992263..20b4f486b1621 100644 --- a/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js +++ b/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js @@ -232,8 +232,6 @@ describe('ReactPropTypes', function() { expectWarningInDevelopment(PropTypes.object.isRequired, null); expectWarningInDevelopment(PropTypes.object.isRequired, undefined); }); - - }); describe('Any type', function() { @@ -259,7 +257,6 @@ describe('ReactPropTypes', function() { expectWarningInDevelopment(PropTypes.any.isRequired, null); expectWarningInDevelopment(PropTypes.any.isRequired, undefined); }); - }); describe('ArrayOf Type', function() {