Подтвердить что ты не робот

Проверка объектов PropTypes с помощью динамических клавиш

React имеет множество способов использования PropTypes для проверки значения пропеллера. Я обычно использую 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
  <dynamicStringKey>: React.PropTypes.number
}))
...

Итак, снова: я хочу, по крайней мере, проверить, что значение каждой клавиши - это число. В идеале, я также хотел бы иметь возможность проверить, что сам ключ является строкой в ​​правильном формате.

4b9b3361

Ответ 1

Это интересный вопрос. Из вашего вопроса это звучит так, как будто вы прочитайте о контрольных шагах в документах 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

Как вы можете видеть, все реквизиты передаются в качестве первого аргумента и имя проверяемого проспекта передается как второе. Последний три аргумента используются для распечатки полезных сообщений об ошибках и необязательно: componentName не требует пояснений. location будет одним из 'prop', 'context' или 'childContext' (нас интересует только 'prop'), а 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`.]

Проверка типа массива

Одна вещь, о которой не говорится в документах, заключается в том, что при поставке пользовательского типа checker в 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] для второго.

Теперь оставьте три части функции.

1. Проверьте, является ли obj объектом Object с помощью 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`.]

Perfect! Далее...

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);
};

Конечно, я бы рекомендовал дать ему более содержательное имя и двигаться это в свой собственный модуль.

Ответ 2

Чтобы проверить только значения, вы можете использовать React.PropTypes.objectOf.

...
someArray: React.PropTypes.arrayOf(
  React.PropTypes.objectOf(React.PropTypes.number)
)
...

Ответ 3

Вы можете создать свой собственный валидатор, передав функцию.

См. customProp здесь.

Я считаю, что вы можете сделать что-то вроде React.PropTypes.arrayOf(customValidator).

Здесь валидатор, который вы ищете:

function objectWithNumericKeys(obj) {
  if (Object.keys(obj).some(key => isNaN(key)))
     return new Error('Validation failed!');
}