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>
    
  • 相关阅读:
    2013-10-31 《问题儿童居然一天两更!?》
    2013-10-31 《October 31st, 2013》
    2013-10-31 《三天里什么都没干……总之把目前为止的代码发了吧……》
    日怎么没人告诉我这博客可以改博文界面的显示宽度的
    俗话说打脸哦不打铁要趁热所以记录下替换图片的方法
    GUI好看码难写不是难写是难看我是说码难看不是GUI
    虽然保持了连续代码生产量但是仔细想想也没什么必要
    重写了电话本代码全面更新居然连续三天每天一个程序
    专注写字典三十年问你怕未又被编码卡了简直难以置信
    我就写个字典居然卡了两天重申一遍文字编码日你大爷
  • 原文地址:https://www.cnblogs.com/cc-freiheit/p/10789309.html
Copyright © 2011-2022 走看看