当前位置:   article > 正文

源码 需求文档 测试文档_asyncvalidator源码解析(一):文档翻译

async-validator 文档

最近在业务中,使用 element-ui 给表单做校验时总是写出一些令人啼笑皆非的低级 bug,感受到了:rules对我的无情嘲讽。使用矛盾分析总结下就是两点原因:第一、对表单的基础知识掌握的不扎实;第二、对 element-ui 校验时使用的 async-validator 原理不熟悉。由此,打算接下来学习时ToDoList新加三条:

  1. 精读 HTML 的 form 相关元素的规范 和 XMLHttpRequest 的 FormData 相关 API 的规范。
  2. 重学一遍红宝书的第 19 章:表单脚本
  3. 阅读解析async-validator库,了解底层原理。

所以虽然之前 lodash 源码解析的系列的天坑还没填完,但是现在也插播一个新系列:async-validator源码解析。

解析之前我先从async-validator 源代码仓库 fork 了一份:https://github.com/MageeLin/async-validator-source-code-analysis 。

时间是 2020 年 9 月 28 日,commitID 是 8e17b30。解析时从 master 分支拉了一个新分支 analysis

代码结构

代码结构如下所示:

723b1a3d6485ca187caf7a51e61108c9.png

代码结构图

由代码依赖关系打算把内容主要分成以下几部分:

  1. 文档翻译
  2. util.js 和 message.js
  3. rule 目录
  4. validator 目录和 index.js

要解析这个库,首先得把文档看明白,所以第一步是把README.MD翻译成中文。以下内容为文档翻译:

异步表单验证。参考自 https://github.com/freeformsystems/async-validate

安装

npm i async-validator

使用方法

基本的使用方法:定义一个 descriptor,将其分配给一个 schema,并将要验证的 object 以及一个回调函数传递给该 schema 的validate方法:

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);  });

API

Validate

function(source, [options], callback): Promise
  • source: 需要校验的对象(必选)。
  • options: 描述校验的处理选项的对象(可选)。
  • callback: 当校验完成时调用的回调函数(必选)。

该方法将返回一个 Promise 对象,如下:

  • then(),校验通过
  • catch({ errors, fields }),校验失败,errors 是一个所有 error 组成的数组;field 是一个对象,键是 field,值是对应的 errors 数组。

Options

  • suppressWarning: Boolean,指示是否取消关于无效值的内部警告。

  • first: Boolean,当第一个校验规则产生error时调用callback,不再继续处理校验规则。如果校验涉及多个异步调用(例如数据库查询) ,且只需要第一个error出现时就停止,则使用此选项。

  • firstFields: Boolean|String[], 当指定字段的第一个校验规则产生error时调用callback,不再继续处理相同字段的校验规则。true意味着所有字段生效。

Rules

Rules 可能是执行校验的函数。

function(rule, value, callback, sourceoptions)
  • rule: 在源descriptor中,与要校验的字段名称相对应的校验规则。始终为它分配一个field属性,其中包含要验证的字段的名称。
  • value: 源对象属性中要校验的值。
  • callback: 校验完成后要调用的callback。它期望传递一个Error实例数组以指示校验失败。如果校验是同步的,则可以直接返回falseErrorError Array
  • source: 传给validate 方法的源对象。
  • options: 额外选项。
  • options.messages: 包含校验 error message 的对象,将与 defaultMessages 进行深度合并。

传给validate 或 asyncValidate的选项被传递给校验函数,以便于可以在校验函数中引用临时数据(例如 model 引用)。但是有些选项名是保留的; 如果使用options对象的这些属性将会导致覆盖。保留的属性是messagesexception 和 error.

