zoukankan      html  css  js  c++  java
  • 仿 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>
    
  • 相关阅读:
    背包九讲
    最小生成树 prime + 队列优化
    最小生成树 prime poj1287
    树状数组--转载
    O(n)求1-n的逆元
    数据结构--线段树
    博弈
    RMQ 数据结构
    BZOJ3687 计算子集和的异或和
    Intel Code Challenge Final Round (Div. 1 + Div. 2, Combined)D Dense Subsequence
  • 原文地址:https://www.cnblogs.com/cc-freiheit/p/10789309.html
Copyright © 2011-2022 走看看