`

ReactPropTypes

 
阅读更多

ReactPropTypes 用于校验props、context、childContext的de函数。

 

'use strict';

var ReactElement = require('./ReactElement');

// 用于区分校验数据类型prop、context、childContext
var ReactPropTypeLocationNames = require('./ReactPropTypeLocationNames');

// 用于判断是否内部调用props校验函数
var ReactPropTypesSecret = require('./ReactPropTypesSecret');

// emptyFunction.thatReturns(arg) 返回arg
var emptyFunction = require('fbjs/lib/emptyFunction');

// 获取迭代函数
var getIteratorFn = require('./getIteratorFn');

// warning(condition,format) condition为否值,替换format中的"%s",并console.error警告 
var warning = require('fbjs/lib/warning');

var ANONYMOUS = '<<anonymous>>';

var ReactPropTypes = {
  // 数据类型校验
  array: createPrimitiveTypeChecker('array'),
  bool: createPrimitiveTypeChecker('boolean'),
  func: createPrimitiveTypeChecker('function'),
  number: createPrimitiveTypeChecker('number'),
  object: createPrimitiveTypeChecker('object'),
  string: createPrimitiveTypeChecker('string'),
  symbol: createPrimitiveTypeChecker('symbol'),

  // 数据类型不限
  any: createAnyTypeChecker(),

  // 以React.PropTypes.arrayOf(function(propValue){})校验prop为数组,且满足function(propValue){}校验函数
  // function(propValue){}返回error对象,意味校验失败;其他,校验成功
  arrayOf: createArrayOfTypeChecker,

  // 校验是否ReactElement
  element: createElementTypeChecker(),

  // 以React.PropTypes.instanceOf(expectedClass)校验是否expectedClass的实例
  instanceOf: createInstanceTypeChecker,

  // 校验prop为可渲染的数据,包含数值、字符串、undefined、false、ReactElement、数组或迭代器形式的前述类型
  node: createNodeChecker(),

  // 以React.PropTypes.objectOf(function(propValue,key){})校验prop为对象,且满足function(propValue,key){}校验函数
  // key为propValue遍历获得的属性名
  objectOf: createObjectOfTypeChecker,

  // 以React.PropTypes.oneOf(array)校验prop为array中的某个值
  oneOf: createEnumTypeChecker,

  // 以React.PropTypes.oneOfType([function(props,propName){}])校验关联的prop数据
  // function(props,propName){}返回null或undefined校验成功,其余失败
  oneOfType: createUnionTypeChecker,

  // 以React.PropTypes.shape({key:function(propValue,key){}})校验prop为对象
  // 且prop[key]属性需满足shapeTypes[key]校验函数
  // function(propValue,key){}}需返回否值或error对象,checkReactTypeSpec模块约定
  shape: createShapeTypeChecker
};

// 等值判断
function is(x, y) {
  if (x === y) {
    return x !== 0 || 1 / x === 1 / y;
  } else {
    return x !== x && y !== y;
  }
}

// 构建特定的错误对象
function PropTypeError(message) {
  this.message = message;
  this.stack = '';
}
PropTypeError.prototype = Error.prototype;

// 调用validate作校验,返回值链式添加isRequired校验
function createChainableTypeChecker(validate) {
  if (process.env.NODE_ENV !== 'production') {
    var manualPropTypeCallCache = {};
  }

  // 参数isRequired校验prop属性是否必填
  // 参数props为ReactElement的props属性
  // 参数propName为待校验的prop属性
  // 参数componentName为自定义组件的displayName
  // 参数location为字符串"prop"、"context"、"childContext"
  // 参数propFullName为null
  // 参数secret为字符串"SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED",校验函数内部调用标识符
  function checkType(isRequired, props, propName, componentName, location, propFullName, secret) {
    componentName = componentName || ANONYMOUS;
    propFullName = propFullName || propName;

    // 校验函数由外部调用,警告
    if (process.env.NODE_ENV !== 'production') {
      if (secret !== ReactPropTypesSecret && typeof console !== 'undefined') {
        var cacheKey = componentName + ':' + propName;
        if (!manualPropTypeCallCache[cacheKey]) {
          process.env.NODE_ENV !== 'production' ? 
            warning(false, 'You are manually calling a React.PropTypes validation ' 
              + 'function for the `%s` prop on `%s`. This is deprecated ' 
              + 'and will not work in production with the next major version. ' 
              + 'You may be seeing this warning due to a third-party PropTypes ' 
              + 'library. See https://fb.me/react-warning-dont-call-proptypes ' + 'for details.', 
              propFullName, componentName) 
            : void 0;
          manualPropTypeCallCache[cacheKey] = true;
        }
      }
    }

    // props属性必填项校验,书写时必须置于常规校验后,如React.PropTypes.func.isRequired
    if (props[propName] == null) {
      var locationName = ReactPropTypeLocationNames[location];// 区分校验数据类型"prop"、"context"、"childContext"
      if (isRequired) {
        if (props[propName] === null) {
          return new PropTypeError('The ' + locationName + ' `' + propFullName + '` is marked as required ' + ('in `' + componentName + '`, but its value is `null`.'));
        }
        return new PropTypeError('The ' + locationName + ' `' + propFullName + '` is marked as required in ' + ('`' + componentName + '`, but its value is `undefined`.'));
      }
      return null;

    // 调用validate校验
    } else {
      return validate(props, propName, componentName, location, propFullName);
    }
  }

  var chainedCheckType = checkType.bind(null, false);
  chainedCheckType.isRequired = checkType.bind(null, true);

  return chainedCheckType;
}

// 数据类型校验
// 参数expectedType为期望的类型'array'、'boolean'、'function'、'number'、'object'、'string'、'symbol'
function createPrimitiveTypeChecker(expectedType) {
  // 参数values为ReactElement的props属性
  // 参数propName为待校验的prop属性
  // 参数componentName为自定义组件的displayName
  // 参数location为字符串"prop"
  // 参数propFullName为null
  function validate(props, propName, componentName, location, propFullName, secret) {
    var propValue = props[propName];
    var propType = getPropType(propValue);// 获取propValue的数据类型
    if (propType !== expectedType) {
      var locationName = ReactPropTypeLocationNames[location];
      var preciseType = getPreciseType(propValue);// 返回propValue的数据类型,区分'date'和'regexp'

      return new PropTypeError('Invalid ' + locationName + ' `' + propFullName + '` of type ' 
        + ('`' + preciseType + '` supplied to `' + componentName + '`, expected ') 
        + ('`' + expectedType + '`.'));
    }
    return null;
  }

  // 调用validate作prop的校验数据类型,返回值链式添加isRequired校验
  return createChainableTypeChecker(validate);
}

// 数据类型不限
function createAnyTypeChecker() {
  // emptyFunction.thatReturns(null) 无论何时均返回null
  return createChainableTypeChecker(emptyFunction.thatReturns(null));
}

// 校验prop为数组类型,且满足typeChecker校验函数
// 参数typeChecker由用户配置,必须是函数function(propValue),返回error对象校验失败
function createArrayOfTypeChecker(typeChecker) {
  function validate(props, propName, componentName, location, propFullName) {
    // typeChecker非函数报错
    if (typeof typeChecker !== 'function') {
      return new PropTypeError('Property `' + propFullName + '` of component `' + componentName 
        + '` has invalid PropType notation inside arrayOf.');
    }

    // 校验prop属性是否数组
    var propValue = props[propName];
    if (!Array.isArray(propValue)) {
      var locationName = ReactPropTypeLocationNames[location];
      var propType = getPropType(propValue);// 获取propValue的数据类型
      return new PropTypeError('Invalid ' + locationName + ' `' + propFullName + '` of type ' 
        + ('`' + propType + '` supplied to `' + componentName + '`, expected an array.'));
    }

    // 执行typeChecker作数组数据校验
    for (var i = 0; i < propValue.length; i++) {
      var error = typeChecker(propValue, i, componentName, location, propFullName + '[' + i + ']', ReactPropTypesSecret);
      if (error instanceof Error) {
        return error;
      }
    }
    return null;
  }

  // 调用validate作校验prop数据类型为数组,同时各数组元素项符合typeChecker校验函数,返回值链式添加isRequired校验
  return createChainableTypeChecker(validate);
}

