React有很多方法可以使用PropTypes来检查prop的值.我经常使用的是React.PropTypes.shape({...})
.但是,我最近遇到一种情况,我有一个内部有动态键/值的对象.我知道每个键应该是一个字符串(以已知格式),每个值应该是一个int.即使使用自定义道具验证功能,它仍然假设您知道道具的关键.如何使用PropTypes检查对象/形状的键和值是否正确?
... someArray: React.PropTypes.arrayOf(React.PropTypes.shape({ // How to specify a dynamic string key? Keys are a date/datetime string: React.PropTypes.number })) ...
所以:我想至少检查每个键的值是否为数字.理想情况下,我还希望能够检查密钥本身是否是正确格式的字符串.
要仅验证值,您可以使用React.PropTypes.objectOf
.
... someArray: React.PropTypes.arrayOf( React.PropTypes.objectOf(React.PropTypes.number) ) ...
这是一个有趣的问题.从您的问题来看,听起来您已经在支持验证的文档中阅读了有关自定义类型检查器的信息.对于后代我会在这里重现它:
// 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!'); } }
在实现类型检查器时,我更喜欢尽可能使用React的内置类型检查器.你想检查这些值是否是数字,所以我们应该使用PropTypes.number
它,对吗?如果我们可以做到PropTypes.number('not a number!')
并且得到适当的错误会很好,但不幸的是它比这更复杂.第一站是了解......
这是类型检查器的函数签名:
function(props, propName, componentName, location, propFullName) => null | Error
如您所见,所有道具都作为第一个参数传递,被测试道具的名称作为第二个传递.最后三个参数用于打印有用的错误消息,并且是可选的:componentName
不言自明.location
将是一个
'prop'
,'context'
或者'childContext'
(我们只关心
'prop'
),并且propFullName
是当我们正在处理嵌套的道具,例如用于 someObj.someKey
.
有了这些知识,我们现在可以直接调用类型检查器:
PropTypes.number({ myProp: 'bad' }, 'myProp');
// => [Error: Invalid undefined `myProp` of type `string` supplied
// to `<>`, expected `number`.]
看到?没有所有的论点,没有那么有用.这个更好:
PropTypes.number({ myProp: 'bad' }, 'myProp', 'MyComponent', 'prop')
// => [Error: Invalid prop `myProp` of type `string` supplied
// to `MyComponent`, expected `number`.]
文档没有提到的一件事是,当你提供自定义类型检查器时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
以查看正在发生的事情.现在我们可以像这样测试它:
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]
第二项.
现在让我们充实这个功能的三个部分.
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
类型检查器中:
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
:
// 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);
};
当然,我建议给它一个更有意义的名称并将其移动到自己的模块中.
我的问题是...
对象具有动态键(我不想检查)
值具有固定的数据结构(我想检查)
基础数据结构的形状是已知的,可以检查
// An object with property values of a certain shape optionalObject: PropTypes.objectOf( PropTypes.shape({ color: PropTypes.string.isRequired, fontSize: PropTypes.number }) );
因此,对于我的问题,缺少的部分是PropTypes.objectOf
从那里您可以使用动态键创建任何类型的结构。结合起来PropTypes.oneOfType
也变得非常灵活。