db2设置默认schema_async-validator源码解析(四):Schema类

c02b0bdd967772209e439e1d1ee39cae.png

上篇 —— async-validator 源码解析(三):rule —— 将 async-validator 校验库的 validator 目录下的代码进行了分析,下面继续自底向上进入到最上层index.js,分析 async-validator 校验库的核心 Schema类

由于 Schema类 的代码整体较长,所以本篇先除去代码量较大的原型链definevalidate方法的相关内容,看其他部分的结构、属性和方法。可以从仓库 https://github.com/MageeLin/async-validator-source-code-analysis 的 analysis 分支看到本篇中的每个文件的代码分析。

依赖关系

代码依赖关系如下所示:

558232cf2c3575644347a6a3461f0e96.png

按照从底向上的方式,本篇主要分析 index.js 文件中的 Schema类 及相关的 utils工具方法 、 messages.js默认消息。

Schema 类

Schema类就是 async-validator 库的标准使用方式,在文档中使用 Schema 的方式就是如下几步:

  1. 引入 async-validatorSchema
  2. 定义校验规则 descriptor
  3. new 一个 Schema 实例。
  4. 调用实例的 validate 方法(回调函数方式或者 promise 方式)。

文档中的调用 demo 如下:

// 官方文档中的demo

import Schema from 'async-validator';
const descriptor = {
  name: {
    type: 'string',
    required: true,
    validator: (rule, value) => value === 'muji',
  },
  age: {
    type: 'number',
    asyncValidator: (rule, value) => {
      return new Promise((resolve, reject) => {
        if (value < 18) {
          reject('too young'); // reject 这个 error message
        } else {
          resolve();
        }
      });
    },
  },
};
const validator = new Schema(descriptor);
validator.validate({ name: 'muji' }, (errors, fields) => {
  if (errors) {
    // 校验失败,errors是一个包含所有error的数组。
    // fields是一个对象,对象中field(字段)是key,每个field对应的所有error组成的数组是value。
    return handleErrors(errors, fields);
  }
  // 校验通过
});

// PROMISE使用方法
validator
  .validate({ name: 'muji', age: 16 })
  .then(() => {
    // 校验通过或者没有error message
  })
  .catch(({ errors, fields }) => {
    return handleErrors(errors, fields);
  });

所以首先从 Schema 的构造函数开始入手。

构造函数

构造函数分了三步:

  1. this 上定义了个实例属性 rulesnull
  2. this 上定义了一个实例私有属性 _messages,初始值为 messages.js 文件里的内容。
  3. 调用原型链 define 方法,传参 descriptor
// index.js
// ...省略

import { messages as defaultMessages, newMessages } from './messages';

/**
 *  封装校验的schema
 *
 *  @param descriptor 一个对此schema声明了校验规则的对象
 */
// Schema构造函数
function Schema(descriptor) {
  // 实例的属性rules默认为空
  this.rules = null;
  // 实例的私有属性_messages默认为messages.js文件里的内容
  this._messages = defaultMessages;
  // 正式开始构建实例!
  this.define(descriptor);
}

export default Schema;

原型链上的 define 方法是核心,而且代码比较长,所以本篇暂不解析 Schema.prototype.define 相关的方法,放在下一篇。

messages.js

Schema 的构造函数和静态方法中,都用到了从messages.js中引过来的 defaultMessages。可以看出,文件中都是针对不同类型的失败校验设置的提示消息模板。

// index.js
import { messages as defaultMessages, newMessages } from './messages';

// ...省略

// Schema的静态属性,存储了各种类型的默认message
Schema.messages = defaultMessages;

// messages.js
export function newMessages() {
  return {
    default: 'Validation error on field %s',
    required: '%s is required',
    enum: '%s must be one of %s',
    whitespace: '%s cannot be empty',
    date: {
      format: '%s date %s is invalid for format %s',
      parse: '%s date could not be parsed, %s is invalid ',
      invalid: '%s date %s is invalid',
    },
    types: {
      string: '%s is not a %s',
      method: '%s is not a %s (function)',
      array: '%s is not an %s',
      object: '%s is not an %s',
      number: '%s is not a %s',
      date: '%s is not a %s',
      boolean: '%s is not a %s',
      integer: '%s is not an %s',
      float: '%s is not a %s',
      regexp: '%s is not a valid %s',
      email: '%s is not a valid %s',
      url: '%s is not a valid %s',
      hex: '%s is not a valid %s',
    },
    string: {
      len: '%s must be exactly %s characters',
      min: '%s must be at least %s characters',
      max: '%s cannot be longer than %s characters',
      range: '%s must be between %s and %s characters',
    },
    number: {
      len: '%s must equal %s',
      min: '%s cannot be less than %s',
      max: '%s cannot be greater than %s',
      range: '%s must be between %s and %s',
    },
    array: {
      len: '%s must be exactly %s in length',
      min: '%s cannot be less than %s in length',
      max: '%s cannot be greater than %s in length',
      range: '%s must be between %s and %s in length',
    },
    pattern: {
      mismatch: '%s value %s does not match pattern %s',
    },
    clone() {
      const cloned = JSON.parse(JSON.stringify(this));
      cloned.clone = this.clone;
      return cloned;
    },
  };
}

