使用动态键检查对象的 PropTypes

IT技术 reactjs
2021-03-28 16:45:16

React 有很多使用 PropTypes 来检查 prop 值的方法。我常用的一种是React.PropTypes.shape({...}). 但是,我最近遇到了一种情况,我有一个对象,其中包含动态键/值。我知道每个键都应该是一个字符串(采用已知格式),每个值都应该是一个整数。即使使用自定义props验证功能,它仍然假设您知道props的密钥。如何使用 PropTypes 检查对象/形状的键和值是否正确?

...
someArray: React.PropTypes.arrayOf(React.PropTypes.shape({
  // How to specify a dynamic string key? Keys are a date/datetime string
  <dynamicStringKey>: React.PropTypes.number
}))
...

再说一遍:我想至少检查每个键的值是否是一个数字。理想情况下,我还希望能够检查密钥本身是否是格式正确的字符串。

5个回答

验证值,您可以使用React.PropTypes.objectOf.

...
someArray: React.PropTypes.arrayOf(
  React.PropTypes.objectOf(React.PropTypes.number)
)
...
恕我直言,这是正确的解决方案,因为它简洁地回答了这个问题。
2021-05-23 16:45:16
作为参考,它记录在这里:reactjs.org/docs/typechecking-with-proptypes.html
2021-05-27 16:45:16

注意:这个答案是在 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!');
  }
}

在实现类型检查器时,我更喜欢尽可能使用 React 的内置类型检查器。您想检查值是否为数字,所以我们应该使用PropTypes.number它,对吗?如果我们能做PropTypes.number('not a number!')并得到适当的错误会很好,但不幸的是它比这更复杂。第一站是了解...

类型检查器的工作原理

这是类型检查器的函数签名:

function(props, propName, componentName, location, propFullName) => null | Error

如您所见,所有 props 都作为第一个参数传递,被测试的 props 的名称作为第二个参数传递。最后三个参数用于打印有用的错误消息并且是可选的:componentName不言自明。location将是 'prop', 'context', or 之一'childContext'(我们只对 感兴趣 'prop'),并且propFullName当我们处理嵌套 props 时,例如 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,将为每个数组元素调用它,前两个参数将分别是数组本身和当前元素的索引。现在我们可以开始勾画我们的类型检查器:

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(表示有效的props),但我们输入了 aconsole.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

正如你所看到的,propFullNamemyProp[0]对第一项和 myProp[1]第二。

现在让我们充实函数的三个部分。

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`.]

完美的!下一个...

2. 检查它的所有密钥是否符合某种指定的格式

在您的问题中,您说“每个键都应该是一个字符串”,但是 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]/.]

最后...

3.检查它的所有值是否都是数字

令人高兴的是,我们不需要在这里做太多工作,因为我们可以使用内置的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);
};

当然,我建议给它一个更有意义的名字并将它移到它自己的module中。

作为后续,以下代码有助于查看并说明 React 本身如何调用验证器 - github 上的代码
2021-05-24 16:45:16
这里的细节非常棒,谢谢。在处理数组时了解这两个额外的参数非常有用!
2021-05-26 16:45:16
请注意,直接调用 PropType 函数将在未来的主要版本中被弃用:facebook.github.io/react/warnings/dont-call-proptypes.html
2021-06-03 16:45:16
TLDR;利用PropTypes.objectOf(PropTypes.number)
2021-06-05 16:45:16
惊人的!这非常适合检查带有 key = id 的映射中的规范化数据。
2021-06-06 16:45:16

我的问题是......

  • 对象具有动态键(我不想检查)
  • 值具有固定的数据结构(我想检查一下)
  • 底层数据结构的形状是已知的,可以检查

    // 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结合也变得非常灵活。

这并没有回答问题,该问题专门询问如何验证对象中的键是否与某种格式匹配。
2021-05-25 16:45:16
没有准确回答 OP 的问题,但确实回答了我的问题。谢谢
2021-05-25 16:45:16
嘿@MatthewHerbst 那是真的。我没有 100% 正确回答这个问题,但由于它以一种简单的方式解决了我的问题,我想也许其他人会发现这个代码片段很有用。很多人按主题搜索,我想我在这里匹配主题。但是我调整了帖子以更清楚该解决方案缺少什么。
2021-06-03 16:45:16
我尝试使用动态表单的动态键验证对象。这个答案解决了检查键内对象的问题,这非常有用。谢谢@teberl
2021-06-10 16:45:16
尽管这不是 OP 想要的,但这正是我正在寻找的。谢谢!
2021-06-13 16:45:16

您可以通过传递函数来创建自己的验证器。

customProp 这里

我相信你可以做类似的事情React.PropTypes.arrayOf(customValidator)

这是您正在寻找的验证器:

function objectWithNumericKeys(obj) {
  if (Object.keys(obj).some(key => isNaN(key)))
     return new Error('Validation failed!');
}
我知道 - 我在问题中提到了它。我要问的是,如果我不知道密钥的值,或者customProp在这种情况下怎么办?
2021-05-29 16:45:16

对于那些使用Immutable.jsreact-immutable-proptypes库的人,你可以使用.mapOf方法。

someArray: ImmutablePropTypes.listOf(ImmutablePropTypes.mapOf({
  React.PropTypes.number, // validates values
  React.PropTypes.string, // validates keys
}))