React有很多使用PropTypes来检查道具价值的方法。我通常使用的是React.PropTypes.shape({...})。但是,最近我遇到一种情况,其中我有一个对象,该对象内部将具有动态键/值。我知道每个键都应该是一个字符串(采用已知格式),每个值都应该是一个整数。即使使用自定义道具验证功能,它仍然假设您知道道具的钥匙。如何使用PropTypes检查对象/形状的键和值是否正确?
React.PropTypes.shape({...})
... someArray: React.PropTypes.arrayOf(React.PropTypes.shape({ // How to specify a dynamic string key? Keys are a date/datetime string <dynamicStringKey>: React.PropTypes.number })) ...
再说一遍:我至少要检查每个键的值是一个数字。理想情况下,我还希望能够检查密钥本身是否是格式正确的字符串。
注意 :该答案写于2015年,当前的React版本是0.14.3。它可能不适用于您今天使用的React版本。
这是一个有趣的问题。从您的问题看来,您似乎已经在Prop Validation的文档中阅读了有关自定义类型检查器的信息。为了后代,我将在此处复制:
// You can also specify a custom validator. It should return an Error // object if the validation fails. Don't `console.warn` or throw, as this // won’t work inside oneOfType. customProp: function(props, propName, componentName) { if (!/matchme/.test(props[propName])) { return new Error(‘Validation failed!’); } }
// You can also specify a custom validator. It should return an Error // object if the validation fails. Don't `console.warn` or throw, as
this // won’t work inside oneOfType. customProp: function(props, propName, componentName) { if (!/matchme/.test(props[propName])) { return new Error(‘Validation failed!’); } }
oneOfType
在实现类型检查器时,我更喜欢尽可能使用React的内置类型检查器。您想检查这些值是否为数字,所以我们应该使用PropTypes.number它,对吗?如果我们可以做PropTypes.number('not a number!')并得到适当的错误,那将是很好的,但是不幸的是,它涉及的更多。首先是要了解…
PropTypes.number
PropTypes.number('not a number!')
这是类型检查器的功能签名:
function(props, propName, componentName, location, propFullName) => null | Error
如您所见,所有道具都作为第一个参数传递,被测试道具的名称作为第二个参数传递。最后三个参数用于打印出有用的错误消息,并且是可选的:componentName不言自明。location将是一个 'prop','context'或者'childContext'(我们只关心 'prop'),并且propFullName是当我们正在处理嵌套的道具,例如用于 someObj.someKey。
componentName
location
'prop'
'context'
'childContext'
propFullName
someObj.someKey
有了这些知识,我们现在可以直接调用类型检查器:
PropTypes.number({ myProp: 'bad' }, 'myProp'); // => [Error: Invalid undefined `myProp` of type `string` supplied // to `<<anonymous>>`, expected `number`.]
看到?没有所有的论点就没有那么有用了。这个更好:
PropTypes.number({ myProp: 'bad' }, 'myProp', 'MyComponent', 'prop') // => [Error: Invalid prop `myProp` of type `string` supplied // to `MyComponent`, expected `number`.]
文档没有提及的一件事是,当您向提供一个自定义类型检查器时PropTypes.arrayOf,将为每个数组元素调用它,并且前两个参数分别是数组本身和当前元素的索引。现在我们可以开始草绘类型检查器了:
PropTypes.arrayOf
function validArrayItem(arr, idx, componentName, location, propFullName) { var obj = arr[idx]; console.log(propFullName, obj); // 1. Check if `obj` is an Object using `PropTypes.object` // 2. Check if all of its keys conform to some specified format // 3. Check if all of its values are numbers return null; }
到目前为止,它总是会返回null(指示有效的道具),但是我们投入了一个console.log以了解发生了什么。现在我们可以像这样测试它:
null
console.log
var typeChecker = PropTypes.arrayOf(validArrayItem); var myArray = [ { foo: 1 }, { bar: 'qux' } ]; var props = { myProp: myArray }; typeChecker(props, 'myProp', 'MyComponent', 'prop'); // -> myProp[0] { foo: 1 } // myProp[1] { bar: 'qux' } // => null
如您所见,propFullName是myProp[0]第一项, myProp[1]第二项。
myProp[0]
myProp[1]
现在让我们充实函数的三个部分。
obj
PropTypes.object
这是最简单的部分:
function validArrayItem(arr, idx, componentName, location, propFullName) { var obj = arr[idx]; var props = {}; props[propFullName] = obj; // Check if `obj` is an Object using `PropTypes.object` var isObjectError = PropTypes.object(props, propFullName, componentName, location); if (isObjectError) { return isObjectError; } return null; } var typeChecker = PropTypes.arrayOf(validArrayItem); var props = { myProp: [ { foo: 1 }, 'bar' ] }; typeChecker(props, 'myProp', 'MyComponent', 'prop'); // => [Error: Invalid prop `myProp[1]` of type `string` supplied to // `MyComponent`, expected `object`.]
完善!下一个…
在您的问题中,您说“每个键应该是一个字符串”,但是JavaScript中的所有对象键都是字符串,因此,我们可以随意地说,我们要测试这些键是否都以大写字母开头。让我们为此做一个自定义类型检查器:
var STARTS_WITH_UPPERCASE_LETTER_EXPR = /^[A-Z]/; function validObjectKeys(props, propName, componentName, location, propFullName) { var obj = props[propName]; var keys = Object.keys(obj); // If the object is empty, consider it valid if (keys.length === 0) { return null; } var key; var propFullNameWithKey; for (var i = 0; i < keys.length; i++) { key = keys[i]; propFullNameWithKey = (propFullName || propName) + '.' + key; if (STARTS_WITH_UPPERCASE_LETTER_EXPR.test(key)) { continue; } return new Error( 'Invalid key `' + propFullNameWithKey + '` supplied to ' + '`' + componentName + '`; expected to match ' + STARTS_WITH_UPPERCASE_LETTER_EXPR + '.' ); } return null; }
我们可以自己对其进行测试:
var props = { myProp: { Foo: 1, bar: 2 } }; validObjectKeys(props, 'myProp', 'MyComponent', 'prop'); // -> myProp.Foo Foo // myProp.bar bar // => [Error: Invalid key `myProp.bar` supplied to `MyComponent`; // expected to match /^[A-Z]/.]
大!让我们将其集成到validArrayItem类型检查器中:
validArrayItem
function validArrayItem(arr, idx, componentName, location, propFullName) { var obj = arr[idx]; var props = {}; props[propFullName] = obj; // Check if `obj` is an Object using `PropTypes.object` var isObjectError = PropTypes.object(props, propFullName, componentName, location); if (isObjectError) { return isObjectError; } // Check if all of its keys conform to some specified format var validObjectKeysError = validObjectKeys(props, propFullName, componentName); if (validObjectKeysError) { return validObjectKeysError; } return null; }
并测试一下:
var props = { myProp: [ { Foo: 1 }, { bar: 2 } ] }; var typeChecker = PropTypes.arrayOf(validArrayItem); typeChecker(props, 'myProp', 'MyComponent', 'prop'); // -> myProp[0].Foo Foo // myProp[1].bar bar // => [Error: Invalid key `myProp[1].bar` supplied to `MyComponent`; // expected to match /^[A-Z]/.]
最后…
幸运的是,我们不需要在这里做很多工作,因为我们可以使用内置的PropTypes.objectOf:
PropTypes.objectOf
// Check if all of its values are numbers var validObjectValues = PropTypes.objectOf(PropTypes.number); var validObjectValuesError = validObjectValues(props, propFullName, componentName, location); if (validObjectValuesError) { return validObjectValuesError; }
我们将在下面对其进行测试。
这是我们的最终代码:
function validArrayItem(arr, idx, componentName, location, propFullName) { var obj = arr[idx]; var props = {}; props[propFullName] = obj; // Check if `obj` is an Object using `PropTypes.object` var isObjectError = PropTypes.object(props, propFullName, componentName, location); if (isObjectError) { return isObjectError; } // Check if all of its keys conform to some specified format var validObjectKeysError = validObjectKeys(props, propFullName, componentName); if (validObjectKeysError) { return validObjectKeysError; } // Check if all of its values are numbers var validObjectValues = PropTypes.objectOf(PropTypes.number); var validObjectValuesError = validObjectValues(props, propFullName, componentName, location); if (validObjectValuesError) { return validObjectValuesError; } return null; }
我们将编写一个用于测试的快速便捷功能,并向其中添加一些数据:
function test(arrayToTest) { var typeChecker = PropTypes.arrayOf(validArrayItem); var props = { testProp: arrayToTest }; return typeChecker(props, 'testProp', 'MyComponent', 'prop'); } test([ { Foo: 1 }, { Bar: 2 } ]); // => null test([ { Foo: 1 }, { bar: 2 } ]); // => [Error: Invalid key `testProp[1].bar` supplied to `MyComponent`; // expected to match /^[A-Z]/.] test([ { Foo: 1 }, { Bar: false } ]); // => [Error: Invalid prop `testProp[1].Bar` of type `boolean` supplied to // `MyComponent`, expected `number`.]
有用!现在,您可以像内置类型检查器一样在React组件中使用它:
MyComponent.propTypes = { someArray: PropTypes.arrayOf(validArrayItem); };
当然,我建议给它起一个更有意义的名称,并将其移至其自己的模块中。