接下来,我们开始完整的功能开发
目录结构
整体目录结构如上图所示
- src/components 项目的业务组件/页面
- src/base form表单组件
- src/mixins 混入相关的内容放在这个文件夹(dispatch、capture方法)
form表单之 input组件
首先我们先看看input.vue文件的内容:
<template>
<input :type="type" :placeholder="placeholder" :value="currentValue" @input="inputChange">
</template>
<script>
import Emitter from '@/mixins/emitter'
export default {
mixins: [Emitter],
name: 'w-input',
props: {
value: {
type: String,
default: ''
},
placeholder: {
type: String,
default: ''
},
type: {
type: String,
default: 'text'
}
},
data () {
return {
currentValue: this.value
}
},
methods: {
inputBlur () {
this.dispatch('form-item', 'on-input-blur', this.currentValue)
},
inputChange (e) {
this.currentValue = e.target.value
this.$emit('input', this.currentValue)
// 如果单需要在输入事件input的时候就开始校验 增加下面语句
this.dispatch('form-item', 'on-input-input', this.currentValue)
}
}
}
我们的稍作分析:
- props我们接受三个属性 value(输入框的值)、 type(input的type值,可配password、email等)、placeholder(占位内容)
- data里面定义一个currentValue,默认值为props中传入的value,之所以要定义这个值,是因为不能直接修改props中的值,所以输入框的value我们用currentValue填充
- methods里面有两个事件 输入框的input事件(实时更新数据,添加表单校验的触发事件)和blur事件(添加表单校验的触发事件)
this.$emit('input', this.currentValue)
这句代码是自定义组件使用v-model双向数据绑定的语法,不了解的小伙伴可以查看Vue的官方文档 自定义组件上面使用v-model 或者我自己写的 v-model详解
form表单之 form-item组件
接下来我们查看 form-item组件代码:
<template>
<div class="form-item">
<label :class="isRequired ? 'is_required' : ''">{{label}}:</label>
<slot></slot>
<p v-if="validateState === 'error'" class="error-text">{{validateMessage}}</p>
</div>
</template>
<script>
import AsyncValidator from 'async-validator'
import Emitter from '@/mixins/emitter'
export default {
mixins: [Emitter],
name: 'form-item',
props: {
label: {
type: String,
default: ''
},
prop: {
type: String,
default: ''
}
},
inject: ['form'],
data () {
return {
currentValue: '',
isRequired: false,
validateState: '',
ValidateMessage: ''
}
},
mounted () {
if (this.prop) {
this.dispatch('w-form', 'on-form-item-add', this)
this.currentValue = this.fieldValue
this.isRequired = this.form.rules[this.prop].some(item => item.required)
this.setInputListen()
}
},
computed: {
fieldValue () {
return this.form.modes[this.prop]
}
},
destroyed () {
this.dispatch('w-form', 'on-form-item-delete', this)
},
methods: {
/**
* 添加监听input触发事件
*/
setInputListen () {
// 当前我们先监听blur事件,input事件可以自行添加
this.$on('on-input-blur', this.listenBlur)
},
/**
* input失去焦点回调
*/
listenBlur (val) {
this.validate('blur')
},
getRules () {
const rules = this.form.rules[this.prop] || []
return [].concat(rules)
},
getFilterRules (trigger) {
let rules = this.getRules()
// return rules.filter(rule => this.prop && rule.trigger === trigger)
return rules.filter(
// 这种比较方式的好处: trigger为空 或者匹配成功的情况都为 true
rule => !rule.trigger || rule.trigger.indexOf(trigger) !== -1
)
},
validate (trigger, callback = function () {}) {
const rules = this.getFilterRules(trigger)
if (!rules || !rules.length) {
return true
}
this.validateState = 'validating'
let descriptor = {}
descriptor[this.prop] = rules
const validator = new AsyncValidator(descriptor)
let model = {}
model[this.prop] = this.fieldValue
validator.validate(model, { firstFields: true }, errors => {
this.validateState = !errors ? 'success' : 'error'
this.validateMessage = errors ? errors[0].message : ''
callback(this.validateMessage)
})
},
resetField () {
this.validateState = ''
this.validateMessage = ''
this.form.modes[this.prop] = this.currentValue
}
}
}
</script>
form-item组件是表单验证的核心组件,功能相对来说比较复杂,我们慢慢看:
- props定义了 label(输入框前面的文字定义) prop(用于匹配rules规则和输入框值的属性名)。
- inject 接受form组件的实例(provide/inject),因为form-item需要使用用户在外面定义的校验规则和表单的值。
- data中定义了四个属性:- currentValue 当前表单元素的value值,在data中缓存;- isRequired 是否必填,用于控制必填的样式(可有可无);- validateState 表单的验证状态 验证失败为‘error’;- ValidateMessage 表单验证的错误信息,验证通过信息为空。
- 因为此表单验证支持一键全部校验,所以我们要将当前的form-item实例缓存到form组件中(mounted生命周期),以便于统一操作,然后再组件卸载的时候(destroyed)将组件卸载,所以我们使用 dispatch方法将其分别暴露给form监听。
- 当form-item加载成功之后,我们通过prop判断当前form-tem是否需要验证;为true时 继续往下执行,监听从input.vue中的事件
this.$on('on-input-blur', this.listenBlur)
。回调中再次调用表单验证方法 validate();- validate方法接受两个参数,trigger(过滤符合校验条件的校验规则) callback(回调函数)。通过两个方法getRules() 、getFilterRules()方法获取当前form-item组件当前事件校验的rules。设置validateState校验状态为 validating ;然后调用async-validator的方法开始验证。如果errors存在,则将validateState赋值 error,validateMessage为报错的文字提示。
form表单之 form组件
form-item组件的功能需要结合form.vue才能更好的理解:
<template>
<form>
<slot></slot>
</form>
</template>
<script>
export default {
name: 'w-form',
props: {
rules: {
type: Object,
default: () => {}
},
modes: {
type: Object,
default: () => {}
}
},
data () {
return {
fields: []
}
},
provide () {
return {
form: this
}
},
created () {
this.$on('on-form-item-add', node => {
if (node.prop) {
this.fields.push(node)
}
})
this.$on('on-form-item-delete', node => {
this.fields.splice(this.fields.indexOf(node), 1)
})
},
methods: {
resetFields () {
for (let field of this.fields) {
field.resetField()
}
},
validateAll (callback) {
return new Promise(resolve => {
let valid = true
let count = 0
this.fields.forEach(field => {
field.validate('', errors => {
console.log(errors)
if (errors) {
valid = false
}
if (++count === this.fields.length) {
// 全部完成
resolve(valid)
if (typeof callback === 'function') {
callback(valid)
}
}
})
})
})
}
}
}
</script>
form.vue主要就是传递用户设置的校验规则和表单双向数据绑定的值并 提供一键校验所有表单元素功能的组件;内容分析:
- props中的两个属性分别为 整个表单的校验规则集合rules 和整个表单值的对象集合modes。
- 因为Vue组件渲染机制的原因,form-item中的mounted会优先于父级的mounted渲染,所以在form-item 的mounted中暴露给form的实例只能在created中监听~~
- methods中定义resetFields() 一键重置(一般form表单中不一定有一键重置检验信息该功能,在此不详细描述);validateAll()方法就是一键全部校验;会返回一个Promise实例,方法内部做了两种处理,兼容callback回调的方式和then()的方式处理返回值。
到此,我们整个form基础组件也就开发完了,我们现在找个demo引用实践一下
<template>
<div class="form-box">
<w-form :rules="validateRules" :modes="validateModes" ref="form">
<form-item label="姓名" prop="name">
<w-input name="name" v-model="validateModes.name"/>
</form-item>
<form-item label="邮箱" prop="email">
<w-input name="email" v-model="validateModes.email"/>
</form-item>
<button type="button" class="submit_btn" @click="handleSubmit">表单验证</button>
</w-form>
</div>
</template>
<script>
import WForm from '@/base/form/form'
import FormItem from '@/base/form-item/form-item'
import WInput from '@/base/input/input'
import Emitter from '@/mixins/emitter'
export default {
mixins: [Emitter],
name: 'index',
components: {
WForm,
FormItem,
WInput
},
data () {
return {
validateRules: {
name: [
{
required: true,
message: '用户名不能为空',
trigger: 'blur'
}
],
email: [
{
required: true,
message: '邮箱不能为空',
trigger: 'blur'
},
{
type: 'email',
message: '邮箱格式不正确',
trigger: 'blur'
}
]
},
validateModes: {
name: '',
email: ''
}
}
},
mounted () {
this.capture('w-input', 'test', 'test')
},
methods: {
handleSubmit () {
// this.$refs.form.validateAll(valid => {
// // console.log(valid)
// if (valid) {
// alert('验证通过')
// }
// })
this.$refs.form.validateAll().then(valid => {
if (valid) {
alert('验证通过')
}
})
}
}
}
</script>
整个demo很简单,两个输入框一个按钮,检验触发事件是blur(form-item.vue暂时只响应blur)
data中validateRules就是 async-validator需要的验证规则格式。
当我们对每个输入框失去焦点,响应的输入框校验就触发了,当我们点击按钮,就触发了全部校验。
总结
这个form表单组件以及封装完成,描述的可能比较跳跃,我简单的画了一个思维导图便于对比理解~
这个表单组件只包含了输入框,还有其他的表单元素并没有加入,另外也不支持自定义校验,我们在下一章节继续分析。
备注
源码地址: https://github.com/wangyangsea/wy-validate