element from表單源碼分析,涉及到input、select、checkbox、picker、radio等組件的的驗證。表單的驗證用到了async-validator 插件。一塊兒來看看吧。javascript
首先form表單組件由兩部分組成
<form class="el-form" :class="[ labelPosition ? 'el-form--label-' + labelPosition : '', { 'el-form--inline': inline } ]">
<slot></slot>
</form>
複製代碼
結構很簡單,form元素包裹插槽,也就是form-itemhtml
// 注入組件實例
provide() {
return {
elForm: this
};
}
複製代碼
created() {
// 監聽el.form.addField事件,觸發:將form-item實例push到fields
this.$on('el.form.addField', (field) => {
if (field) {
this.fields.push(field);
}
});
// 監聽el.form.removeField事件,觸發:form-item實例有prop規則屬性從fields移除form-item實例
this.$on('el.form.removeField', (field) => {
if (field.prop) {
this.fields.splice(this.fields.indexOf(field), 1);
}
});
}
複製代碼
form組件是作是統一管理具體執行仍是在form-item中
對整個表單進行驗證vue
// 對整個表單進行驗證
validate(callback) {
// 沒有表單數據 拋警告跳出
if (!this.model) {
console.warn('[Element Warn][Form]model is required for validate to work!');
return;
}
let promise;
// 沒有callback而且瀏覽器支持Promise return promise
if (typeof callback !== 'function' && window.Promise) {
promise = new window.Promise((resolve, reject) => {
callback = function(valid) {
valid ? resolve(valid) : reject(valid);
};
});
}
let valid = true;
let count = 0;
// 若是須要驗證的fields爲空,調用驗證時馬上返回callback
if (this.fields.length === 0 && callback) {
callback(true);
}
let invalidFields = {};
// 遍歷全部實例,一個個驗證
this.fields.forEach(field => {
// 這裏的validate是form-item的方法
field.validate('', (message, field) => {
// 若是有返回信息, 則說明驗證失敗
if (message) {
valid = false;
}
// 將錯誤對象複製到invalidFields
invalidFields = objectAssign({}, invalidFields, field);
// 調callback
if (typeof callback === 'function' && ++count === this.fields.length) {
callback(valid, invalidFields);
}
});
});
if (promise) {
return promise;
}
}
複製代碼
objectAssign方法在utils/merge 中,合併對象的方法java
對部分表單進行驗證api
// 對部分表單驗證
validateField(prop, cb) {
let field = this.fields.filter(field => field.prop === prop)[0];
if (!field) { throw new Error('must call validateField with valid prop string!'); }
// 驗證對應表單規則的表單
field.validate('', cb);
}
複製代碼
移除表單項的校驗結果數組
傳入待移除的表單項的 prop 屬性組成的數組,如不傳則移除整個表單的校驗結果
promise
clearValidate(props = []) {
const fields = props.length
? this.fields.filter(field => props.indexOf(field.prop) > -1)
: this.fields;
fields.forEach(field => {
// form-item實例的方法clearValidate(清除驗證狀態與提示)
field.clearValidate();
});
}
複製代碼
對整個表單進行重置,將全部字段值重置爲初始值並移除校驗結果瀏覽器
resetFields() {
// 沒有表單數據 return
if (!this.model) {
// 環境變量,非生產環境拋警告再return
process.env.NODE_ENV !== 'production' &&
console.warn('[Element Warn][Form]model is required for resetFields to work.');
return;
}
this.fields.forEach(field => {
field.resetField();
});
}
複製代碼
// 監聽表單驗證規則
watch: {
rules() {
// validateOnRuleChange未傳入false則當即觸發
if (this.validateOnRuleChange) {
// 驗證
this.validate(() => {});
}
}
}
複製代碼
<div class="el-form-item" :class="[{ 'el-form-item--feedback': elForm && elForm.statusIcon, 'is-error': validateState === 'error', 'is-validating': validateState === 'validating', 'is-success': validateState === 'success', 'is-required': isRequired || required }, sizeClass ? 'el-form-item--' + sizeClass : '' ]">
<!-- 表單域標籤文本 -->
<label :for="labelFor" class="el-form-item__label" :style="labelStyle" v-if="label || $slots.label">
<slot name="label">{{label + form.labelSuffix}}</slot>
</label>
<div class="el-form-item__content" :style="contentStyle">
<!-- 插槽接收表單驗證的元素,input框單選框多選框之類的 -->
<slot></slot>
<!-- 驗證不經過時的message -->
<transition name="el-zoom-in-top">
<div v-if="validateState === 'error' && showMessage && form.showMessage" class="el-form-item__error" :class="{ 'el-form-item__error--inline': typeof inlineMessage === 'boolean' ? inlineMessage : (elForm && elForm.inlineMessage || false) }" >
{{validateMessage}}
</div>
</transition>
</div>
</div>
複製代碼
外層div控制總體樣式,內部分爲兩部分併發
這裏的變量elForm就是form組件provide的from實例
async
provide() { // 注入form-item實例
return {
elFormItem: this
};
},
// 接收form實例
inject: ['elForm']
複製代碼
初始化,須要驗證的讓form組件收集起來,有驗證規則的el.form.blur ,el.form.change 事件監聽起來,等待觸發驗證。
mounted() {
// 有須要驗證的表單
if (this.prop) {
// 向上查找form組件,併發布el.form.addField,暴露form-item實例
// 即讓form組件收集須要驗證的form-item實例
this.dispatch('ElForm', 'el.form.addField', [this]);
// 須要驗證的表單數據
let initialValue = this.fieldValue;
// 是數組
if (Array.isArray(initialValue)) {
initialValue = [].concat(initialValue);
}
// 響應屬性變成普通屬性
Object.defineProperty(this, 'initialValue', {
value: initialValue
});
// 該項驗證規則
let rules = this.getRules();
if (rules.length || this.required !== undefined) {
// 監聽el.form.blur,回調爲bluer事件驗證
// 監聽el.form.change事件,回調爲change事件驗證
this.$on('el.form.blur', this.onFieldBlur);
this.$on('el.form.change', this.onFieldChange);
}
}
}
複製代碼
validate(trigger, callback = noop) {
this.validateDisabled = false;
// 符合規則的trigger
const rules = this.getFilteredRule(trigger);
// 沒有規則也不是必填 返回true
if ((!rules || rules.length === 0) && this.required === undefined) {
// 執行回調
callback();
return true;
}
// 驗證中
this.validateState = 'validating';
const descriptor = {};
// 爲了匹配AsyncValidator插件所須要的格式,須要作規則數據作一些操做
if (rules && rules.length > 0) {
rules.forEach(rule => {
delete rule.trigger;
});
}
// AsyncValidator須要的驗證規則
descriptor[this.prop] = rules;
// 驗證規則AsyncValidator實例對象
const validator = new AsyncValidator(descriptor);
const model = {};
// AsyncValidator須要的驗證數據
model[this.prop] = this.fieldValue;
// 驗證
validator.validate(model, { firstFields: true }, (errors, invalidFields) => {
// 驗證後的狀態
this.validateState = !errors ? 'success' : 'error';
// 驗證提示
this.validateMessage = errors ? errors[0].message : '';
// 執行回調
callback(this.validateMessage, invalidFields);
// form組件發佈validate事件
this.elForm && this.elForm.$emit('validate', this.prop, !errors);
});
}
複製代碼
// 清空驗證狀態及message
clearValidate() {
// 驗證狀態
this.validateState = '';
// 驗證message
this.validateMessage = '';
this.validateDisabled = false;
},
// 重置
resetField() {
this.validateState = '';
this.validateMessage = '';
// 拿到初始數據
let model = this.form.model;// 因此表單數據
let value = this.fieldValue; // 該項表單數據
let path = this.prop; //該項
if (path.indexOf(':') !== -1) {
path = path.replace(/:/, '.');
}
// 該項表單數據
let prop = getPropByPath(model, path, true);
this.validateDisabled = true;
// 重置爲初始表單數據
if (Array.isArray(value)) {
prop.o[prop.k] = [].concat(this.initialValue);
} else {
prop.o[prop.k] = this.initialValue;
}
// 向下尋找select組件,發佈fieldReset事件暴露初始表單數據
this.broadcast('ElTimeSelect', 'fieldReset', this.initialValue);
}
複製代碼
這裏的error是props接收的:若傳入,狀態變爲error,並顯示錯誤信息
watch: {
// 監聽error
error: {
// 當即執行handler
immediate: true,
handler(value) {
// 驗證狀態變爲error,並顯示錯誤信息
this.validateMessage = value;
this.validateState = value ? 'error' : '';
}
},
// 監聽驗證狀態
validateStatus(value) {
this.validateState = value;
}
}
複製代碼
beforeDestroy() {
// 向上尋找form組件,發佈el.form.removeField事件,暴露當前實例
this.dispatch('ElForm', 'el.form.removeField', [this]);
}
複製代碼
是怎麼觸發from驗證的呢
form在mounted時監聽了el.form.blur和el.form.change事件,並指定了驗證的回調函數
this.$on('el.form.blur', this.onFieldBlur);
this.$on('el.form.change', this.onFieldChange);
複製代碼
以input爲例
// form表單發佈el.form.blur事件
this.dispatch('ElFormItem', 'el.form.blur', [this.currentValue]);
複製代碼
以checkbox多選爲例
// 監聽value 向上尋找form組件發佈el.form.change事件暴露value(數組)
this.dispatch('ElFormItem', 'el.form.change', [value]);
複製代碼
等... , 這些時機觸發校驗
$on
就是監聽指定的事件而且指定回調函數,$emit
就是發佈某個事件並傳遞某些數據(可不傳),當監聽的事件名與發佈的事件名一致就會觸發監聽的回調函數而且參數就是對應$emit
傳遞的參數。