拜讀及分析Element源碼-form表單組件篇

element from表單源碼分析,涉及到input、select、checkbox、picker、radio等組件的的驗證。表單的驗證用到了async-validator 插件。一塊兒來看看吧。javascript

首先form表單組件由兩部分組成

  • form: 統一管理form-item。
  • form-item:負責完成驗證等。

form

結構

<form class="el-form" :class="[ labelPosition ? 'el-form--label-' + labelPosition : '', { 'el-form--inline': inline } ]">
    <slot></slot>
  </form>
複製代碼

結構很簡單,form元素包裹插槽,也就是form-itemhtml

script部分

1.方便與form-item關聯,注入form實例

// 注入組件實例
    provide() {
      return {
        elForm: this
      };
    }
複製代碼

2.實例建立後,fields收集form-item的實例

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);
        }
      });
    }
複製代碼

3.關於表單驗證的一些方法,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();
            });
          }
    複製代碼

4.監聽驗證規則

// 監聽表單驗證規則
    watch: {
      rules() {
        // validateOnRuleChange未傳入false則當即觸發
        if (this.validateOnRuleChange) {
          // 驗證
          this.validate(() => {});
        }
      }
    }
複製代碼

form-item

結構

<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控制總體樣式,內部分爲兩部分併發

  • 第一部分:可展現label或拼接統一的後綴(from的屬性)
  • 第二部分:slot接收input,select等組件,以及驗證不經過時的message展現

這裏的變量elForm就是form組件provide的from實例async

script部分

1.inject接收form實例,並向子孫後代注入form-item實例

provide() { // 注入form-item實例
      return {
        elFormItem: this
      };
    },
    // 接收form實例
    inject: ['elForm']
複製代碼

2.組件$el掛載到實例後

初始化,須要驗證的讓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);
        }
      }
    }
複製代碼
  • 這裏用到的dispatch方法從mixins中引入:找到指定組件,發佈指定事件。

3.驗證方法

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);
        });
      }
複製代碼

4.清空驗證狀態與重置表單驗證方法

// 清空驗證狀態及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);
      }
複製代碼

5.監聽error以及驗證狀態

這裏的error是props接收的:若傳入,狀態變爲error,並顯示錯誤信息

watch: {
      // 監聽error
      error: {
        // 當即執行handler
        immediate: true, 
        handler(value) {
          // 驗證狀態變爲error,並顯示錯誤信息
          this.validateMessage = value;
          this.validateState = value ? 'error' : '';
        }
      },
      // 監聽驗證狀態
      validateStatus(value) {
        this.validateState = value;
      }
    }
複製代碼
6.實例銷燬以前,發佈form移除收集的該form-item實例
beforeDestroy() {
      // 向上尋找form組件,發佈el.form.removeField事件,暴露當前實例
      this.dispatch('ElForm', 'el.form.removeField', [this]);
    }

複製代碼

form表單組件與input等組件的關聯

是怎麼觸發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傳遞的參數。

官網直通實例方法$on以及$emit

相關文章
相關標籤/搜索