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


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

 

一、目標

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

  • Form
  • FormItem
  • Input
  • 表單驗證

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

 

二、組件設計

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

 

三、開始

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>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM