js对象类型检测 -- 扯皮终结者
故事背景
笔者所在的项目组目前使用的是微前端
的架构;这就导致各个同事分别维护一个子项目
。而这些子项目之间常常需要数据传递,数据传递的方式目前主要是通过发布-订阅
模式完成的。
问题出现了,因为微前端框架的问题,每个人的视野都局限在了自己负责的子项目上,所以常常出现传递数据格式不正确
的问题,由此衍生出来的一系列扯皮事件,非常烦人。
为此我终于下定决心,自己写了一个对象类型检测函数,这个函数可以对传递数据的格式进行检查,将检测结果输出到控制台上;检测策略高度定制化,可以这样说,只要调用接口的使用者认真去写数据格式策略,就一定能够将扯皮这种事情拒之门外。
1. 被检测对象的类型
携带数据的被检测对象必须是引用类型的(如果不是引用类型,属于比较简单的数据传递,一般来说不会存在扯皮的可能性),其格式约束如下:
type ISource = Record<PropertyKey, any>;
2. 定制策略函数类型
对被检测对象的检测策略是高度定制化的,使用策略函数实现:对传入的值进行自定义的类型检测,返回一个元组;参数一表示是否通过检测,参数二表示通过/不通过时候的信息
type IFunc = (value:any)=>readonly [boolean, string];
3. 策略对象的类型
对被测对象的key-value检测的时候,可以简单的检测value的类型,也可以通过自定义的策略函数对其进行细致的检查,所以策略对象的类型为(需要考虑递归的情况,也就是value也是引用类型):
interface IStrategy {
[key: PropertyKey]: string | IFunc | IStrategy;
}
4. 辅助函数
需要一个辅助函数提取value的数据类型,因为很常用,所以单独抽取出来
const _toString = (_x: any) => Object.prototype.toString.call(_x).slice(8,-1);
5. 测试步骤
-
- 对策略对象的每一个ownProperty进行遍历,拿到具体的key-strategy
-
- 拿到被测对象上的同名key对应的value, 然后通过辅助函数得到value的type
-
- 对strategy进行分类讨论,
-
- 如果strategy是的类型是
function
, 这种情况表示自定义检测,这种情况下将value作为此函数的形参传入,然后根据策略函数返回值进行状态更新即可;
- 如果strategy是的类型是
-
- 如果strategy是的类型是
object
, 这种情况表示value应该是一个引用类型,需要递归调用checkParams方法;
- 如果strategy是的类型是
-
- 其它情况下,表示value此时的类型应该是
基本数据类型
,这个时候比较strategy和value的类型是否相同即可。
- 其它情况下,表示value此时的类型应该是
6. 实现checkParams类型检测
// 被测对象类型
type ISource = Record<PropertyKey, any>;
// 策略函数类型
// 策略函数会对传入的值进行自定义的类型检测,返回一个元组;参数一表示是否通过检测,参数二表示通过/不通过时候的信息
type IFunc = (value:any)=>readonly [boolean, string];
// 策略类型
interface IStrategy {
// 测试策略的类型可能是string(表示期望value该有的值)或者策略函数(以自定义的方式对value进行检测)
[key: PropertyKey]: string | IFunc | IStrategy;
}
// 获取值的类型
const _toString = (_x: any) => Object.prototype.toString.call(_x).slice(8,-1);
// 类型检测函数
/**
*
* @param source 被测对象
* @param strategy 策略
* @param outer 是否为最外层
* @param prefix 属性前缀
* @param record 信息列表
* @returns rst 布尔值,表示测验是否通过
*/
export const checkParams = (source: ISource, strategy: IStrategy, outer=true, prefix = '', record?: string[]): boolean => {
// 创建信息记录数组
const _record = record || [];
// source必须是引用类型
if(Object(source) !== source) return false;
// 只检测ownProperty
const keys = [...Object.getOwnPropertyNames(strategy), ...Object.getOwnPropertySymbols(strategy)];
// 整体检测结果
let rst = true;
// 遍历检查
keys.forEach(
(key: PropertyKey) => {
// 获取key对应的value的类型
const _type = _toString(source[key]);
// 如果检测的这个key-value的策略是一个函数
if(_toString(strategy[key]) === 'Function'){
// 那么就调用策略函数
const [state, stateMsg] = (<IFunc>strategy[key])(source[key]);
// 如果没有经过策略函数的检验,就将原因保存到记录数组中
if(!state) _record.push(`property ${prefix.concat(String(key))}:\n>>>\t${stateMsg}`);
// 更新整体检测结果的值
rst = rst && state;
// 如果检测的这个key对应的value是一个引用类型
} else if (Object(strategy[key]) === strategy[key]){
// 那么久递归的调用checkParams
const _prefix = `${String(key)}.`;
const state = checkParams(source[key], strategy[key] as IStrategy, false, _prefix, _record);
// 由于记录数组在整个过程中以单例方式传递,所以无需将msg存入数组中,所以checkParams也无需返回记录数组
// 更新整体检测结果的值
rst = rst && state;
// if(!rst) keys.length = 0; // 这里是为了实现一旦整体检测结果为false则立即返回的效果
} else {
// 如果测试策略既不是函数也不是其它引用类型,则是一个字符串,表示的是期望的type
const state = _type === strategy[key];
// 更新整体检测结果的值
rst = rst && state;
// if(!rst) keys.length = 0; // 这里是为了实现一旦整体检测结果为false则立即返回的效果
// 如果没有经过策略的检验,就将原因保存到记录数组中
const stateMsg = `require ${strategy[key]}, but ${_type}`;
if(!state) _record.push(`property ${prefix.concat(String(key))}:\n>>>\t${stateMsg}`);
}
}
)
// 判断是否在最外层,如果是的话,打印检测信息数组
outer && console.log(_record.join('\n'));
return rst;
}
7. 测试用例
// 被测对象
const target = {
path: 'views/alarm.pic',
count: '10',
data: {
age: 15,
}
}
// 测试策略
const strategy = {
path: function(value: any){
const type = _toString(value);
let msg: string;
let flag: boolean;
if (type === 'Boolean') {
flag = true;
msg = 'success';
} else {
flag = false;
msg = `require Boolean, but ${type}`;
}
return [flag,msg] as const;
},
count: 'Number',
data: {
age: 'String',
}
}
const result = checkParams(target, strategy);
console.log('params test result: ', result);
8. 测试结果
property path:
>>> require Boolean, but String
property count:
>>> require Number, but String
property data.age:
>>> require String, but Number
params test result: false
如果你觉得上面的函数能够减轻你的心智负担,那么就请顺手点个赞吧,谢谢!
转载自:https://juejin.cn/post/7283080455851245622