仿 ELEMENTUI 實現一個簡單的 Form 表單

  原文:仿 ElmentUI 實現一個 Form 表單javascript

 

1、目標

  ElementUI 中 Form 組件主要有如下 功能 / 模塊:
vue

  • Form
  • FormItem
  • Input
  • 表單驗證

  在這套組件中,有 3 層嵌套,這裏面使用的是 slot 插槽,咱們在接下來的代碼中也須要運用到它。
java

 

2、組件設計

  • e-form 全局校驗,並提供插槽;
  • e-form 單一項校驗和顯示錯誤信息,並提供插槽;
  • e-input 負責數據的雙向綁定

 

3、開始

e-input

<template>
  <div>
    <!-- 1.綁定 value 2.響應 input 事件 -->
    <input type="text" :value="valueInInput" @input="handleInput">
  </div>
</template>

<script>
export default {
  name: 'EInput',
  props: {
    value: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
      valueInInput: this.value // 確保數據流的單向傳遞
    }
  },
  methods: {
    handleInput(e) {
      this.valueInInput = e.target.value;
      this.$emit('input', this.valueInInput); // 此處提交的事件名必須是 ‘input’

      // 數據變了,定向通知 formItem 進行校驗
      this.dispatch('EFormItem', 'validate', this.valueInInput);
    },

    dispatch(componentName, eventName, params) { // 查找指定 name 的組件,
      let parent = this.$parent || this.$root;
      let name = parent.$options.name

      while(parent && (!name || name !== componentName)) {
        parent = parent.$parent;
        if (parent) {
          name = parent.$options.name;
        }
      }
      if (parent) {
        // 這裏,咱們不能用 this.$emit 直接派發事件,由於在 FormItem 組件中,Input 組件的位置只是一個插槽,沒法作事件監聽,
        // 因此此時咱們讓 FormItem 本身派發事件,並本身監聽。修改 FormItem 組件,在 created 中監聽該事件。
        parent.$emit.apply(parent, [eventName].concat(params));
      }
    }
  }
}
</script>

 這裏須要注意的是 v-model 綁定的值與 props 傳遞的值的關係,從而能將修改後的值暴露至頂層自定義組件。使用以下:數組

<template>
  <div id="app">
    <e-input v-model="initValue"></e-input>
    <div>{{ initValue }}</div>
  </div>
</template>

<script>
import EInput from './components/Input.vue';

export default {
  name: "app",
  components: {
    EInput
  },
  data() {
    return {
      initValue: '223',
    };
  },
};
</script>

 

FormItem 的設計

<template>
  <div>
    <label v-if="label">{{ label }}</label>
    <div>
      <!-- 拓展槽 -->
      <slot></slot>
      <!-- 驗證提示信息 -->
      <p v-if="validateState === 'error'" class="error">{{ validateMessage }}</p>
    </div>
  </div>
</template>

<script>
import AsyncValidator from 'async-validator';

export default {
  name: 'EFormItem',
  props: {
    label: { type: String, default: '' },  // 表單項的名稱
    prop: { type: String, default: '' } // 表單項的自定義 prop
  },
  inject: ['eForm'], // provide/inject,vue 跨層級通訊
  data() {
    return {
      validateState: '',
      validateMessage: ''
    }
  },
  methods: {
    validate() {
      return new Promise(resolve => {
        const descriptor = {}; // async-validator 建議用法;
        descriptor[this.prop] = this.eForm.rules[this.prop];
        // 校驗器
        const validator = new AsyncValidator(descriptor);
        const model = {};
        model[this.prop] = this.eForm.model[this.prop];
        // 異步校驗
        validator.validate(model, errors => {
          if (errors) {
            this.validateState = 'error';
            this.validateMessage = errors[0].message;
            resolve(false);
          } else {
            this.validateState = '';
            this.validateMessage = '';
            resolve(true);
          }
        })
      })
    },
    dispatch(componentName, eventName, params) { // 查找上級指定 name 的組件
      var parent = this.$parent || this.$root;
      var name = parent.$options.name;

      while (parent && (!name || name !== componentName)) {
        parent = parent.$parent;

        if (parent) {
          name = parent.$options.name;
        }
      }
      if (parent) {
        parent.$emit.apply(parent, [eventName].concat(params));
      }
    }
  },
  created() {
    this.$on('validate', this.validate); // 'validate' 事件由 e-input 組件通知,在 e-form-item 組件接收到後自行觸發對應方法
  },
  // 由於咱們須要在 Form 組件中校驗全部的 FormItem ,
  // 因此當 FormItem 掛載完成後,須要派發一個事件告訴 Form :你能夠校驗我了。
  mounted() {
    // 當 FormItem 中有 prop 屬性的時候才校驗,
    // 沒有的時候不校驗。好比提交按鈕就不須要校驗。
    if (this.prop) {
      this.dispatch('EForm', 'addFiled', this);
    }
  }
}
</script>

<style scoped>
.error {
  color: red;
}
</style>

 其中, methods 中的方法均是輔助方法,validate() 是異步校驗的方法。緩存

 

Form 的設計

<template>
  <form>
    <slot></slot>
  </form>
</template>

<script>
export default {
  name: 'EForm',
  props: {
    model: {
      type: Object,
      required: true
    },
    rules: {
      type: Object
    }
  },
  provide() { // provide/inject,vue 跨層級通訊
    return {
      eForm: this // form 組件實例, 在其餘組件中能夠經過 this.xxx 來獲取屬性/方法
    }
  },
  data() {
    return {
      fileds: [] // 須要校驗的 e-form-item 組件數組
    }
  },
  methods: {
    async validate(cb) {
      const eachFiledResultArray = this.fileds.map(filed => filed.validate());

      const results = await Promise.all(eachFiledResultArray);
      let ret = true;
      results.forEach(valid => {
        if (!valid) {
          ret = false;
        }
      });
      cb(ret)
    }
  },
  created() {
    // 緩存須要檢驗的組件
    this.fileds = [];
    this.$on('addFiled', filed => this.fileds.push(filed))
  }
}
</script>
相關文章
相關標籤/搜索