likes
comments
collection
share

js对象类型检测 -- 扯皮终结者

作者站长头像
站长
· 阅读数 39

故事背景

笔者所在的项目组目前使用的是微前端的架构;这就导致各个同事分别维护一个子项目。而这些子项目之间常常需要数据传递,数据传递的方式目前主要是通过发布-订阅模式完成的。

问题出现了,因为微前端框架的问题,每个人的视野都局限在了自己负责的子项目上,所以常常出现传递数据格式不正确的问题,由此衍生出来的一系列扯皮事件,非常烦人。

为此我终于下定决心,自己写了一个对象类型检测函数,这个函数可以对传递数据的格式进行检查,将检测结果输出到控制台上;检测策略高度定制化,可以这样说,只要调用接口的使用者认真去写数据格式策略,就一定能够将扯皮这种事情拒之门外。

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. 测试步骤

    1. 对策略对象的每一个ownProperty进行遍历,拿到具体的key-strategy
    1. 拿到被测对象上的同名key对应的value, 然后通过辅助函数得到value的type
    1. 对strategy进行分类讨论,
    1. 如果strategy是的类型是function, 这种情况表示自定义检测,这种情况下将value作为此函数的形参传入,然后根据策略函数返回值进行状态更新即可;
    1. 如果strategy是的类型是object, 这种情况表示value应该是一个引用类型,需要递归调用checkParams方法;
    1. 其它情况下,表示value此时的类型应该是基本数据类型,这个时候比较strategy和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
评论
请登录