在上一篇《vue elementUI組件表單動態驗證失效的問題與解決辦法》中,講到直接修改prop
屬性,未觸發form-item
的重新渲染,所以雖然有校驗*
的標誌,實際上並不會校驗。這是表面現象,最近有了空餘時間,去看看了element form
組件的源碼,找到了根本原因。
源碼分析
- 在
form
組件的created
鉤子函數中添加了el.form.addField
和el.form.removeField
事件監聽,往fields
中添加或刪除field
,校驗的時候會遍歷fields數組,而field
就是form-item
的實例。接着查看form-item
組件的源碼,可以看到在組件掛載後mounted
,如果prop屬性有值
就會觸發el.form.addField
事件,在組件銷燬前beforeDestroy
觸發el.form.removeField
事件。由此可知如果掛載時form-item
組件prop
屬性無值,不會觸發el.form.addField
。
// form.vue
created() {
this.$on('el.form.addField', (field) => {
if (field) {
this.fields.push(field);
}
});
/* istanbul ignore next */
this.$on('el.form.removeField', (field) => {
if (field.prop) {
this.fields.splice(this.fields.indexOf(field), 1);
}
});
}
// form-item.vue
mounted() {
// 重點掛載前有prop屬性,纔會觸發'el.form.addField'
if (this.prop) {
this.dispatch('ElForm', 'el.form.addField', [this]);
// ...
this.addValidateEvents();
}
},
beforeDestroy() {
this.dispatch('ElForm', 'el.form.removeField', [this]);
}
form
表單校驗是調用表單實例的validate
方法,去掉邊界、合法性判斷一類的代碼,其核心源碼如下,從代碼註釋中可以知道,form
表單的校驗方法核心是調用form-item
組件實例的validate
方法。
// form.vue
methods: {
validate(callback) {
// ...
this.fields.forEach(field => {
// 調用form-item實例的validate方法
field.validate('', (message, field) => {
if (message) {
valid = false;
}
invalidFields = objectAssign({}, invalidFields, field);
if (typeof callback === 'function' && ++count === this.fields.length) {
callback(valid, invalidFields);
}
});
});
}
},
watch: {
// 如果rules有變化,強制更新form-item的校驗事件,並觸發一次form實例validate方法
rules() {
// remove then add event listeners on form-item after form rules change
this.fields.forEach(field => {
field.removeValidateEvents();
field.addValidateEvents();
});
if (this.validateOnRuleChange) {
this.validate(() => {});
}
}
}
- 最後看
form-item
的validate
方法,省略一些邏輯判斷和優化代碼,其核心是獲取form
組件的對應屬性的rules
及form-item
自身的rules
,生成AsyncValidator
校驗器的校驗規則描述,並進行數據校驗。
// form-item.vue
validate(trigger, callback = noop) {
this.validateDisabled = false;
// 獲取form組件的對應屬性的rules及form-item自身的rules,並根據觸發器trigger過濾
const rules = this.getFilteredRule(trigger);
// 判斷校驗規則,假值或者rules數組爲空,則跳過。值得注意的是空對象並不是假值,所以不會跳過
if ((!rules || rules.length === 0) && this.required === undefined) {
callback();
return true;
}
this.validateState = 'validating';
// 設置AsyncValidator的校驗規則描述
const descriptor = {};
if (rules && rules.length > 0) {
rules.forEach(rule => {
delete rule.trigger;
});
}
descriptor[this.prop] = rules;
const validator = new AsyncValidator(descriptor);
const model = {};
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);
this.elForm && this.elForm.$emit('validate', this.prop, !errors, this.validateMessage || null);
});
}
從上面的第一點中可以看到,因爲prop
初始值爲空字符串''
,所以form
表單的fileds
不會持有該form-item
實例,故即使後面修改prop
屬性,也不會去校驗該表單項,導致校驗失效。
解決辦法
- 《vue elementUI組件表單動態驗證失效的問題與解決辦法》中講的強制重新渲染
form-item
,當時不知道根本原因採用的笨辦法。 form-item
保留prop
屬性,根據條件動態修改form
組件的rules
對象。比如下面這樣:
watch: {
'formData.age': {
immediate: true,
handler (newVal) {
if (newVal >= 18) {
this.addRule('bankCardNo', [{ required: true, message: '請輸入銀行卡號', trigger: 'blur' }])
} else {
this.addRule('bankCardNo', [])
}
}
}
}
data () {
return {
rules: {}
}
},
methods: {
addRule (prop, rule) {
this.$set(this.rules, prop, rule)
}
}
form-item
保留prop
屬性,根據條件動態修改form-item
組件的rules
屬性。比如下面兩種方式,因爲rules
屬性接受數據和對象。
<el-form-item label="銀行卡號" prop="bankCardNo" :rules="formData.age >= 18 ? [{ required: true, message: '請輸入銀行卡號', trigger: 'blur' }] : []">
<el-input v-model="formData.bankCardNo"></el-input>
</el-form-item>
<!-- 或者 -->
<el-form-item label="銀行卡號" prop="bankCardNo" :rules="formData.age >= 18 ? { required: true, message: '請輸入銀行卡號', trigger: 'blur' } : []">
<el-input v-model="formData.bankCardNo"></el-input>
</el-form-item>
總結
終須由淺入深,浮於表面便是管中窺豹,自身還需靜下心認真研究