Validation 如何创建元组属性类型检查器?

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元

我正在尝试为React创建一个自定义的“元组”道具类型,但是,我遇到了一些障碍

API如下所示:

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