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.
Dan Abramov committed
Aug 17, 2016 at 10:04 UTC
680685bec4f260fe635c66297fc3fb38a5116e07
2 files changed
+27
-16
src/isomorphic/classic/types/ReactPropTypes.js
+27
-13
@@ -105,6 +105,20 @@ function is(x, y) {
105
}
106
/*eslint-enable no-self-compare*/
107
108
+/**
109
+ * We use an Error-like object for backward compatibility as people may call
110
+ * PropTypes directly and inspect their output. However we don't use real
111
+ * Errors anymore. We don't inspect their stack anyway, and creating them
112
+ * is prohibitively expensive if they are created too often, such as what
113
+ * happens in oneOfType() for any type before the one that matched.
114
+ */
115
+function PropTypeError(message) {
116
+ this.message = message;
117
+ this.stack = '';
118
+}
119
+// Make `instanceof Error` still work for returned errors.
120
+PropTypeError.prototype = Error.prototype;
121
+
122
function createChainableTypeChecker(validate) {
123
if (__DEV__) {
124
var manualPropTypeCallCache = {};
@@ -145,12 +159,12 @@ function createChainableTypeChecker(validate) {
159
var locationName = ReactPropTypeLocationNames[location];
160
if (isRequired) {
161
if (props[propName] === null) {
148
- return new Error(
162
+ return new PropTypeError(
163
`The ${locationName} \`${propFullName}\` is marked as required ` +
164
`in \`${componentName}\`, but its value is \`null\`.`
165
);
166
}
153
- return new Error(
167
+ return new PropTypeError(
168
`The ${locationName} \`${propFullName}\` is marked as required in ` +
169
`\`${componentName}\`, but its value is \`undefined\`.`
170
);
@@ -191,7 +205,7 @@ function createPrimitiveTypeChecker(expectedType) {
205
// 'of type `object`'.
206
var preciseType = getPreciseType(propValue);
207
194
- return new Error(
208
+ return new PropTypeError(
209
`Invalid ${locationName} \`${propFullName}\` of type ` +
210
`\`${preciseType}\` supplied to \`${componentName}\`, expected ` +
211
`\`${expectedType}\`.`
@@ -209,7 +223,7 @@ function createAnyTypeChecker() {
223
function createArrayOfTypeChecker(typeChecker) {
224
function validate(props, propName, componentName, location, propFullName) {
225
if (typeof typeChecker !== 'function') {
212
- return new Error(
226
+ return new PropTypeError(
227
`Property \`${propFullName}\` of component \`${componentName}\` has invalid PropType notation inside arrayOf.`
228
);
229
}
@@ -217,7 +231,7 @@ function createArrayOfTypeChecker(typeChecker) {
231
if (!Array.isArray(propValue)) {
232
var locationName = ReactPropTypeLocationNames[location];
233
var propType = getPropType(propValue);
220
- return new Error(
234
+ return new PropTypeError(
235
`Invalid ${locationName} \`${propFullName}\` of type ` +
236
`\`${propType}\` supplied to \`${componentName}\`, expected an array.`
237
);
@@ -246,7 +260,7 @@ function createElementTypeChecker() {
260
if (!ReactElement.isValidElement(propValue)) {
261
var locationName = ReactPropTypeLocationNames[location];
262
var propType = getPropType(propValue);
249
- return new Error(
263
+ return new PropTypeError(
264
`Invalid ${locationName} \`${propFullName}\` of type ` +
265
`\`${propType}\` supplied to \`${componentName}\`, expected a single ReactElement.`
266
);
@@ -262,7 +276,7 @@ function createInstanceTypeChecker(expectedClass) {
276
var locationName = ReactPropTypeLocationNames[location];
277
var expectedClassName = expectedClass.name || ANONYMOUS;
278
var actualClassName = getClassName(props[propName]);
265
- return new Error(
279
+ return new PropTypeError(
280
`Invalid ${locationName} \`${propFullName}\` of type ` +
281
`\`${actualClassName}\` supplied to \`${componentName}\`, expected ` +
282
`instance of \`${expectedClassName}\`.`
@@ -289,7 +303,7 @@ function createEnumTypeChecker(expectedValues) {
303
304
var locationName = ReactPropTypeLocationNames[location];
305
var valuesString = JSON.stringify(expectedValues);
292
- return new Error(
306
+ return new PropTypeError(
307
`Invalid ${locationName} \`${propFullName}\` of value \`${propValue}\` ` +
308
`supplied to \`${componentName}\`, expected one of ${valuesString}.`
309
);
@@ -300,7 +314,7 @@ function createEnumTypeChecker(expectedValues) {
314
function createObjectOfTypeChecker(typeChecker) {
315
function validate(props, propName, componentName, location, propFullName) {
316
if (typeof typeChecker !== 'function') {
303
- return new Error(
317
+ return new PropTypeError(
318
`Property \`${propFullName}\` of component \`${componentName}\` has invalid PropType notation inside objectOf.`
319
);
320
}
@@ -308,7 +322,7 @@ function createObjectOfTypeChecker(typeChecker) {
322
var propType = getPropType(propValue);
323
if (propType !== 'object') {
324
var locationName = ReactPropTypeLocationNames[location];
311
- return new Error(
325
+ return new PropTypeError(
326
`Invalid ${locationName} \`${propFullName}\` of type ` +
327
`\`${propType}\` supplied to \`${componentName}\`, expected an object.`
328
);
@@ -357,7 +371,7 @@ function createUnionTypeChecker(arrayOfTypeCheckers) {
371
}
372
373
var locationName = ReactPropTypeLocationNames[location];
360
- return new Error(
374
+ return new PropTypeError(
375
`Invalid ${locationName} \`${propFullName}\` supplied to ` +
376
`\`${componentName}\`.`
377
);
@@ -369,7 +383,7 @@ function createNodeChecker() {
383
function validate(props, propName, componentName, location, propFullName) {
384
if (!isNode(props[propName])) {
385
var locationName = ReactPropTypeLocationNames[location];
372
- return new Error(
386
+ return new PropTypeError(
387
`Invalid ${locationName} \`${propFullName}\` supplied to ` +
388
`\`${componentName}\`, expected a ReactNode.`
389
);
@@ -385,7 +399,7 @@ function createShapeTypeChecker(shapeTypes) {
399
var propType = getPropType(propValue);
400
if (propType !== 'object') {
401
var locationName = ReactPropTypeLocationNames[location];
388
- return new Error(
402
+ return new PropTypeError(
403
`Invalid ${locationName} \`${propFullName}\` of type \`${propType}\` ` +
404
`supplied to \`${componentName}\`, expected \`object\`.`
405
);
src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js
-3
@@ -232,8 +232,6 @@ describe('ReactPropTypes', function() {
232
expectWarningInDevelopment(PropTypes.object.isRequired, null);
233
expectWarningInDevelopment(PropTypes.object.isRequired, undefined);
234
});
235
-
236
-
235
});
236
237
describe('Any type', function() {
@@ -259,7 +257,6 @@ describe('ReactPropTypes', function() {
257
expectWarningInDevelopment(PropTypes.any.isRequired, null);
258
expectWarningInDevelopment(PropTypes.any.isRequired, undefined);
259
});
262
-
260
});
261
262
describe('ArrayOf Type', function() {