业务需求
实现一个简易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)
})
}
大功告成