background

While working with Ivew.design, I found that the Async Validator is used for form validation in the source code, so I looked at the source code to understand the principle and make some adjustments.

const validator = new AsyncValidator(descriptor); let model = {}; model[this.prop] = this.fieldValue; validator.validate(model, { firstFields: true }, errors => { this.validateState = ! errors ? 'success' : 'error'; this.validateMessage = errors ? errors[0].message : ''; callback(this.validateMessage); this.FormInstance && this.FormInstance.$emit('on-validate', this.prop, ! errors, this.validateMessage || null); });Copy the code

This is the source code version


Know how to use it first

  • GitHub source address

    Star is quite a lot, many libraries use this, can only say 666 ~

  • The installation
npm i async-validator
Copy the code
  • usage

    More details from the source code to explain, say more also can not remember ~


The overall analytical

  1. The general design is as follows

2. The last exported Schema constructor As you can see from above, the JS library finally exports the Schema constructor and breaks it up into several blocks: methods on the prototype chain, Register, Warning, Messages, and Validators

Warning method

Set the Warning method before instantiating the Schema. This method is actually a utility method in the util.js file

Warning = () => {}Copy the code
Export let warning = () => {}; export let warning = () => {}; // In production or Node Runtime, no warning message is printed if (typeof Process! == 'undefined' && process.env && process.env.NODE_ENV ! == 'production' && typeof window ! == 'undefined' && typeof document ! == 'undefined' ) { warning = (type, errors) => { if (typeof console ! == 'undefined' && console.warn) {if (errors. Every (e => typeof e === 'string')) {// is actually a console.warn console.warn(type, errors); }}}; }Copy the code

Method the messages

The message method in the Schema is actually an exported instance of the message.js file, which is a message template used to prompt for different types of failure verification.

