目录
1. 需求分析
我们要把我们的表单组件分成两个部分,一个是item部分,一个是整体的 form 部分,form部分由item和button提交按钮共同组成。
在我们单击每个输入框时会触发每一个item的验证规则,然后点击登录按钮会验证整个 form 。
2. 表单功能的简单实现
我们先去 bootstrap 文档里找到 form 表单然后把它的模板代码 copy 过来,当然前提是我们首先要在项目中安装 BootStrap。
现在运行我们的项目,就能看到 form 表单的样式了:
首先我们通过 reactive 来绑定每个输入框需要绑定的数据:
const emailRef = reactive({
val: '',
error: false,
message: ''
})
然后通过 v-model 和我们刚刚定义的数据进行双向绑定:
我们又定义了一个 validateEmail 函数当鼠标失去焦点时触发, 我们在这个方法中定义输入框的验证标准:
const emailReg = /^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/
const validateEmail = () => {
if (emailRef.val.trim() === '') {
emailRef.error = true
emailRef.message = '输入内容不能为空'
} else if (!emailReg.test(emailRef.val)) {
emailRef.error = true
emailRef.message = '输入邮箱格式不正确'
} else {
emailRef.error = false
}
}
现在当我们什么也不输入时,输入框效果:
输入邮箱格式不正确时,输入框效果:
3.抽象验证规则
刚刚我们完成了邮箱的验证逻辑,我们还得做密码框的验证逻辑,如果我们的表单还有很多功能不一样的输入框,那我们得挨个给他们添加验证功能,这样就有大量重复代码,要写非常多冗余的变量和函数,我们作为开发者最忌讳的就是复制粘贴做搬运工,所以我们就想把这部分的逻辑抽离出去作为一个可复用的组件
我们输入框的组件就是图片中的 validate-input,如果我们只需要在父组件中输入要验证的规则和验证失败的信息,把逻辑交给 validate-input 来判断,那整体代码就非常清晰了。
我们通过 rules 属性来传给组件指定验证类型。message字段是出现问题时提示的内容,因为我们的输入框组件可以使用不止一种规则,所以 RulesProp 应该是 RuleProp 的数组。如果以后要添加其他的规则,就可以直接在下面的 type 中添加,这样可扩展性非常高。
interface RuleProp {
type: 'required' | 'email';
message: string;
}
export type RulesProp = RuleProp[]
我们在子组件中定义规则的接口,然后定义都是这种类型的数组结构并把它导出出去方便父组件使用。如果不熟习 typescript 的朋友,就可以把它当作定义一个RuleProp对象,里面有两个属性,一个是规则类型,一个是出现问题时提示的内容。然后再定义一个对象数组,把它导出出去这样父组件向子组件传递的都是这种规定的对象数组。
子组件接受的 props把它断言成 RulesProp 类型的数组:
我们再看一下 validate-input 的逻辑部分:
setup (props) {
const inputRef = reactive({
val: '',
error: false,
message: ''
})
const validateInput = () => {
if (props.rules) {
const allPassed = props.rules.every(rule => {
let passed = true
inputRef.message = rule.message
switch (rule.type) {
case 'required':
passed = (inputRef.val.trim() !== '')
break
case 'email':
passed = emailReg.test(inputRef.val)
break
default:
break
}
return passed
})
inputRef.error = !allPassed
}
}
return {
inputRef,
validateInput
}
}
我们先定义一个 inputRef 对象来绑定输入信息和状态。validateInput 当输入框失去焦点的时候触发这个验证函数。下面我们来看一下这个函数的实现逻辑:
首先通过一个 if 实现当有 props 的时候才做验证。然后通过数组的 every 方法来给每一个数组中的每一个验证对象做判定,every 方法如果全部为真时才为真,有一个为假就是假。他很符合表单验证的逻辑,所以最后 every 方法一定会返回 true 或者 false,我们让一个变量接受它,如果这个变量是真就代表输入框全部验证规则都通过,那么 inputRef 的 error 属性就是 false,这样就不会如下的错误提示:
<span v-if="inputRef.error" class="invalid-feedback">{{inputRef.message}}</span>
在 every 方法中,我们一项一项判断,先设置当前项返回 true ,然后把当前项的 message ,也就是错误提示内容赋值给 inputRef 的 message,然后通过 switch 来判定当前项的状态,这样当当前项不满足规则时,返回的 message 就是当前项的 message
如果最后 inputRef.error 是 false ,那么就添加 bootstrap 中表单错误类,来实现动态绑定:
<input type="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp"
v-model="inputRef.val"
:class="{'is-invalid':inputRef.error}"
@blur = "validateInput"
>
到这里我们就实现了子组件的处理,下面只需要在父组件中把参数传过去就行了:
const emailRules: RulesProp = [
{ type: 'required', message: '电子邮箱地址不能为空' },
{ type: 'email', message: '请输入正确的电子邮箱格式' }
]
const passwordRules: RulesProp = [
{ type: 'required', message: '输入密码不能为空' }
]
我们定义两个 RulesProp 类型的数组作为参数,传递给子组件:
现在启动项目,看一下效果:
这样我们这一节抽离验证规则的目的就达到了。
4. 支持 v-model 双向绑定
现在我们已经把验证规则抽离出来,实现了表单的基本验证,但是有一个痛点需要我们解决,我们在父组件中现在拿不到用户在输入框中输入的值,这样就实现不了下一步的其他需求,在 input 中我们通过 v-model 指令来进行双向绑定可以很轻松地获得用户输入的值,那么在 validate-input 组件中我们如何来实现 v-model 呢?
我们先看一下 vue3 中 v-model 的实现原理:
vue3 中摒弃了 vue2 里通过动态绑定 input 的 value 属性和 input 事件实现的双向绑定,通过 modelValue 这么个属性和 onUpdate:modelValue 这个事件来实现双向绑定。所以我们要实现 v-model 就要有上面的属性和更新输入框的时候出发的事件。
首先我们来写 validate-input 子组件中的内容,在 props 参数中加入 modelValue:
然后用 :value 和 input 事件把原来 input 中的 v-model 替换一下:
<input type="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp"
:value="inputRef.val"
:class="{'is-invalid':inputRef.error}"
@blur = "validateInput"
@input="updateValue"
>
定义 input 事件,注意提交的事件名为 update:modelValue:
const updateValue = (e: KeyboardEvent) => {
const targetValue = (e.target as HTMLInputElement).value
inputRef.val = targetValue
context.emit('update:modelValue', targetValue)
}
我们在父组件中使用一下 v-model ,看看效果:
可以看到我们成功实现了 v-model 的双向绑定:
5.使用 $attrs 支持默认属性
在原生 input 中有很多属性,比如 placeholder ,如果我们在我们的输入框组件中添加这个属性,会正常显示在页面上吗?我们试一下:
启动项目,查看效果:
placeholder没有正常显示出来,我们查看控制台,看看哪里有问题:
可以看到我们的placeholder被直接添加到 input 的父级上了,那如何把属性正确添加在 input 上呢?
1. 首先我们只需要在组件的选项中设置 inheriAttrs: false:
2. 通过 $attrs 把属性添加到元素上:
我们先输出一下 $attrs 看看里面有什么:
这是一个响应式对象,里面包括了我们传递给子组件的属性
下面我们先通过 v-bind 绑定 $attrs :
给我们的 input 组件添加属性:
启动项目,查看输出:
现在成功给组件添加了 placeholder 属性,也成功添加了 type ,密码框也变成了小圆点。
6. 父组件调用子组件中的方法
现在我们要实现的就是点击提交按钮,然后分别进行两个输入框的验证。可是我们的验证方法在 validate-input 这个子组件中,所以我们就得在父组件中调用子组件里的方法来实现表单验证。
1. 给子组件添加 ref 属性:
2. 给提交按钮添加点击事件:
3. 在 setup 中定义响应式对象及点击事件:
const emailChild = ref<InstanceType<typeof ValidateInput>>()
const passwordChild = ref<InstanceType<typeof ValidateInput>>()
const ensureForm = () => {
emailChild.value?.validateInput()
passwordChild.value?.validateInput()
}
这样我们就成功调用了子组件中的 validateInput 方法,实现了我们想要的效果。
4. 启动项目,查看效果:
直接点击提交按钮:
输入错误邮箱格式,点击提交:
到这里我们的表单组件就开发完成了。