简易实现elementUI-form

361 阅读1分钟

业务需求

实现一个简易el-form包括:
  数据收集
  表单校验

需求分析

* 实现form
	指定数据 model、 校验规则rules
    全局校验validate()
* 实现form-item
	label标签
    校验属性prop
    校验规则validate()
    错误信息
* 实现表单input
	数据绑定v-model
    样式图标等

需要掌握对知识点

$attrs && $listeners
inheritAttrs
provide && inject

实现MyInput

input就实现了简单的样式及双向绑定和一些基础事件 源码中还有很多disabled、size,clearable等属性不是此次的重点就不放上来了

<!--
 * @Author: lam-jl
 * @Date: 2020-08-01 20:29:25
 * @LastEditors: lam-jl
 * @LastEditTime: 2020-08-01 20:33:30
 * @FilePath: @/components/form/MyInput.vue
--> 
<template>
    <div class="el-input">
        <input 
            class="el-input__inner" 
            :type="type" 
            :value="value" 
            v-bind="$attrs"		// 获取父组件中未通过props绑定的属性例如placeholder
            @input="handleInput"
            @focus="handleFocus"
            @blur="handleBlur"
            @change="handleChange">
    </div>
</template>

<script>
    export default {
        inheritAttrs: false,	// 组件将不会把未被注册的props呈现为普通的HTML属性
        name: 'MyInput',
        componentName: 'MyInput',
        props: {
            type: {
                type: String,
                default: 'text'
            },
            value: {
                type: String,
                default: ''
            },
        },
        data() {
            return {
                focused: false,
            }
        },
        methods: {
            handleInput(e) {
                this.$emit('input', e.target.value)
            },
            handleFocus(event) {
                this.focused = true;
                this.$emit('focus', event);
            },
            handleBlur(event) {
                this.focused = false;
                this.$emit('blur', event);
                // 注意这里触发校验
                this.$parent.$emit('validate')
            },
            handleChange(event) {
                this.$emit('change', event.target.value);
            },
        },
    }
</script>

实现MyFormItem

首先实现label及其样式 我在这里加上了labelWidth来控制label的长度

<template>
    <div class="el-form-item" >
        <label class="el-form-item__label" :style="labelStyle" v-if="label">
            <slot name="label">{{label}}</slot>
        </label>
        <div class="el-form-item__content" :style="contentStyle">
            <slot></slot>
            <div
                v-if="validateMessage"
                class="el-form-item__error">{{validateMessage}}</div>
        </div>
    </div>
</template>
		computed: {
            labelStyle() {
                const ret = {};
                const labelWidth = this.labelWidth;
                if (labelWidth) {
                    ret.width = labelWidth;
                }
                return ret;
            },
            contentStyle() {
                const ret = {};
                const label = this.label;
                if (!label && !this.labelWidth) return ret;
                const labelWidth = this.labelWidth;
                if (labelWidth === 'auto') {
                    if (this.labelWidth === 'auto') {
                        ret.marginLeft = this.computedLabelWidth;
                    } 
                } else {
                    ret.marginLeft = labelWidth;
                }
                return ret;
            },
        },

其次就要考虑表单的验证了 这里我们引用了elementUI使用的验证库async-validator

import Validator from 'async-validator'

这个时候有一个问题 因为表单规则是写在form上而不是写在form-item上的 那么如何在不确定中间有多少层封装的情况下拿到父组件(祖先组件)中的校验规则呢 这个时候就用到了provide && inject

// MyForm.vue
provide() {
    return {
        MyForm: this
    }
},

// MyFormItem.vue
inject: ["MyForm"],

这个时候我们就能拿到form里的value信息和校验规则rules了

// MyFormItem.vue
		mounted () {
        	// 这里接收到之前MyInput派发过来的事件来进行校验
            this.$on('validate', () => {
                this.validate()
            });
        },
        methods: {
            validate() {
                const rules = this.MyForm.rules[this.prop]
                const value = this.MyForm.model[this.prop]
                const validator = new Validator({
                    [this.prop]: rules
                })
                这里validator返回的是一个promise 有助于最后我们进行表单全局校验时操作
                return validator.validate({
                    [this.prop]: value
                }, errors => {
                    if(errors) {
                        this.validateMessage = errors[0].message
                    } else {
                        this.validateMessage = ''
                    }
                })
            }
        },

好了 这个时候只剩最后一步对表单提交对时候进行校验了

// index.vue
	login() {
        this.$refs.loginForm.validate(isValid => {
          if(isValid) {
            alert('登陆成功')
          } else {
            alert('登陆失败')
          }
        })
      }
// MyForm.vue
			validate(cb) {
                var tasks = this.$children
                .filter(item => {
                	// 这里我们过滤掉所有不含有prop的组件
                    return item.prop
                }).map(item => {
                	// 得到所有form-item验证后返回的promise
                    return item.validate()
                })
                Promise.all(tasks)
                    .then(() => {
                        cb(true)
                    })
                    .catch(() => {
                        cb(false)
                    })
            }

大功告成