export const messages = newMessages();

message 本来就是针对不同的失败校验提供不同提示消息,所以这个 message 模板对不同的项目来说可能需要定制化。官方文档中也提供了如何给实例添加 messagedemo

// demo
import Schema from 'async-validator';
const cn = {
  required: '请填写 %s',
};
const descriptor = { name: { type: 'string', required: true } };
const validator = new Schema(descriptor);
// 与defaultMessages深度合并
validator.messages(cn);
...

demo 中利用了方法Schema.prototype.message来进行 messages 的深度合并,看一下这块的代码:

// message方法的相关代码

// index.js
import { deepMerge } from './util';
import { messages as defaultMessages, newMessages } from './messages';

Schema.prototype = {
  messages(messages) {
    // 如果新的messages参数存在
    if (messages) {
      // 将 默认messages 和 参数 合并
      this._messages = deepMerge(newMessages(), messages);
    }
    // 最后把合并后的messages返回
    return this._messages;
  },
  // ......
};

// util.js

/* 深合并 */
export function deepMerge(target, source) {
  // 当新messages存在时
  if (source) {
    // 迭代新messages
    for (const s in source) {
      // 确保s为自身属性key
      if (source.hasOwnProperty(s)) {
        const value = source[s];
        // 当在原messages和新messages中这个键都为object类型时
        if (typeof value === 'object' && typeof target[s] === 'object') {
          // 使用扩展运算符把两个messages合并,新messages优先级高
          target[s] = {
            ...target[s],
            ...value,
          };
        } else {
          // 只要两个messages有一个不是对象,就把新messages的该属性直接赋值给老messages
          target[s] = value;
        }
      }
    }
  }
  return target; // 返回合并后的messages
}

可以发现此处的深度合并是有 bug 的,只能达到两级深合并,target[s]value 再往下一级就只能合并引用而不能合并值了。但是恰巧默认的 messages 只有两级的深度,所以这种写法当前情况是没有问题的。

专门看了下 git 记录,翻到了之前的issue。原来之前使用的 lodashmergeWith 进行合并,后来next的开发者提了意见,嫌弃 lodash 的包体积太大,所以作者用手写的 merge 代替了 lodashmergeWith

warning

官方文档中也给出了控制台警告信息的显示控制,就是在实例化 Schema 之前设置 warning 方法,只要把 warning 方法设置为一个空函数就能屏蔽控制台警告。

// demo
import Schema from 'async-validator';
Schema.warning = function () {};

下面看看源代码中关于 warning 是怎么写的。

// index.js
// ...省略
import { warning } from './util';

// ...省略

// Schema的静态方法,在开发环境中可以console.warn
Schema.warning = warning;

// util.js
export let warning = () => {}; // 默认warning不能输出

/* 当在非生产环境或者非node运行时,才输出warning信息 */
if (
  typeof process !== 'undefined' && // Node环境
  process.env &&
  process.env.NODE_ENV !== 'production' && // Node环境不为生产环境
  typeof window !== 'undefined' && // 确保window存在
  typeof document !== 'undefined' // 确保document存在
) {
  // 修改了warning函数
  warning = (type, errors) => {
    // 检查是否能用console
    if (typeof console !== 'undefined' && console.warn) {
      // 检查errors中每一个error是否都是string类型
      if (errors.every((e) => typeof e === 'string')) {
        console.warn(type, errors); // 打印warning
      }
    }
  };
}

可以发现 warning 本身就被设为了一个空函数,只有在开发环境或非 node运行时,才会用 console.warn 打印出 errors 数组中的每一个 error

validators

在上一篇中已经发现,写各种各样的 rule 规则其实都是转化为 validator 校验器来处理的。所以官方给留了个静态方法用于添加自定义的 type,方便进行自定义类型的校验(但是不知道为啥官方文档中没写)。

// index.js
// ...省略

import validators from './validator/index';

// ...省略

// Schema的静态属性,存储了从validator目录中引进来的众多类型的校验器
Schema.validators = validators;

// Schema的静态方法,添加一个新类型的校验器
Schema.register = function register(type, validator) {
  // 校验器必须是个函数,不是就报错
  if (typeof validator !== 'function') {
    throw new Error(
      'Cannot register a validator by type, validator is not a function'
    );
  }
  validators[type] = validator;
};

所以在实例化之前,调用 Schema 的静态方法 register(type, validator)可以注册一个对自定义类型的校验规则。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值