文章目錄
- 場景
- 思考
- Mixin
- 具體代碼
- 目錄結構
- validator相關代碼
- FormItem相關
- 按需參考
- 使用
- 總結
場景
ElementUI的Form表單組件自帶的校驗規則是不是有點少,通過yarn.lock查詢ElementUI得知校驗使用了async-validator依賴

閱讀async-validator相關檔案得知內置型別如下:
- string: 必須是 string,默認型別;
- number: 必須是 number;
- boolean: 必須是 boolean;
- method: 必須是 function;
- regexp: 必須是正則或者是在呼叫 new RegExp 時不報錯的字串;
- integer: 必須是number型別且為整數;
- float: 必須是number型別且為浮點數;
- array: 必須是陣列,通過 Array.isArray 判斷;
- object: 必須是物件且不為陣列;
- enum: 值必須出現在 enum 列舉值中;
- date: 合法的日期,使用 Date 物件判斷;
- url: url型別;
- hex: 16進制;
- email: 郵箱地址;
常用型別有email,卻沒phone,如何自定義規則型別呢,比如ip、mac、phone,且達到同樣的效果,
寫法如下:
computed: {
rules() {
const required = true;
return {
email: [{ required, type: 'email', message: '請輸入正確的郵箱' }],
phone: [{ required, type: 'phone', message: '請輸入正確的手機號碼' }],
url: [{ required, type: 'url', message: '請輸入正確的url' }],
ip: [{ required, type: 'ip', message: '請輸入正確的IP地址' }],
mac: [{ required, type: 'mac', message: '請輸入正確的MAC地址' }]
}
}
},
實作效果如下:(左:失敗、右:成功)
思考
通過閱讀ElementUI的 FormItem組件原始碼,FormItem組件validate函式參考了async-validator,具體到下面29行代碼,它初始化了一個校驗器,若我們在初始化之前改造AsyncValidator函式是不是就能實作自定義型別擴展?
FormItem核心原始碼解讀:
// 路徑 node_modules\element-ui\packages\form\src\form-item.vue
import AsyncValidator from 'async-validator';
export default {
methods: {
// 核心校驗函式 trigger值為change、blur 默認change
validate(trigger, callback = noop) {
// 開放校驗
this.validateDisabled = false;
// 過濾對應trigger的規則
const rules = this.getFilteredRule(trigger);
// 若無規則或無必填 回呼并回傳
if ((!rules || rules.length === 0) && this.required === undefined) {
callback();
return true;
}
// 設定當前校驗狀態:校驗中
this.validateState = 'validating';
// 以下代碼可參考async-validator使用檔案
const descriptor = {};
// 因為async-validator的rules沒有trigger屬性 洗掉每條規則的trigger
if (rules && rules.length > 0) {
rules.forEach(rule => {
delete rule.trigger;
});
}
// 以FormItem當前prop屬性為鍵賦值對應rules
descriptor[this.prop] = rules;
// 以descriptor為構造引數 初始化一個校驗器
const validator = new AsyncValidator(descriptor);
const model = {};
// 獲取當前FormItem的對應欄位值
model[this.prop] = this.fieldValue;
// 觸發表單校驗 firstFields: true 指當某一規則校驗失敗時,終止校驗;
validator.validate(model, { firstFields: true }, (errors, invalidFields) => {
this.validateState = !errors ? 'success' : 'error';
this.validateMessage = errors ? errors[0].message : '';
// 回呼校驗資訊、不符合的欄位
callback(this.validateMessage, invalidFields);
// 對應 Form Events 如果elForm存在,則每次觸發validate事件
// 引數:被校驗的表單項 prop 值,校驗是否通過,錯誤訊息(如果存在)
this.elForm && this.elForm.$emit('validate', this.prop, !errors, this.validateMessage || null);
});
},
}
}
通過閱讀asycn-validator官方檔案,使用例子如下,是不是很熟悉,FormItem組件validate函式照著官方例子進一步封裝,下面的Schema也就是上面的AsyncValidator,只要弄懂Schema大概便能實作我們要的效果,
import Schema from 'async-validator';
const descriptor = {
// name欄位的校驗規則
name: {type: "string", required: true},
};
const validator = new Schema(descriptor);
validator.validate({ name: "muji" }, (errors, fields) => {
if (errors) {
// 校驗不通過 do something
return;
}
// 校驗通過 do something
});
通過閱讀asycn-validator原始碼,Schema類開放了一個register注冊器,不過官方檔案并沒有這個API的介紹,推測是注冊校驗規則型別,
// 路徑node_modules\async-validator\es\index.js
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;
};
追溯到validators,這里存放了內置型別,驗證了剛才的設想,我們在new AsyncValidator(descriptor)之前注冊自己想要的型別,便可擴展,

