我们可以给组件的 data 指定校验规则。如果传入的数据不符合规则,那么 san 会抛出异常。当组件给其他人使用时,这很有用。
指定校验规则,需要使用 DataTypes 进行声明:
import san, {DataTypes} from 'san';
let MyComponent = san.defineComponent({
dataTypes: {
name: DataTypes.string, // 校验 name 必须为字符串类型
}
});
处理 DataTypes.string 校验字符串外,san 提供了一系列校验函数(如下),方便开发中使用。需要注意的是san为了考虑性能,数据校验只会自development模式下进行。
DataTypes = {
any: createChainableChecker(), // 该类型校验官方文档并没有指出如何使用
// 类型检测
array: createPrimaryTypeChecker('array'), // 数组
object: createPrimaryTypeChecker('object'), // 对象
func: createPrimaryTypeChecker('function'), // 函数
string: createPrimaryTypeChecker('string'), // 字符串
number: createPrimaryTypeChecker('number'), // 数值
bool: createPrimaryTypeChecker('boolean'), // 布尔
symbol: createPrimaryTypeChecker('symbol'), // symbol
// 复合类型检测
arrayOf: createArrayOfChecker, // 例如:DataTypes.arrayOf(DataTypes.number),则是校验数组中每个元素都必须是数值
instanceOf: createInstanceOfChecker, // 例如:DataTypes.instanceOf(Message),则是校验声明数据是否为为指定类的实例
shape: createShapeChecker, // 例如:DataTypes.shape({
// color: DataTypes.string,
// fontSize: DataTypes.number
//}),该类型的校验类似鸭子类型校验
oneOf: createOneOfChecker, // 例如:DataTypes.oneOf(['News', 'Photos']),是校验你的数据是否是指定的['News', 'Photos']这几个值之一
oneOfType: createOneOfTypeChecker, // 例如:DataTypes.oneOfType([
// DataTypes.string,
// DataTypes.number,
// DataTypes.instanceOf(Message)
// ]), 和 DataTypes.oneOf 校验的类型相似
objectOf: createObjectOfChecker, // 例如:DataTypes.objectOf(DataTypes.number),是对象的所有属性值都必须是 DataTypes.number 类型
exact: createExactChecker, // 该类型校验官方文档并没有指出如何使用
};
// 子组件
<template>
<div>
子组件
{{ currentNum }}
</div>
</template>
<script>
import { DataTypes } from 'san';
export default {
dataTypes: {
currentNum: DataTypes.number, // 校验 currentNum 变量必须为数值类型,否则报错
},
}
</script>
// 父组件
<template>
<child currentNum="sd"/> // 这里传入值
</template>
<script>
import child from './child.san';
export default {
components: {
'san-button': Button,
'child': child
},
}
</script>
父组件中传入的不为 number, 所以报错
//子组件
// ...
dataTypes: {
ArrayOfString: DataTypes.arrayOf(DataTypes.string),
},
//...
//父组件
<child ArrayOfString="sd"/>
父组件中传入的不为 DataTypes.arrayOf(DataTypes.string), 所以报错
/**
* Copyright (c) Baidu Inc. All rights reserved.
*
* This source code is licensed under the MIT license.
* See LICENSE file in the project root for license information.
*
* @file data types
*/
var bind = require('./bind');
var empty = require('./empty');
var extend = require('./extend');
// #[begin] error
var ANONYMOUS_CLASS_NAME = '<<anonymous>>';
/**
* 获取精确的类型
*
* @NOTE 如果 obj 是一个 DOMElement,我们会返回 `element`;
*
* @param {*} obj 目标
* @return {string}
*/
function getDataType(obj) {
// 不支持element了。data应该是纯数据
// if (obj && obj.nodeType === 1) {
// return 'element';
// }
return Object.prototype.toString
.call(obj)
.slice(8, -1)
.toLowerCase();
}
// #[end]
/**
* 创建链式的数据类型校验器
*
* @param {Function} validate 真正的校验器
* @return {Function}
*/
function createChainableChecker(validate) {
/* istanbul ignore next */
var chainedChecker = function () {};
chainedChecker.isRequired = empty;
// 只在 error 功能启用时才有实际上的 dataTypes 检测
// #[begin] error
validate = validate || empty;
var checkType = function (isRequired, data, dataName, componentName, fullDataName) {
var dataValue = data[dataName];
var dataType = getDataType(dataValue);
/* istanbul ignore next */
componentName = componentName || ANONYMOUS_CLASS_NAME;
// 如果是 null 或 undefined,那么要提前返回啦
if (dataValue == null) {
// 是 required 就报错
if (isRequired) {
throw new Error('[SAN ERROR] '
+ 'The `' + dataName + '` '
+ 'is marked as required in `' + componentName + '`, '
+ 'but its value is ' + dataType
);
}
// 不是 required,那就是 ok 的
return;
}
validate(data, dataName, componentName, fullDataName);
};
chainedChecker = bind(checkType, null, false);
chainedChecker.isRequired = bind(checkType, null, true);
// #[end]
return chainedChecker;
}
// #[begin] error
/**
* 生成主要类型数据校验器
*
* @param {string} type 主类型
* @return {Function}
*/
function createPrimaryTypeChecker(type) {
return createChainableChecker(function (data, dataName, componentName, fullDataName) {
var dataValue = data[dataName];
var dataType = getDataType(dataValue);
if (dataType !== type) {
throw new Error('[SAN ERROR] '
+ 'Invalid ' + componentName + ' data `' + fullDataName + '` of type'
+ '(' + dataType + ' supplied to ' + componentName + ', '
+ 'expected ' + type + ')'
);
}
});
}
/**
* 生成 arrayOf 校验器
*
* @param {Function} arrayItemChecker 数组中每项数据的校验器
* @return {Function}
*/
function createArrayOfChecker(arrayItemChecker) {
return createChainableChecker(function (data, dataName, componentName, fullDataName) {
if (typeof arrayItemChecker !== 'function') {
throw new Error('[SAN ERROR] '
+ 'Data `' + dataName + '` of `' + componentName + '` has invalid '
+ 'DataType notation inside `arrayOf`, expected `function`'
);
}
var dataValue = data[dataName];
var dataType = getDataType(dataValue);
if (dataType !== 'array') {
throw new Error('[SAN ERROR] '
+ 'Invalid ' + componentName + ' data `' + fullDataName + '` of type'
+ '(' + dataType + ' supplied to ' + componentName + ', '
+ 'expected array)'
);
}
for (var i = 0, len = dataValue.length; i < len; i++) {
arrayItemChecker(dataValue, i, componentName, fullDataName + '[' + i + ']');
}
});
}
/**
* 生成 instanceOf 检测器
*
* @param {Function|Class} expectedClass 期待的类
* @return {Function}
*/
function createInstanceOfChecker(expectedClass) {
return createChainableChecker(function (data, dataName, componentName, fullDataName) {
var dataValue = data[dataName];
if (dataValue instanceof expectedClass) {
return;
}
var dataValueClassName = dataValue.constructor && dataValue.constructor.name
? dataValue.constructor.name
: /* istanbul ignore next */ ANONYMOUS_CLASS_NAME;
/* istanbul ignore next */
var expectedClassName = expectedClass.name || ANONYMOUS_CLASS_NAME;
throw new Error('[SAN ERROR] '
+ 'Invalid ' + componentName + ' data `' + fullDataName + '` of type'
+ '(' + dataValueClassName + ' supplied to ' + componentName + ', '
+ 'expected instance of ' + expectedClassName + ')'
);
});
}
/**
* 生成 shape 校验器
*
* @param {Object} shapeTypes shape 校验规则
* @return {Function}
*/
function createShapeChecker(shapeTypes) {
return createChainableChecker(function (data, dataName, componentName, fullDataName) {
if (getDataType(shapeTypes) !== 'object') {
throw new Error('[SAN ERROR] '
+ 'Data `' + fullDataName + '` of `' + componentName + '` has invalid '
+ 'DataType notation inside `shape`, expected `object`'
);
}
var dataValue = data[dataName];
var dataType = getDataType(dataValue);
if (dataType !== 'object') {
throw new Error('[SAN ERROR] '
+ 'Invalid ' + componentName + ' data `' + fullDataName + '` of type'
+ '(' + dataType + ' supplied to ' + componentName + ', '
+ 'expected object)'
);
}
for (var shapeKeyName in shapeTypes) {
/* istanbul ignore else */
if (shapeTypes.hasOwnProperty(shapeKeyName)) {
var checker = shapeTypes[shapeKeyName];
if (typeof checker === 'function') {
checker(dataValue, shapeKeyName, componentName, fullDataName + '.' + shapeKeyName);
}
}
}
});
}
/**
* 生成 oneOf 校验器
*
* @param {Array} expectedEnumValues 期待的枚举值
* @return {Function}
*/
function createOneOfChecker(expectedEnumValues) {
return createChainableChecker(function (data, dataName, componentName, fullDataName) {
if (getDataType(expectedEnumValues) !== 'array') {
throw new Error('[SAN ERROR] '
+ 'Data `' + fullDataName + '` of `' + componentName + '` has invalid '
+ 'DataType notation inside `oneOf`, array is expected.'
);
}
var dataValue = data[dataName];
for (var i = 0, len = expectedEnumValues.length; i < len; i++) {
if (dataValue === expectedEnumValues[i]) {
return;
}
}
throw new Error('[SAN ERROR] '
+ 'Invalid ' + componentName + ' data `' + fullDataName + '` of value'
+ '(`' + dataValue + '` supplied to ' + componentName + ', '
+ 'expected one of ' + expectedEnumValues.join(',') + ')'
);
});
}
/**
* 生成 oneOfType 校验器
*
* @param {Array<Function>} expectedEnumOfTypeValues 期待的枚举类型
* @return {Function}
*/
function createOneOfTypeChecker(expectedEnumOfTypeValues) {
return createChainableChecker(function (data, dataName, componentName, fullDataName) {
if (getDataType(expectedEnumOfTypeValues) !== 'array') {
throw new Error('[SAN ERROR] '
+ 'Data `' + dataName + '` of `' + componentName + '` has invalid '
+ 'DataType notation inside `oneOf`, array is expected.'
);
}
var dataValue = data[dataName];
for (var i = 0, len = expectedEnumOfTypeValues.length; i < len; i++) {
var checker = expectedEnumOfTypeValues[i];
if (typeof checker !== 'function') {
continue;
}
try {
checker(data, dataName, componentName, fullDataName);
// 如果 checker 完成校验没报错,那就返回了
return;
}
catch (e) {
// 如果有错误,那么应该把错误吞掉
}
}
// 所有的可接受 type 都失败了,才丢一个异常
throw new Error('[SAN ERROR] '
+ 'Invalid ' + componentName + ' data `' + dataName + '` of value'
+ '(`' + dataValue + '` supplied to ' + componentName + ')'
);
});
}
/**
* 生成 objectOf 校验器
*
* @param {Function} typeChecker 对象属性值校验器
* @return {Function}
*/
function createObjectOfChecker(typeChecker) {
return createChainableChecker(function (data, dataName, componentName, fullDataName) {
if (typeof typeChecker !== 'function') {
throw new Error('[SAN ERROR] '
+ 'Data `' + dataName + '` of `' + componentName + '` has invalid '
+ 'DataType notation inside `objectOf`, expected function'
);
}
var dataValue = data[dataName];
var dataType = getDataType(dataValue);
if (dataType !== 'object') {
throw new Error('[SAN ERROR] '
+ 'Invalid ' + componentName + ' data `' + dataName + '` of type'
+ '(' + dataType + ' supplied to ' + componentName + ', '
+ 'expected object)'
);
}
for (var dataKeyName in dataValue) {
/* istanbul ignore else */
if (dataValue.hasOwnProperty(dataKeyName)) {
typeChecker(
dataValue,
dataKeyName,
componentName,
fullDataName + '.' + dataKeyName
);
}
}
});
}
/**
* 生成 exact 校验器
*
* @param {Object} shapeTypes object 形态定义
* @return {Function}
*/
function createExactChecker(shapeTypes) {
return createChainableChecker(function (data, dataName, componentName, fullDataName, secret) {
if (getDataType(shapeTypes) !== 'object') {
throw new Error('[SAN ERROR] '
+ 'Data `' + dataName + '` of `' + componentName + '` has invalid '
+ 'DataType notation inside `exact`'
);
}
var dataValue = data[dataName];
var dataValueType = getDataType(dataValue);
if (dataValueType !== 'object') {
throw new Error('[SAN ERROR] '
+ 'Invalid data `' + fullDataName + '` of type `' + dataValueType + '`'
+ '(supplied to ' + componentName + ', expected `object`)'
);
}
var allKeys = {};
// 先合入 shapeTypes
extend(allKeys, shapeTypes);
// 再合入 dataValue
extend(allKeys, dataValue);
// 保证 allKeys 的类型正确
for (var key in allKeys) {
/* istanbul ignore else */
if (allKeys.hasOwnProperty(key)) {
var checker = shapeTypes[key];
// dataValue 中有一个多余的数据项
if (!checker) {
throw new Error('[SAN ERROR] '
+ 'Invalid data `' + fullDataName + '` key `' + key + '` '
+ 'supplied to `' + componentName + '`. '
+ '(`' + key + '` is not defined in `DataTypes.exact`)'
);
}
if (!(key in dataValue)) {
throw new Error('[SAN ERROR] '
+ 'Invalid data `' + fullDataName + '` key `' + key + '` '
+ 'supplied to `' + componentName + '`. '
+ '(`' + key + '` is marked `required` in `DataTypes.exact`)'
);
}
checker(
dataValue,
key,
componentName,
fullDataName + '.' + key,
secret
);
}
}
});
}
// #[end]
/* eslint-disable fecs-valid-var-jsdoc */
var DataTypes = {
array: createChainableChecker(),
object: createChainableChecker(),
func: createChainableChecker(),
string: createChainableChecker(),
number: createChainableChecker(),
bool: createChainableChecker(),
symbol: createChainableChecker(),
any: createChainableChecker,
arrayOf: createChainableChecker,
instanceOf: createChainableChecker,
shape: createChainableChecker,
oneOf: createChainableChecker,
oneOfType: createChainableChecker,
objectOf: createChainableChecker,
exact: createChainableChecker
};
// #[begin] error
DataTypes = {
any: createChainableChecker(),
// 类型检测
array: createPrimaryTypeChecker('array'),
object: createPrimaryTypeChecker('object'),
func: createPrimaryTypeChecker('function'),
string: createPrimaryTypeChecker('string'),
number: createPrimaryTypeChecker('number'),
bool: createPrimaryTypeChecker('boolean'),
symbol: createPrimaryTypeChecker('symbol'),
// 复合类型检测
arrayOf: createArrayOfChecker,
instanceOf: createInstanceOfChecker,
shape: createShapeChecker,
oneOf: createOneOfChecker,
oneOfType: createOneOfTypeChecker,
objectOf: createObjectOfChecker,
exact: createExactChecker
};
/* eslint-enable fecs-valid-var-jsdoc */
// #[end]
module.exports = DataTypes;
createChainableChecker 函数是所有校验函数的基础,它接收一个参数(validate),在后面 checkType 函数中运行。在createChainableChecker函数中逻辑主要分为两种,1. 初始化,2. 相关绑定
checkType 函数是数据类型校验所在地,逻辑如下
checkType函数中逻辑流程
对于简单类型(any、array、object、func、string、number、bool和symbol)校验函数在san中都是使用 createPrimaryTypeChecker 函数传入对应要校验的类型而生成的。
例如:createPrimaryTypeChecker(‘array’)或则createPrimaryTypeChecker(‘object’)
这里以DataTypes.object 为例。代码如下
createPrimaryTypeChecker函数接受一个 type参数:即要校验的类型,并返回 createChainableChecker 函数。在 createChainableChecker 中传入了一个匿名函数作为参数,它的逻辑如下
最后该匿名函数作为 validate 参数在 checkType 函数中直接校验
其余复合类型如:instanceOf、shape、oneOf… 原理和DataTypes.arrayOf 基本一致,因此这里以DataTypes.arrayOf为例,代码如下
可以看出复合校验和简单校验它们大致结构基本一致,不同的地方在于传入参数类型和 createChainableChecker 中匿名函数处理逻辑不一样。
对于 DataTypes.arrayOf 类型校验来说,arrayItemChecker 为用户传入要校验的类型。
例如
ArrayOfString: DataTypes.arrayOf(DataTypes.string),
arrayItemChecker 则为 DataTypes.string 类型返回的函数。
createChainableChecker 中匿名函数处理逻辑如下
数据校验是在数据初始化之后进行校验