// 校验是否ReactElement
function createElementTypeChecker() {
  function validate(props, propName, componentName, location, propFullName) {
    var propValue = props[propName];
    if (!ReactElement.isValidElement(propValue)) {
      var locationName = ReactPropTypeLocationNames[location];
      var propType = getPropType(propValue);
      return new PropTypeError('Invalid ' + locationName + ' `' + propFullName + '` of type ' 
        + ('`' + propType + '` supplied to `' + componentName + '`, expected a single ReactElement.'));
    }
    return null;
  }
  return createChainableTypeChecker(validate);
}

// 校验是否expectedClass的实例
function createInstanceTypeChecker(expectedClass) {
  function validate(props, propName, componentName, location, propFullName) {
    if (!(props[propName] instanceof expectedClass)) {
      var locationName = ReactPropTypeLocationNames[location];
      var expectedClassName = expectedClass.name || ANONYMOUS;
      var actualClassName = getClassName(props[propName]);
      return new PropTypeError('Invalid ' + locationName + ' `' + propFullName + '` of type ' 
        + ('`' + actualClassName + '` supplied to `' + componentName + '`, expected ') 
        + ('instance of `' + expectedClassName + '`.'));
    }
    return null;
  }
  return createChainableTypeChecker(validate);
}

// 校验prop为可渲染的数据,包含数值、字符串、undefined、false、ReactElement、数组或迭代器形式的前述类型
function createNodeChecker() {
  function validate(props, propName, componentName, location, propFullName) {
    // isNode(propValue),校验propValue为数值、字符串、undefined、false、ReactElement、数组或迭代器形式的前述类型
    if (!isNode(props[propName])) {
      var locationName = ReactPropTypeLocationNames[location];// 返回字符串"prop"
      return new PropTypeError('Invalid ' + locationName + ' `' + propFullName + '` supplied to ' + ('`' + componentName + '`, expected a ReactNode.'));
    }
    return null;
  }
  return createChainableTypeChecker(validate);
}

