san.js源码解读之工具(util)篇——数据校验

发布时间:2024年01月06日

一、用法

我们可以给组件的 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, // 该类型校验官方文档并没有指出如何使用

};

二、 示例

DataTypes.number 类型示例

// 子组件
<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.arrayOf 类型示例

//子组件
// ...
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 函数

createChainableChecker 函数是所有校验函数的基础,它接收一个参数(validate),在后面 checkType 函数中运行。在createChainableChecker函数中逻辑主要分为两种,1. 初始化,2. 相关绑定
请添加图片描述

  1. 初始化中做了如下事情
    • chainedChecker 和 isRequired 复制为空函数
    • 如果 validate 参数没有传入值,默认为空函数
    • 声明 checkType 函数

checkType 函数是数据类型校验所在地,逻辑如下
checkType函数中逻辑流程

不是
不是
相关参数(isRequired, data, dataName, componentName, fullDataName)
根据dataName在数据中获取值并
赋值给dataValue
通过getDataType函数获取当前值类型
componentName是否为undefined,null
或者能够隐式转换为false的值
ANONYMOUS_CLASS_NAME
赋值给
componentName
componentName
赋值给
componentName
dataValue是为null或undefined
是否为必须
结束
报错
执行validate函数
  1. 绑定中做了如下事情
    • 使用 bind 函数绑定 checkType
      函数,并赋值给chainedChecker,最后一个参数传入为false,这是因为该场景下isRequired为false。其中bind函数和Function.prototype.bind()一样
    • 使用 bind 函数绑定 checkType 函数,并赋值给chainedChecker.isRequired,最后一个参数传入为false
(二)、简单类型校验 DataTypes.object 源码分析

对于简单类型(any、array、object、func、string、number、bool和symbol)校验函数在san中都是使用 createPrimaryTypeChecker 函数传入对应要校验的类型而生成的。

例如:createPrimaryTypeChecker(‘array’)或则createPrimaryTypeChecker(‘object’)

这里以DataTypes.object 为例。代码如下
请添加图片描述
createPrimaryTypeChecker函数接受一个 type参数:即要校验的类型,并返回 createChainableChecker 函数。在 createChainableChecker 中传入了一个匿名函数作为参数,它的逻辑如下

  1. 在data中获取对于名称的数据
  2. 获取该数据的类型
  3. 如果该类型和要检验的不一样,则抛错

最后该匿名函数作为 validate 参数在 checkType 函数中直接校验

(三)、复合类型校验 DataTypes.arrayOf 源码分析

其余复合类型如:instanceOf、shape、oneOf… 原理和DataTypes.arrayOf 基本一致,因此这里以DataTypes.arrayOf为例,代码如下
请添加图片描述
可以看出复合校验和简单校验它们大致结构基本一致,不同的地方在于传入参数类型和 createChainableChecker 中匿名函数处理逻辑不一样。

对于 DataTypes.arrayOf 类型校验来说,arrayItemChecker 为用户传入要校验的类型。
例如

ArrayOfString: DataTypes.arrayOf(DataTypes.string),

arrayItemChecker 则为 DataTypes.string 类型返回的函数。

createChainableChecker 中匿名函数处理逻辑如下

  1. 判断 arrayItemChecker 是否为函数,不会函数则抛错
  2. 获取 dataValue 值的类型,如果不为数组类型,则抛错
  3. 符合条件后,对于数组中每个元素调用传入的 arrayItemChecker 函数进行校验

四、在san中何处执行校验

数据校验是在数据初始化之后进行校验
请添加图片描述

文章来源:https://blog.csdn.net/qq_42683219/article/details/135422228
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。