Function Schema(Descriptor) {this.rules = null; // Save defaultMessages with the private attribute _messages this._messages = defaultMessages; this.define(descriptor); } Schema.messages = defaultMessages; //message.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() {// deep copy const verification = json.parse (json.stringify (this)); cloned.clone = this.clone; return cloned; }}; } export const messages = newMessages();Copy the code

Of course, the Messages template can be modified itself if necessary.

If (messages) {// merge _messages with parameter depth this._messages = deepMerge(newMessages(), messages); } return this._messages; } //util.js //deepMerge is a common method of merging objects in util.js, first iterating through the objects and then using structures on the objects at the next level, Export function deepMerge(target, source) { if (source) { for (const s in source) { if (source.hasOwnProperty(s)) { const value = source[s]; if (typeof value === 'object' && typeof target[s] === 'object') { target[s] = { ... target[s], ... value, }; } else { target[s] = value; } } } } return target; }Copy the code
Import Schema from 'async-validator'; Const cn = {required: '%s required ',}; const descriptor = { name: { type: 'string', required: true } }; const validator = new Schema(descriptor); // Merge cn with defaultMessages deep validator.messages(cn); .Copy the code

validators

  1. Validation methods for various data types provided to users
import validators from './validator/index';
Schema.validators = validators;
Copy the code
  1. Take, for example, the determination of string
  • Rule: Verification rule corresponding to the field name to be verified in the source descriptor. It is always assigned a field property that contains the name of the field to validate.
/ / this way {[field: string] : RuleItem | RuleItem []} / examples / {name: {type: "string", required: true, the message: "Name is required"}}Copy the code
  • Value: the value to be verified in the source object attribute.
  • Callback: Callback to be called after verification. Pass an array of Error instances to determine validation failure. If the validation is synchronous, you can return false, Error, or Error Array directly
callback(errors)
Copy the code
  • Source: The source object passed to the validate method
  • Options: indicates additional options
Export interface ValidateOption {// Whether to cancel internal warnings about invalid values suppressWarning? : boolean; // Stop processing first? When the first validation rule generates an error. : boolean; // Stop processing fields when the first validation rule for the specified field generates an error. "true" means all fields. firstFields? : boolean | string[]; }Copy the code
  • Options. messages: The object containing the validation error message, which will be deeply merged with defaultMessages
Function string(rule, value, callback, source, options) function string(rule, value, callback, source, options) Options) {// List of callback errors const errors = []; / / first verify the required to false or haven't fill in the state is returned directly const validate = rule. The required | | (! rule.required && source.hasOwnProperty(rule.field)); If (isEmptyValue(value, 'string') &&! rule.required) { return callback(); } // Use the rules method to check whether rules.required(rule, value, source, errors, options, 'string') is required; if (! IsEmptyValue (value, 'string')) {// Determine type, range (len, min, Max), Rules.type (rule, value, source, errors, options); rules.range(rule, value, source, errors, options); rules.pattern(rule, value, source, errors, options); If (rule-whitespace === true) {// Add extra validation rules.whitespace(rule, value, source, errors, options) for strings consisting only of whitespace; } } } callback(errors); } export default string;Copy the code

register

In addition to the Validators approach provided above, this register is used to customize judgments

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; };Copy the code

Rules for validators validated methods

/** * All methods pass the following values * @param rule check rule * @param value source object value of this field * @param source source object to check * @param errors @param options check options * @param options.messages Check messages */ export default {required, // Count len, min, Max as number. // Count len, min, Max as number. EnumRule,// Check whether the pattern exists in the enumeration value list,// check whether the check regular expression};Copy the code

Type Has the following types

const custom = [
    'integer',
    'float',
    'array',
    'regexp',
    'object',
    'method',
    'email',
    'number',
    'date',
    'url',
    'hex',
  ];
Copy the code
Const Descriptor = {role: {type: 'enum', enum: ['admin', 'user', 'guest']},};Copy the code

The method above the prototype chain

  1. messages
Messages(messages) {if (messages) {this._messages = deepMerge(newMessages(), messages); } return this._messages; },Copy the code
  1. define

Registration verification rules rules

  1. getType

GetValidationMethod getValidationMethod getValidationMethod getValidationMethod getValidationMethod getValidationMethod getValidationMethod

This method is used to get a validator for the rule

If (typeof rule. Validator === 'function') {return rule. } // Collect all keys defined in a single rule const keys = object.keys (rule); const messageIndex = keys.indexOf('message'); if (messageIndex ! Key.splice (messageIndex, 1); } // There is only one key except message and it is required, Return the required method provided by validators if (keys.length === 1 && keys[0] === 'required') {return validators. } / / otherwise, the last case, according to the rule defined type judgment / / if the type is illegal undefined type directly returns false return the validators [this. GetType (rule)] | | false; },Copy the code
  1. validate
Core method 1. Parameter: source_checksum object O (options) object that describes the checksum processing options (above, suppressWarning, firstFields, first) OC (callback callback function after checksum is completed) 2. Errors is an array of all errors, fiels is an array like {field1:[error,error...] ,field2:[error,error...] The object of the}Copy the code

I have to say what asyncMap is all about first of all. If options.first is no,; Then execute asyncSerialArray and asyncParallelArray respectively according to whether options.firstFields is true.

Export function asyncMap(objArr, option, func, callback) {if (option.first) {if (option.first) {if (option.first) { Call asyncSerialArray to process series array // If a rule fails to check, terminate the check, Const pending = new Promise((resolve, reject) => {const next = errors => {callback(errors); return errors.length ? reject(new AsyncValidationError(errors, convertFieldsError(errors))) : resolve(); }; const flattenArr = flattenObjArr(objArr); asyncSerialArray(flattenArr, func, next); }); pending.catch(e => e); return pending; } let firstFields = option.firstFields || []; if (firstFields === true) { firstFields = Object.keys(objArr); } const objArrKeys = Object.keys(objArr); const objArrLength = objArrKeys.length; let total = 0; const results = []; Const pending = new Promise((resolve, reject) => {// Build next to wrap callback, Next = errors => {results.push. Apply (results, errors); total++; if (total === objArrLength) { callback(results); return results.length ? reject( new AsyncValidationError(results, convertFieldsError(results)), ) : resolve(); }}; if (! objArrKeys.length) { callback(results); resolve(); } objArrKeys.forEach(key => { const arr = objArr[key]; AsyncSerialArray asyncParallelArray asyncParallelArray asyncSerialArray asyncParallelArray asyncParallelArray asyncSerialArray asyncParallelArray asyncParallelArray //asyncSerialArray implements ordered verification. After the asynchronous validator completes, the next validator is enabled. if (firstFields.indexOf(key) ! == -1) { asyncSerialArray(arr, func, next); } else { asyncParallelArray(arr, func, next); }}); }); pending.catch(e => e); return pending; }Copy the code

The last

So, there is a new understanding, and then edit, modify

Like, help like 👍