我們拎個默認型別string的原始碼觀摩一下:
// node_modules\async-validator\es\validator\string.js
import rules from '../rule/';
import { isEmptyValue } from '../util';
/**
* 對string型別校驗.
*
* @param rule 校驗規則.
* @param value 源物件欄位的值.
* @param callback 回呼函式.
* @param source 校驗后的源物件.
* @param options 校驗選項.
* @param options.messages 校驗資訊.
*/
function string(rule, value, callback, source, options) {
var errors = [];
var validate = rule.required || !rule.required && source.hasOwnProperty(rule.field);
if (validate) {
// 如果表單值為空 且不是必填 不校驗
if (isEmptyValue(value, 'string') && !rule.required) {
return callback();
}
// 校驗必填
rules.required(rule, value, source, errors, options, 'string');
if (!isEmptyValue(value, 'string')) {
// 以下代碼為校驗邏輯 可換成自定義業務校驗
// 校驗值型別
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) {
// 校驗空白字符
rules.whitespace(rule, value, source, errors, options);
}
}
}
callback(errors);
}
export default string;
如果我們擴展一個phone型別,就要實作類似上面這個string校驗函式,
上面26到36行代碼可換成我們需要的校驗函式,通過register函式注冊,便注冊了一個phone型別,
校驗函式代碼如下:
function validator(rule, value, callback, source, options) {
const errors = [];
// 如果表單值為空 且不是必填 不校驗
if (isEmptyValue(value, 'string') && !rule.required) {
return callback();
}
// 校驗必填
rules.required(rule, value, source, errors, options);
// 手機號校驗
if (!isEmptyValue(value, 'string') && !/^1\d{10}$/.test(value)) {
errors.push('phone格式不正確');
}
return callback(errors);
}
上面10到12代碼是會根據業務變化的的,其余代碼都是不變的,我們將校驗業務抽出封裝一下,
代碼如下(上下對比一下):
function validator(rule, value, callback, source, options) {
const type = rule.type;
const errors = [];
// 如果表單值為空 且不是必填 不校驗
if (isEmptyValue(value, 'string') && !rule.required) {
return callback();
}
// 校驗必填
rules.required(rule, value, source, errors, options);
// 自定義規則校驗 ruleDict存放各種自定義的校驗規則
if (!isEmptyValue(value, 'string') && !ruleDict[type](value)) {
errors.push(format('%s 格式不正確', rule.fullField));
}
return callback(errors);
}
ruleDict 存放各個型別的校驗,比如port、phone等等,
部分代碼如下:
// ruleDict校驗規則
export default {
// 埠號
port(value) {
return /^[1-9][0-9]*$/.test(value) && parseInt(value, 10) <= 65535;
},
// 手機號
phone(value) {
return /^1\d{10}$/.test(value);
}
};
統一進行register,并將 Schema吐出,
// 注冊validator
Object.keys(ruleDict).forEach(key => {
Schema.register(key, validator);
});
export default Schema;
Schema改造好了,那FormItem組件怎么參考呢?這是就想到了我們把原始碼整個copy一份不就好了?
這是一種辦法,不過還有更好的,本文主角——混入(mixin);(總是最后出場),
Mixin
混入 mixin是Vue自帶的,用來復用Vue組件一種靈活方式,mixin物件可以包含任意組件選項,常見選項props、data、components、methods 、computed等等,還有常見的生命周期鉤子比如created、mounted,
不僅mixin物件和組件之間互不影響,而且當組件和mixin物件含有同名選項時,這些選項將以恰當的方式進行“合并”,
精簡混入代碼如下:
import { FormItem } from 'element-ui';
import { noop } from 'element-ui/src/utils/util';
// 引入擴展后的校驗器
import AsyncValidator from './validator/index';
export default {
name: 'ElFormItem',
// 混入一個FormItem組件
mixins: [FormItem],
methods: {
// 核心校驗函式 trigger值為change、blur 默認change
validate(trigger, callback = noop) {
// 省略其余代碼
const validator = new AsyncValidator(descriptor);
// 省略其余代碼
}
}
};
如上代碼,只要混入了FormItem組件,即可擁有其全部組件選項,即復制版FormItem組件,再重寫methods的validate函式即可引入我們擴展后的AsyncValidator,
這么實作的好處:
- 不用拷貝整個代碼;
- 組件和復用組件之間互不影響,并以恰當方式合并;
- ElementUI組件升級,最多影響
validate這一個函式;
最后再按需參考FormItem組件:
// 引入mixin后的FormItem組件
import FormItem from '@/components/Mixins/FormItem/index.js'
// 按需參考
Vue.component(FormItem.name, FormItem);
具體代碼
目錄結構
|-- components # 組件
| |-- Mixins # 混用組件
| | |-- FormItem
| | | |-- index.js # mixin后的ElementUI FormItem組件
| | | |-- validator # async-validator 校驗器相關
| | | |-- index.js # 注冊自定義組件
| | | |-- rules.js # 自定義校驗規則
validator相關代碼
// src\components\Mixins\FormItem\validator\rules.js
const patterns = {
ip: /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/,
mac: /^(([a-fA-F0-9]{2}[:-]?)){5}[a-fA-F0-9]{2}$/
};
export default {
ip(value) {
return patterns.ip.test(value);
},
port(value) {
return /^[1-9][0-9]*$/.test(value) && parseInt(value, 10) <= 65535;
},
mac(value) {
return patterns.mac.test(value);
},
phone(value) {
return /^1\d{10}$/.test(value);
}
};
// src\components\Mixins\FormItem\validator\index.js
import Schema from 'async-validator';
import rules from 'async-validator/lib/rule';
import { format, isEmptyValue } from 'async-validator/lib/util';
// 規則串列
import ruleList from './rules';
/**
* @param rule 校驗規則.
* @param value 源物件欄位的值.
* @param callback 回呼函式.
* @param source 校驗后的源物件.
* @param options 校驗選項.
* @param options.messages 校驗資訊.
*/
function validator(rule, value, callback, source, options) {
const type = rule.type;
const errors = [];
// 如果表單值為空 且不是必填 不校驗
if (isEmptyValue(value, 'string') && !rule.required) {
return callback();
}
// 校驗必填
rules.required(rule, value, source, errors, options);
// 自定義規則校驗
if (!isEmptyValue(value, 'string') && !ruleList[type](value)) {
errors.push(format('%s 格式不正確', rule.fullField));
}
return callback(errors);
}
// 注冊validator
Object.keys(ruleList).forEach(key => {
Schema.register(key, validator);
});
export default Schema;
FormItem相關
// src\components\Mixins\FormItem\index.js
import { FormItem } from 'element-ui';
import { noop } from 'element-ui/src/utils/util';
import AsyncValidator from './validator/index';
export default {
name: 'ElFormItem',
mixins: [FormItem],
methods: {
// 核心校驗函式 trigger值為change、blur 默認change
validate(trigger, callback = noop) {
// 開放校驗
this.validateDisabled = false;
// 過濾對應trigger的規則
const rules = this.getFilteredRule(trigger);
// 若無規則或無必填 回呼并回傳
if ((!rules || rules.length === 0) && this.required === undefined) {
callback();
return true;
}
// 設定當前校驗狀態:校驗中
this.validateState = 'validating';
// 以下代碼可參考async-validator使用檔案
const descriptor = {};
// 因為async-validator的rules沒有trigger屬性 洗掉每條規則的trigger
if (rules && rules.length > 0) {
rules.forEach(rule => {
delete rule.trigger;
});
}
// 以FormItem當前prop屬性為鍵賦值對應rules
descriptor[this.prop] = rules;
// 以descriptor為構造引數 初始化一個校驗器
const validator = new AsyncValidator(descriptor);
const model = {};
// 獲取當前FormItem的對應欄位值
model[this.prop] = this.fieldValue;
// 觸發表單校驗 firstFields: true 指當某一規則校驗失敗時,終止校驗;
validator.validate(model, { firstFields: true }, (errors, invalidFields) => {
this.validateState = !errors ? 'success' : 'error';
this.validateMessage = errors ? errors[0].message : '';
// 回呼校驗資訊、不符合的欄位
callback(this.validateMessage, invalidFields);
// 對應 Form Events 如果elForm存在,則每次觸發validate事件
// 引數:被校驗的表單項 prop 值,校驗是否通過,錯誤訊息(如果存在)
this.elForm && this.elForm.$emit('validate', this.prop, !errors, this.validateMessage || null);
});
}
}
};
按需參考
在main.js里按需參考
// 引入mixin后的FormItem組件
import FormItem from '@/components/Mixins/FormItem/index.js'
// 按需參考
Vue.component(FormItem.name, FormItem);
使用
寫法還是和原來一樣如下:
<template>
<el-form ref="form" :model="form" :rules="rules" label-width="120px">
<el-form-item label="ip" prop="ip">
<el-input v-model="form.ip" />
</el-form-item>
<el-form-item label="MAC" prop="mac">
<el-input v-model="form.mac" />
</el-form-item>
</el-form>
</template>
<script>
export default {
data() {
return {
form: {}
}
},
computed: {
rules() {
const required = true;
return {
ip: [{ required, type: 'ip', message: '請輸入正確的IP地址', trigger: 'blur' }],
mac: [{ required, type: 'mac', message: '請輸入正確的MAC地址', trigger: 'blur' }]
}
}
},
}
</script>
寫法還是和原來一樣,卻多了自定義校驗規則,豈不美哉,
總結
通過原始碼解讀,一步一步摸索出來的,新人創作不易,求點贊關注給點動力,
代碼其實放的差不多了,點贊只要超過10,放出具體demo,感謝各位大佬
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/300261.html
標籤:其他
