Ответ 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);
};
Конечно, я бы рекомендовал дать ему более содержательное имя и двигаться это в свой собственный модуль.