目标
- 实现 elForm
- 指定数据、校验规则
- elFormItem
- label 标签添加
- 执⾏校验
- 显示错误信息
- elInput
- 维护数据
el-input
维护数据
- 输入框输入的值, 或者是单选框,复选框选中的值, 亦或者是下拉菜单, datePicker 等值。
el-input 是对 input 的简单封装, el-input 的用法
<el-input v-model="name"></el-input>
而 v-model= 'name' 是 :value = 'name' @input = 'name = value' 的语法糖
所以 el-input.vue 的简单实现可以是下面这样的。
el-input.vue
<template>
<input :value="value" @input="Oninput" />
</template>
<script>
export default {
data() {
return {
value: '',
}
},
methods: {
Oninput(e) {
this.$emit('input', e.target.value)
},
},
}
</script>
el-form-item
el-form-item 对表单元素进行二次封装主要功能有 3 个
- 添加 label 标签
- 执行校验
- 显示错误信息
el-form-item 的基本用法可能是这样的
<el-form-item lable = '用户名:'>
<el-input v-model = 'name'>
</el-form-item>
el-form-item 中要显示的错误信息是由 el-form 的 rules 传入的,校验规则有可能有好多, 具体显示哪个是由 el-form-item 校验后选择的。
如校验规则有 2 条
- 必填项,不能为空
- 长度在 6 - 12 之间
经过校验 如果不符合第一条,则显示第一天错误信息, 否则显示第二条错误信息。
则 el-form-item 维护的 状态 有 error, 外部传入的状态有 label, rules。
又因为 el-form-item 中除了放 el-input 还可以放 el-checkbox, el-select 等,所以需要 用到 solt 进行插入。
el-form-item.vue
<template>
<div>
<label>{{label}}</label>
<slot></slot>
<span v-if="error">{{error}}</span>
</div>
</template>
<script>
export default {
props: {
label: {
type: String,
default: '',
},
rules: {
type: Array,
default() {
return []
},
},
},
data() {
return {
error: '',
}
},
}
</script>
什么时候改变 error 的值,也就是说什么时候开始校验,是在 el-input 的值改变的时候, 我们需要 在 el-input 值改变的时候向外派发一个校验事件 validate
el-input.vue
Oninput(e) {
this.$emit('input', e.target.value)
+ this.$emit('validate', e.target.value) // 传入值并通知父元素可以校验啦
}
el-form-item 中怎么监听这个事件呢,由于 el-input 是通过 slot 插入的, 在 slot 上添加 @validate= 'onValidate' 是监听不到的。
我们可以让 el-form-item 自己派发, 自己监听, 那么 el-input.vue 中应该修改成这样
Oninput(e) {
this.$emit('input', e.target.value)
- this.$emit('validate', e.target.value) // 传值并通知父元素可以校验啦
+ this.$parent.$emit('validate', e.target.value) // 让父组件自己 管理事件派发和监听
}
el-form-item.vue
此处执行校验用到了第三方库, async-validate, 提前 install, 并在 el-form-item.vue 中 import 进来。
+ mounted() {
+ this.$on('validate', (val) => this.validate(val))
+ }
methods: {
+ validate(val) {
+ // 拿到输入的值
+ // const value = val
+ // 拿到校验规则 rules, 父元素传进来的,直接用
+ const rules = this.rules
+ // 进行校验
+ if (!rules) {
+ return Promise.resolve(true);
+ }
+ // 创建描述对象
+ const descriptor = { [this.prop]: rules };
+ // 创建校验函数
+ const validator = new Schema(descriptor);
+ // 校验
+ return validator.validate({ [this.prop]: value }, errors => {
+ if (errors) {
+ this.error = errors[0].message;
+ } else {
+ this.error = "";
+ }
+ });
+ }
}
此时这样已经可以使用了,用法应该是这样的
<template>
<div>
<el-form-item label="用户名" :rules="rules.name">
<el-input v-model="name" />
</el-form-item>
<el-form-item label="密码" :rules="rules.password">
<el-input v-model="password" />
</el-form-item>
</div>
</template>
<script>
import Schema from 'async-validator'
export default {
data() {
return {
name: '',
password: '',
rulse: {
name: [
{ required: true, message: '必填项, 不能为空' },
{ max: 12, min: 6, message: '长度在6-12之间' },
],
password: [
{ required: true, message: '不填密码怎么登录' },
{ max: 6, message: '密码这么长, 能记得住吗' },
],
},
}
},
}
</script>
和咱们平时使用的 el-form-item 用法不太一样, 数据 model 和 规则 rules 都是由 el-form 管理的。
<template>
<div>
<el-form :model="model" :rules="rules">
<el-form-item label="用户名" prop="name">
<el-input v-model="model.name" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="model.password" />
</el-form-item>
</el-form>
</div>
</template>
<script>
import Schema from 'async-validator'
export default {
data() {
return {
model: {
name: '',
password: '',
},
rulse: {
name: [
{ required: true, message: '必填项, 不能为空' },
{ max: 12, min: 6, message: '长度在6-12之间' },
],
password: [
{ required: true, message: '不填密码怎么登录' },
{ max: 6, message: '密码这么长, 能记得住吗' },
],
},
}
},
}
</script>
el-form
el-form 的主要功能是指定数据和校验规则
用法就如同上面那样
el-form 的简单实现可能是这样的
el-form.vue
<template>
<div>
<slot></slot>
</div>
</template>
<script>
export default {
props: {
model: {
type: Object,
default() {
return {}
},
},
rules: {
type: Object,
default() {
return {}
},
},
},
}
</script>
el-form 中的 model 和 rules 怎么传递给 el-form-item 和 el-input 呢,使用 provide 和 inject 进行 父和后代组件进行通信。
provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。所以我们把 this 传递下去, 后代组件拿到 el-form 组件实例之后就可以轻松访问 model, rules 等 属性和方法。
el-form.vue
<template>
<div>
<slot></slot>
</div>
</template>
<script>
export default {
+ provide() {
+ return {
+ form: this
+ }
+ },
props: {
model: {
type: Object,
default() {
return {}
},
},
rules: {
type: Object,
default() {
return {}
},
},
},
}
</script>
而 el-form-item 中获取 value 和 rules 的方法就要改变一下了
el-input 派发 validate 事件的时候也不用 传值了
el-input.vue
- this.$parent.$emit('validate', e.target.value)
+ this.$parent.$emit('validate') // 只需要通知, 可以校验啦, 就行了
el-form-item.vue
export default {
+ inject: ['form'], // 注入祖先组件, 传进来的数据
props: {
label: {
type: String,
default: ''
},
prop: {
type: String,
default: ''
}
},
mounted() {
- this.$on('validate', (val) => this.validate(val))
+ this.$on('validate', () => this.validate())
}
methods: {
- validate(val) {
+ validate() {
// 拿到输入的值
- const value = val // 拿到对应的值
+ const value = this.form.model[this.prop] // 拿到对应的值
- // 拿到校验规则 rules, 父元素传进来的,直接用
- const rules = this.rules
+ const rules = this.form.rules[this.prop] // 拿到对应的规则
// 进行校验
if (!rules) {
return Promise.resolve(true);
}
// 创建描述对象
const descriptor = { [this.prop]: rules };
// 创建校验函数
const validator = new Schema(descriptor);
// 校验
return validator.validate({ [this.prop]: value }, errors => {
if (errors) {
this.error = errors[0].message;
} else {
this.error = "";
}
});
}
}
}
提交表单
form 表单的正确使用姿势可能是这样的
<template>
<div>
<el-form ref="form" :model="model" :rules="rules">
<el-form-item prop="name" label="用户名">
<el-input v-model="model.name"></el-input>
</el-form-item>
<el-form-item prop="password" label="密码">
<el-input v-model="model.password"></el-input>
</el-form-item>
<el-form-item>
<button @click="checkForm">提交表单</button>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
methods: {
checkForm() {
this.$refs.form.validate((valid: any) => {
if (valid) {
console.log('提交')
} else {
console.log('不能提交')
}
})
},
},
}
</script>
提交表单需要主动校验所有规则, 需要给 form 添加 validate 方法。
el-form.vue
validate(cb) {
let flag = false
const tasks = this.$children.map(item => item.prop) // 排除掉不需要校验的组件
Promise.all(tasks).then(() => {
flag = true
cb(true)
}).catch(err => {
flag = false
cb(true)
})
}