// 校验prop为对象,且其属性满足typeChecker校验条件
function createObjectOfTypeChecker(typeChecker) {
  function validate(props, propName, componentName, location, propFullName) {
    if (typeof typeChecker !== 'function') {
      return new PropTypeError('Property `' + propFullName + '` of component `' + componentName 
        + '` has invalid PropType notation inside objectOf.');
    }
    var propValue = props[propName];
    var propType = getPropType(propValue);
    if (propType !== 'object') {
      var locationName = ReactPropTypeLocationNames[location];
      return new PropTypeError('Invalid ' + locationName + ' `' + propFullName + '` of type ' 
        + ('`' + propType + '` supplied to `' + componentName + '`, expected an object.'));
    }
    for (var key in propValue) {
      if (propValue.hasOwnProperty(key)) {
        var error = typeChecker(propValue, key, componentName, location, propFullName + '.' + key, ReactPropTypesSecret);
        if (error instanceof Error) {
          return error;
        }
      }
    }
    return null;
  }
  return createChainableTypeChecker(validate);
}

// 校验prop属性为设定值expectedValues中的一个
function createEnumTypeChecker(expectedValues) {
  if (!Array.isArray(expectedValues)) {
    process.env.NODE_ENV !== 'production' ? 
      warning(false, 'Invalid argument supplied to oneOf, expected an instance of array.') : void 0;
    return emptyFunction.thatReturnsNull;
  }

  function validate(props, propName, componentName, location, propFullName) {
    var propValue = props[propName];
    for (var i = 0; i < expectedValues.length; i++) {
      if (is(propValue, expectedValues[i])) {
        return null;
      }
    }

    var locationName = ReactPropTypeLocationNames[location];
    var valuesString = JSON.stringify(expectedValues);
    return new PropTypeError('Invalid ' + locationName + ' `' + propFullName + '` of value `' + propValue + '` ' + ('supplied to `' + componentName + '`, expected one of ' + valuesString + '.'));
  }
  return createChainableTypeChecker(validate);
}