import Schema from 'async-validator';const descriptor = {  name(rule, value, callback, sourceoptions) {    const errors = [];    if (!/^[a-z0-9]+$/.test(value)) {      errors.push(        new Error(util.format('%s 必须为小写字母的数字字符', rule.field))      );    }    return errors;  },};const validator = new Schema(descriptor);validator.validate({ name: 'Firstname' }, (errors, fields) => {  if (errors) {    return handleErrors(errors, fields);  }  // 校验通过});

对单个字段的多个校验规则进行测试的情况很常见,此时可以使规则成为一个对象数组,例如:

const descriptor = {  email: [    { type'string', required: true, pattern: Schema.pattern.email },    {      validator(rule, value, callback, sourceoptions) {        const errors = [];        // 测试email地址是否已经在数组库中存在        // 并当已存在时在errors数组中添加一个error        return errors;      },    },  ],};
Type

标志要使用的validatortype,可识别的type值为:

  • string: 必须为类型 string, 这是默认的 type。
  • number: 必须为类型 number
  • boolean: 必须为类型 boolean
  • method: 必须为类型 function
  • regexp: 必须为 RegExp的实例 或者 一个string,使用它 new RegExp时不能报错。
  • integer: 必须为类型 number 并且是一个整数。
  • float: 必须为类型 number 并且是一个浮点数。
  • array: 必须是一个 array ,由 Array.isArray确定。
  • object: 必须为类型 object 并且 Array.isArray返回false
  • enum: 值必须在 enum中存在。
  • date: 值必须是有效的,由Date确定。
  • url: 必须为类型 url
  • hex: 必须为类型 hex
  • email: 必须为类型 email
  • any: 可以是任意一种类型。
Required

rule属性required指示在校验时该field必须在source对象上存在

Pattern

rule属性pattern指示在校验时该值必须能通过正则表达式的校验。

Range

使用 min 和 max 属性定义范围。对于stringarray类型,根据length属性进行比较,对于number类型,数字不能小于 min,也不能大于 max

Length

若要校验field的精确长度,请指定 len 属性。对于stringarray类型的比较是在length属性上执行的,对于number类型,这个属性表示数字的精确匹配,也就是说,它只能严格等于 len

如果 len 属性与 min 和 max 两个 range 属性组合使用,len 优先。

Enumerable

从 3.0.0 版本开始,如果想校验enum类型中的值0或 false,就必须显式地包含它们。

要校验 所有可能值组成的列表 中的值,使用enum类型和enum属性列出该字段的有效值,例如:

const descriptor = {  role: { type'enum', enum: ['admin', 'user', 'guest'] },};
Whitespace

通常将只包含空白的必填字段视为错误。若要为仅由空格组成的字符串添加额外的校验,请向值为 true 的规则添加whitespace属性。规则必须是string类型。

您可能希望对用户输入进行净化,而不是校验whitespace,请参阅transform,以了解删除空白的示例。

Deep Rules

如果需要校验深层次对象的属性,可以通过将嵌套规则分配给rulesfields属性来校验属于 object 或 array 类型的校验规则。

const descriptor = {  address: {    type'object',    required: true,    fields: {      street: { type'string', required: true },      city: { type'string', required: true },      zip: { type'string', required: true, len: 8, message: 'invalid zip' },    },  },  name: { type'string', required: true },};const validator = new Schema(descriptor);validator.validate({ address: {} }, (errors, fields) => {  // address.street, address.city, address.zip产生了errors});

请注意,如果没有在父规则上指定required属性,那么不在source对象上声明该字段是完全有效的,并且深度校验规则也不会执行,因为没有什么需要校验的东西。

深度规则校验为嵌套rules创建schema,因此还可以指定传递给 schema.validate()方法的options

const descriptor = {  address: {    type'object',    required: true,    options: { firsttrue },    fields: {      street: { type'string', required: true },      city: { type'string', required: true },      zip: { type'string', required: true, len: 8, message: '无效zip' },    },  },  name: { type'string', required: true },};const validator = new Schema(descriptor);validator.validate({ address: {} }).catch(({ errors, fields }) => {  // 只有street 和 name 产生了 errors});

父规则也会被校验,所以如果你有一组rules,比如:

const descriptor = {  roles: {    type'array',    required: true,    len: 3,    fields: {      0: { type'string', required: true },      1: { type'string', required: true },      2: { type'string', required: true },    },  },};

提供{ roles: ['admin', 'user'] }这样的source对象,将创建两个error。一个用于数组长度不匹配,另一个用于缺少索引2处所需的数组。

defaultField

defaultField 属性可以与array 或 object类型一起使用,以校验内部的所有值。它可能是包含校验规则的 object 或 array。例如:

const descriptor = {  urls: {    type'array',    required: true,    defaultField: { type'url' },  },};

注意,若将defaultField 扩展为fields,请参见deep rules。

Transform

有时需要校验之前转换值,可能是为了强制转换类型或以某种方式对其进行净化。为此,在校验规则中添加一个transform函数。在校验之前对属性进行转换,并重新分配给source对象以更改属性的值。

import Schema from 'async-validator';const descriptor = {  name: {    type'string',    required: true,    pattern: /^[a-z]+$/,    transform(value) {      return value.trim();    },  },};const validator = new Schema(descriptor);const source = { name: ' user  ' };validator.validate(source).then(() => assert.equal(source.name, 'user'));

如果没有transform函数,校验将会失败,原因是pattern不匹配,因为输入包含前置和后置空白。但是通过添加transform函数,校验成功并且字段值同时被净化。

Messages

根据应用的需求,可能需要 i18n 支持,或者可能更喜欢个性化的校验error message

最简单的方法是给一个rule分配一条message:

{ name: { type'string', required: true, message: '请填写名称' } }

message可以是任意类型,比如jsx格式:

{ name: { type'string', required: true, message: '请填写名称' } }

message也可以是一个函数,比如vue-i18n

{ name: { type: 'string'requiredtrue, message: () => this.$t( '请填写名称' ) } }

对于不同的语言,可能需要相同的schema校验规则,在这种情况下,为每种语言复制schema规则是无效的。

但是,可以提供你自己的语言版本的message,然后把它分配给schema:

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

如果要自定义校验函数,最好将message字符串分配给message对象,然后通过校验函数中的 options.messages 属性访问message

asyncValidator

可以为指定字段自定义异步校验函数:

const fields = {  asyncField: {    asyncValidator(rule, value, callback) {      ajax({        url'xx',        value: value,      }).then(        function (data) {          callback();        },        function (error) {          callback(new Error(error));        }      );    },  },  promiseField: {    asyncValidator(rule, value) {      return ajax({        url'xx',        value: value,      });    },  },};
validator

可以为指定的字段自定义同步校验函数:

const fields = {  field: {    validator(rule, value, callback) {      return value === 'test';    },    message'Value is not equal to "test".',  },  field2: {    validator(rule, value, callback) {      return new Error(`${value} is not equal to 'test'.`);    },  },  arrField: {    validator(rule, value) {      return [new Error('Message 1'), new Error('Message 2')];    },  },};

常见问题

如何取消 warning

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

如果检查布尔值 true

使用 enum 类型 并传布尔值 true 参数作为选项。

{  type: 'enum',  enum: [true],  message: '',}

测试用例

npm test

测试覆盖率

npm run coverage

打开 coverage/ dir

许可证

一切内容皆适用 MIT 许可证。

文档翻译完成,下一节打算从utils.jsmessage.js入手开始解析代码。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/706075
推荐阅读
相关标签
  

闽ICP备14008679号