Validation 如何创建元组属性类型检查器?
我正在尝试为React创建一个自定义的“元组”道具类型,但是,我遇到了一些障碍 API如下所示:Validation 如何创建元组属性类型检查器?,validation,reactjs,Validation,Reactjs,我正在尝试为React创建一个自定义的“元组”道具类型,但是,我遇到了一些障碍 API如下所示: import {PropTypes} from 'react'; export default class MyComponent extends React.Component { static propTypes = { myProp: tuple(PropTypes.number, PropTypes.string), }; 这将确保myProp是一个2元
import {PropTypes} from 'react';
export default class MyComponent extends React.Component {
static propTypes = {
myProp: tuple(PropTypes.number, PropTypes.string),
};
这将确保myProp
是一个2元素数组,第一个元素是数字,第二个元素是字符串
以下是我取得的成绩:
export function tuple(...arrayOfTypeCheckers) {
return requirable(function(props, propName, componentName) {
let value = props[propName];
if(!Array.isArray(value)) {
throw new Error(`Expected array for \`${propName}\` in \`${componentName}\``);
}
if(value.length !== arrayOfTypeCheckers.length) {
throw new Error(`\`${propName}\` must have exactly ${arrayOfTypeCheckers.length} elements in \`${componentName}\`, got ${value.length}`);
}
for(let i = 0; i < value.length; ++i) {
let checker = arrayOfTypeCheckers[i];
if(!__NEED_HELP_HERE__) {
throw new Error(`${propName}[${i}] is not of the expected type in \`${componentName}\``)
}
}
return null;
});
}
你会注意到它需要一个充满道具的对象,外加一个道具名。该值的访问方式为
var propValue = props[propName];
这意味着我不能从数组中提供我自己的值
我如何调用React的
PropTypes.xyz
验证器?要利用React的内部API太难了,而且他们告诉我们,所以我必须自己重新实现类型检查器
代码如下:
const lo = require('lodash');
function tuple(...types) {
return requirable(function(props, propName, componentName, location, propFullName) {
let value = props[propName];
if(!location) {
location = 'prop';
}
if(!propFullName) {
propFullName = propName;
}
if(!Array.isArray(value)) {
throw new Error(`Invalid ${location} \`${propFullName}\` supplied to \`${componentName}\`, expected ${types.length}-element array`);
}
if(value.length !== types.length) {
throw new Error(`Invalid ${location} \`${propFullName}\` supplied to \`${componentName}\`, expected ${types.length}-element array, got array of length ${value.length}`);
}
for(let i = 0; i < value.length; ++i) {
if(!types[i](value[i])) {
throw new Error(`Invalid ${location} ${propFullName}[${i}] supplied to \`${componentName}\`, unexpected type`)
}
}
return null;
});
}
function requirable(predicate) {
const propType = (props, propName, ...rest) => {
// don't do any validation if empty
if(props[propName] === undefined) {
return;
}
return predicate(props, propName, ...rest);
};
propType.isRequired = (props, propName, componentName, ...rest) => {
// warn if empty
if(props[propName] === undefined) {
return new Error(`Required prop \`${propName}\` was not specified in \`${componentName}\`.`);
}
return predicate(props, propName, componentName, ...rest);
};
return propType;
}
const TypeCheckers = {
TypedArray: lo.isTypedArray,
Object: lo.isObject,
PlainObject: lo.isPlainObject,
RegExp: lo.isRegExp,
String: lo.isString,
Undefined: lo.isUndefined,
Number: lo.isNumber,
Null: lo.isNull,
NativeFunction: lo.isNative,
Function: lo.isFunction,
Error: lo.isError,
FiniteNumber: lo.isFinite,
NaN: lo.isNaN,
DomElement: lo.isElement,
Date: lo.isDate,
Array: lo.isArray,
Boolean: lo.isBoolean,
Stringable: obj => obj && lo.isFunction(obj.toString),
};
const lo=require('lodash');
函数元组(…类型){
return requireable(函数(props、propName、componentName、location、propFullName){
让值=道具[道具名称];
如果(!位置){
位置='prop';
}
如果(!propFullName){
propFullName=propName;
}
如果(!Array.isArray(值)){
抛出新错误(`Invalid${location}\`${propFullName}`提供给\${componentName}\`,应为${types.length}-元素数组`);
}
if(value.length!==types.length){
抛出新错误(`Invalid${location}\`propFullName}`提供给\`componentName}\`,应为${types.length}-元素数组,获取的数组长度为${value.length}`);
}
for(设i=0;i{
//如果为空,则不执行任何验证
if(道具[propName]==未定义){
返回;
}
返回谓词(props,propName,…rest);
};
propType.isRequired=(props、propName、componentName,…rest)=>{
//如果为空,则发出警告
if(道具[propName]==未定义){
返回新错误(`Required prop\`${propName}`未在\`${componentName}`中指定`Required prop\`${propName}`);
}
返回谓词(props、propName、componentName、…rest);
};
返回类型;
}
常量类型检查器={
TypedArray:lo.isTypedArray,
对象:lo.isObject,
PlainObject:lo.isPlainObject,
RegExp:lo.isRegExp,
字符串:lo.isString,
未定义:lo.isUndefined,
编号:lo.isNumber,
Null:lo.isNull,
NativeFunction:lo.isNative,
函数:lo.isFunction,
错误:lo.isError,
FiniteNumber:lo.isFinite,
NaN:lo.isNaN,
多梅莱恩:lo.isElement,
日期:lo.isDate,
阵列:lo.isArray,
Boolean:lo.isBoolean,
Stringable:obj=>obj&&lo.isFunction(obj.toString),
};
“类型”不是实际的类型,它们只是接受值并返回true或false的函数。我使用了lodash
中的一些helper函数,因为这比试图从React中删除它们要容易得多,但是您可以使用任何东西
我仍然在使用内部API来获取propFullName
,但如果他们删除了它,我会提供一个后备方案。如果没有完整的道具名称,错误消息看起来非常糟糕。e、 g
警告:失败的道具类型:提供给PhsaDocToolbar
的道具0[1]无效,类型意外
但是通过propFullName
我们可以得到:
警告:失败的道具类型:提供给PhsaDocToolbar
的道具文档类型[0][1]无效,类型意外
这只关系到你是否在创建类似于
PropTypes.arrayOf(tuple(TypeCheckers.Number,TypeCheckers.String))
的东西。我发现这个问题很有用,所以我想我会为任何正在寻找它的人提供一个更新的答案(即使它已经有几年了)。答案基于@mpen的答案。它已更新为使用最新的道具类型
,即版本15.6.2
主要更新是将secret
(隐藏的第6个参数到类型检查器)传递到内部类型检查器。这允许在此检查器中使用标准的道具类型
类型检查器,而不会抛出错误
s。它也可以在其他类型检查器中使用(shape
,arrayOf
,objectOf
,tuple
本身等)
该实现还基于道具类型的内部实现
,并符合其API(返回错误
或空
)
createchaineabletypechecker
的实现是从库中提取出来的,去掉了一些东西(检查secret
是否正确),并添加了向下传递secret
的功能
这是解决办法。它使用ES6类和模块导入/导出:
function CustomPropTypeError (message) {
this.message = message;
this.stack = '';
}
CustomPropTypeError.prototype = Error.prototype;
function createChainableTypeChecker (validate) {
function checkType (isRequired, props, propName, componentName, location, propFullName, secret) {
componentName = componentName || ANONYMOUS;
propFullName = propFullName || propName;
if (props[propName] == null) {
if (isRequired) {
if (props[propName] === null) {
return new CustomPropTypeError('The ' + location + ' `' + propFullName + '` is marked as required ' + ('in `' + componentName + '`, but its value is `null`.'));
}
return new CustomPropTypeError('The ' + location + ' `' + propFullName + '` is marked as required in ' + ('`' + componentName + '`, but its value is `undefined`.'));
}
return null;
} else {
return validate(props, propName, componentName, location, propFullName, secret);
}
}
var chainedCheckType = checkType.bind(null, false);
chainedCheckType.isRequired = checkType.bind(null, true);
return chainedCheckType;
}
export function tuple (...types) {
return createChainableTypeChecker((props, propName, componentName, location, propFullName, secret) => {
const value = props[propName];
if (!location) {
location = 'prop';
}
if (!propFullName) {
propFullName = propName;
}
if (!Array.isArray(value)) {
return new CustomPropTypeError(`Invalid ${location} \`${propFullName}\` supplied to \`${componentName}\`, expected ${types.length}-element array`);
}
if (value.length !== types.length) {
return new CustomPropTypeError(`Invalid ${location} \`${propFullName}\` supplied to \`${componentName}\`, expected ${types.length}-element array, got array of length ${value.length}`);
}
for (let i = 0; i < value.length; ++i) {
const error = types[i](value, i, componentName, 'element', `${propFullName}[${i}]`, secret);
if (error) {
return error;
}
}
return null;
});
}
函数CustomPropTypeError(消息){
this.message=消息;
this.stack='';
}
CustomPropTypeError.prototype=Error.prototype;
函数CreateChaineTableTypeChecker(验证){
函数检查类型(isRequired、props、propName、componentName、location、propFullName、secret){
componentName=componentName | |匿名;
propFullName=propFullName | | propName;
if(props[propName]==null){
如果(需要){
if(props[propName]==null){
返回新的CustomPropTypeError(“+location+”、“+propFullName+”`标记为必需的“+”(“+componentName+”`中的“但其值为空”);
}
返回新的CustomPropTypeError(“+location+”、“+propFullName+”`标记为“+(“+componentName+”,但其值为“undefined”);
}
返回null;
}否则{
返回验证(props、propName、componentName、location、propFullName、secret);
}
}
柴华
function CustomPropTypeError (message) {
this.message = message;
this.stack = '';
}
CustomPropTypeError.prototype = Error.prototype;
function createChainableTypeChecker (validate) {
function checkType (isRequired, props, propName, componentName, location, propFullName, secret) {
componentName = componentName || ANONYMOUS;
propFullName = propFullName || propName;
if (props[propName] == null) {
if (isRequired) {
if (props[propName] === null) {
return new CustomPropTypeError('The ' + location + ' `' + propFullName + '` is marked as required ' + ('in `' + componentName + '`, but its value is `null`.'));
}
return new CustomPropTypeError('The ' + location + ' `' + propFullName + '` is marked as required in ' + ('`' + componentName + '`, but its value is `undefined`.'));
}
return null;
} else {
return validate(props, propName, componentName, location, propFullName, secret);
}
}
var chainedCheckType = checkType.bind(null, false);
chainedCheckType.isRequired = checkType.bind(null, true);
return chainedCheckType;
}
export function tuple (...types) {
return createChainableTypeChecker((props, propName, componentName, location, propFullName, secret) => {
const value = props[propName];
if (!location) {
location = 'prop';
}
if (!propFullName) {
propFullName = propName;
}
if (!Array.isArray(value)) {
return new CustomPropTypeError(`Invalid ${location} \`${propFullName}\` supplied to \`${componentName}\`, expected ${types.length}-element array`);
}
if (value.length !== types.length) {
return new CustomPropTypeError(`Invalid ${location} \`${propFullName}\` supplied to \`${componentName}\`, expected ${types.length}-element array, got array of length ${value.length}`);
}
for (let i = 0; i < value.length; ++i) {
const error = types[i](value, i, componentName, 'element', `${propFullName}[${i}]`, secret);
if (error) {
return error;
}
}
return null;
});
}
import React from 'react';
import PropTypes from 'prop-types';
import * as CustomPropTypes from './path/to/CustomPropTypes';
class Component extends React.Component { ... }
Component.propTypes = {
myTupleType: CustomPropTypes.tuple(
PropTypes.number.isRequired,
PropTypes.string.isRequired
).isRequired,
};
<Component myTupleType={[5, 'string']} /> // No error
<Component myTupleType={['5', 6]} /> // PropType error
//Component
<Component state={[10, () => 20]} />
//Tupple
const tupple = (...validators) =>
PropTypes.arrayOf((_, index) => {
const currentValidators = validators.filter((v, i) => i === index);
if (currentValidators.length <= 0) return true;
const [currentValidator] = currentValidators;
return currentValidator;
});
//PropsValidation
SomeComponent.propTypes = {
state: tupple(
PropTypes.number,
PropTypes.func
),
};