// 关联prop数据校验,arrayOfTypeCheckers格式为[function(props,propName){}]
function createUnionTypeChecker(arrayOfTypeCheckers) {
  if (!Array.isArray(arrayOfTypeCheckers)) {
    process.env.NODE_ENV !== 'production' ? 
      warning(false, 'Invalid argument supplied to oneOfType, expected an instance of array.') : void 0;
    return emptyFunction.thatReturnsNull;
  }

  function validate(props, propName, componentName, location, propFullName) {
    for (var i = 0; i < arrayOfTypeCheckers.length; i++) {
      var checker = arrayOfTypeCheckers[i];
      if (checker(props, propName, componentName, location, propFullName, ReactPropTypesSecret) == null) {
        return null;
      }
    }

    var locationName = ReactPropTypeLocationNames[location];
    return new PropTypeError('Invalid ' + locationName + ' `' + propFullName + '` supplied to ' 
      + ('`' + componentName + '`.'));
  }
  return createChainableTypeChecker(validate);
}

// prop为对象,且其key属性需满足shapeTypes[key]校验函数
function createShapeTypeChecker(shapeTypes) {
  function validate(props, propName, componentName, location, propFullName) {
    var propValue = props[propName];
    var propType = getPropType(propValue);

    if (propType !== 'object') {
      var locationName = ReactPropTypeLocationNames[location];
      return new PropTypeError('Invalid ' + locationName + ' `' + propFullName + '` of type `' + propType + '` ' + ('supplied to `' + componentName + '`, expected `object`.'));
    }
    for (var key in shapeTypes) {
      var checker = shapeTypes[key];
      if (!checker) {
        continue;
      }
      var error = checker(propValue, key, componentName, location, propFullName + '.' + key, ReactPropTypesSecret);
      if (error) {
        return error;
      }
    }
    return null;
  }
  return createChainableTypeChecker(validate);
}

// 校验propValue为数值、字符串、undefined、false、ReactElement、数组或迭代器形式的前述类型
function isNode(propValue) {
  switch (typeof propValue) {
    case 'number':
    case 'string':
    case 'undefined':
      return true;
    case 'boolean':
      return !propValue;
    case 'object':
      if (Array.isArray(propValue)) {
        return propValue.every(isNode);
      }
      if (propValue === null || ReactElement.isValidElement(propValue)) {
        return true;
      }

      var iteratorFn = getIteratorFn(propValue);
      if (iteratorFn) {
        var iterator = iteratorFn.call(propValue);
        var step;
        if (iteratorFn !== propValue.entries) {
          while (!(step = iterator.next()).done) {
            if (!isNode(step.value)) {
              return false;
            }
          }
        } else {
          // Iterator will provide entry [k,v] tuples rather than values.
          while (!(step = iterator.next()).done) {
            var entry = step.value;
            if (entry) {
              if (!isNode(entry[1])) {
                return false;
              }
            }
          }
        }
      } else {
        return false;
      }

      return true;
    default:
      return false;
  }
}

// 判断propValue是否'symbol'类型
function isSymbol(propType, propValue) {
  if (propType === 'symbol') {
    return true;
  }

  // 19.4.3.5 Symbol.prototype[@@toStringTag] === 'Symbol'
  if (propValue['@@toStringTag'] === 'Symbol') {
    return true;
  }

  if (typeof Symbol === 'function' && propValue instanceof Symbol) {
    return true;
  }

  return false;
}

// 获取propValue的数据类型,正则返回"object",同时区分"symbol"
function getPropType(propValue) {
  var propType = typeof propValue;
  if (Array.isArray(propValue)) {
    return 'array';
  }
  if (propValue instanceof RegExp) {
    // 兼容性处理,Old webkits (at least until Android 4.0) return 'function'
    return 'object';
  }
  if (isSymbol(propType, propValue)) {
    return 'symbol';
  }
  return propType;
}

// 返回propValue的数据类型,区分'date'和'regexp'
function getPreciseType(propValue) {
  var propType = getPropType(propValue);// 获取propValue的数据类型
  if (propType === 'object') {
    if (propValue instanceof Date) {
      return 'date';
    } else if (propValue instanceof RegExp) {
      return 'regexp';
    }
  }
  return propType;
}

// 获取构造函数名
function getClassName(propValue) {
  if (!propValue.constructor || !propValue.constructor.name) {
    return ANONYMOUS;
  }
  return propValue.constructor.name;
}

module.exports = ReactPropTypes;

 

0
0
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics