需求分析:
现在我们还需要一个整体的表单在单击某个按钮的时候可以循环的验证每个input的值,最后我们还需要有一个事件可以得到最后验证的结果,从而进行下一步的操作
如下,我们应该有一个form表单包裹着全部的input表单;然后有一个提交按钮;点击这个按钮触发一个事件去验证包裹的这些input,从而获取验证的结果。
这里有一个难点就是获取每一个input的验证结果。
一、ValidateForm编码 - 使用插槽slot(实现所有input都在form表单中)
如下我们创建好了基本结构后
用插槽实现相对应样式
我们是在该组件内添加自定义的内容,那么我们想到之前Dropdown下拉菜单栏组件的slot插槽,当时下拉菜单的‘新建文字’‘编辑资料’‘退出登录’我们是怎么做的呢。我们建一个DropdownItem组件是一个新建文字的一个选项的组件,在该组件中用插槽占位,Dropdown也是用插槽占位,然后我们在GlobalHearder标题组件组件中想要多少个下拉选项就直接在DropdownItem中插入即可,插入的这些DropdownItem们就是插入到Dropdown的。如下,这种就可以想要多少个选项就直接在GlobalHearder中加入即可,这样不限定DropdownItem插入多少也不限定Dropdown插入多少,插入多少都可以
但是和之前这个稍微不同的呢,是我们的插槽其实分为2块区域,一块是这个input表单,一块是提交按钮的区域,不传任何内容的时候它会渲染一个默认的按钮,就等于其实我们有两个可以自定义的插槽,我们去文档看看这种方法应该怎么实现,如下通过添加name的方式
如下我们就填入了两个具名插槽
我们到App.vue中导入感受一下
如下我们使用一个叫template的元素,template上有一个属性叫v-slot,这个v-slot指令就是组件内部定义的这个模板名称,它就等于这个submit,那个name为submit的插槽。也可以用缩写:#submit 也是一样的
然后我们做点击提交按钮发送事件。
在子组件中点击按钮,点击事件发送后,在父组件中监听结果。
首先我们要在emit字段里面确定我们要发送的这个自定义事件的名称,之前我们都是使用Object定义它,其实如果没有这个事件的验证,也可以使用数组来定义要触发的事件,我们事件这里就叫form-submit
那么这个事件什么时候触发呢,我们可以给这个submit-area上面添加一个click事件叫submitForm,然后这个点击事件中通过context.emit('form-submit',true)来触发它
然后我们到父组件中监听结果。我们创建一个函数来监听结果,如下定义一个函数叫onFormSubmit,然后在这个子组件标签中通过@form-submit="onFormSubmit"即可
如下,我们定义一个onFromSubmit的函数,子组件那个点击事件中触发的就是父组件中这个事件,这个事件中我们打印一下获取的参数。
如下,点击按钮后,控制台打印出传过来的值了
二、ValidateForm编码 - 尝试父子通讯(在form组件中获取input组件中验证方法返回的值)
现在我们来做在form中完成所有input的验证。
要想在父组件中访问子组件的方法,那么我们就必须拿到这个方法,并且调用,那么怎样拿到一个组件的实例呢,就是说你怎么在父组件App.vue中拿到子组件ValidateInput.vue中的实例(实例即有这个子组件的所有属性、方法)
我们之前获取dom节点,使用了ref这个属性,即在这个dom节点中添加这个ref。那么当这个ref属性不是添加到一个节点上,而是添加到一个自定义组件上又会发生什么有趣的事呢。
如下,我们在父组件App.vue的setup中定义一个ref响应式的变量叫inputRef,然后在子组件validate-input的标签中加上ref="inputRef",然后通过inputRef.val就可以获取这个子组件validate-input的实例,我们在点击事件即onFormSubmit中打印出这个实例
如下可以看到,这个Proxy对象中的这些属性和方法都是子组件validateinput组件的属性和方法,即我们成功获得了子组件的实例对象。我们是想得到验证的input的结果,也就是我们想在父组件中获得validateInput组件中验证表单的方法validateInput返回的结果,结果是验证为true还是false。这种方法我们可以获取子组件实例,也就是我们就可以获得子组件某方法返回的结果。
如上,子组件验证规则这个方法中返回验证结果,然后我们在父组件中通过inputRef.value.validateInput()即可获取到子组件中这个验证方法,如此父组件中即可拿到子组件中验证方法返回的结果了
那这种做法可以在validateForm中使用吗,也就是说我们想在validateForm组件中也获取validateInput组件的验证方法的返回值。
来看validateForm的结构,这时候发现这里没有validate-input标签,变成了slot标签,而这个slot占位符可能有多个validate-input,我们没法用一个变量或者数组来定义它即像上面那样定义一个响应式变量,而且slot也不支持ref属性,那么就无法像上面那样通过定义一个响应式变量,然后在标签中通过ref="这个响应式变量",然后通过这个响应式变量.value来获得这个validateInput组件的实例。
那怎么办呢,那么这个通过ref获取另一个组件实例的方法走不通了,我们就需要其他的一种父组件和子组件通讯方式。
由于slot的特殊性,这时候我们就需要事件监听器来帮忙了。
我们在父组件validate-form中创建一个事件监听器,去监听相应的事件;然后在子组件validate-input中通过某种方法往这个监听器里面手动触发事件,把想要的内容传递过去
我们来想想应该怎么实现
首先我们应该在父组件即validateForm中创建事件监听,也就是this.$on(事件名称A,传过来的函数)创建一个事件监听,然后创建一个数组为空,该函数就把子组件传过来的函数一个个放到数组中;
然后在子组件即validateItem中我们怎么拿到父组件这个事件呢,其实有一个神奇的属性称为$parent,这个$parent可以用来从一个子组件直接访问父组件的实例,所以我们在子组件中通过this.$parent.emit('A',validateInput验证函数),就可以实现在子组件中获取父组件的事件监听函数,同时把子组件的验证函数发过去。
假如email这个item被初始化的时候,email的validateChange函数就会被加到数组中,password这个item被初始化的时候,password的validateChange函数就会被加到数组中,最后在父组件中循环调用这个方法就可以看到每个子组件的执行结果了
接下来我们编码
由于this在setup中无法访问,所以我们先用这个vue2方法创建,如下,发现说vue3中关于这个事件的这三个$on、$off、$once已经被放弃,推荐mitt
三、ValidateForm编码 - 寻找外援mitt
可以看到mitt是一个流行的库
如下可以看到它的API有如下
首先我们来安装它 npm install --save mitt,然后在validate-form组件中引入这个mitt
然后我们到validate-form组件中创建一个事件监听器,并且监听相应的事件。
我们在它用法中可以看到我们直接调用mitt() 函数就可以创建监听器了
所以如下创建监听器mitt(),因为我们要把这个监听器给validate使用,所以我们要把它导出去
然后现在是有了监听器,然后我们定义监听事件callback,然后我们通过emitter.on()把这个监听事件添加到监听器中,注意在组件销毁阶段记得把创建的监听器销毁
现在这个监听器已经设置完毕,它像一个收音机一样正在等待接收信号,现在让我们到validateInput组件中向它发动信息
首先我们要把监听器导入进validateInput组件中,然后在组件onmounted后就可以把信息发送出去了。
如下,我们在onmounted中向监听器中发送东西了
如下,validateInput那边onmounted中把inputRef.val值传给名叫form-item-created的监听器中了,监听器监听到有信息来了,就去触发绑在它身上的这个callback函数并且把传过来的inputRef.val值这个值传给这个callback,该函数中打印出传过来的值
所以如图,控制台中就打印出了validateInput中传过来的输入框的值
这就说明了我们在form中设立的电台成功的收到了input的信号,那我们就成功在两个组件中打通了沟通的桥梁,当然通过这种方法可以发送各种内容,而不仅仅是这个val字符串。
这样我们就实现了子组件向父组件传东西的想法,这样下面子组件把验证函数或者说验证结果传给父组件就行得通了
四、ValidateForm - 传递子组件的验证函数给父组件
子组件向父组件发送子组件中真实的验证函数。
所以validateInput组件中这里应该传入validateInput函数
然后validateForm中应该接,首先我们定义一个空数组,用来放置子组件validateInput传过来的验证函数,这些函数执行以后可以显示错误的信息并且返回input是否通过验证,所以我们要给它一个简单的定义,如下,定义一个类型ValidateFunc是一个函数,返回的是布尔值
然后最重要的一步,就是在这个submitForm即提交事件中,循环执行funcArr数组中传过来的这些验证函数,并且返回所有结果的最终值,并且最后通过这个事件发送出去。
循环调用一系列方法并且最终返回一个布尔,当有一个返回false,那么说明里面有错误,那么整个表单的验证就没有通过,那么就想到了用every方法。
但是当你用every()方法去执行funcArr数组中传过来的这些验证函数时,会发现如果输入框都为空,点击提交,发现只执行了一个验证函数,即只有一个input框显示出不能为空的提示。
这是因为类似every()、some() 这些Array上面的方法,它会提前停止循环,当我们里面有一个验证函数返回false时,所以最终结果就是false,所以它很聪明就不再执行后面的函数就直接返回false了节省代码执行时间,而这不是我们想要的,因为后面的验证函数不执行的话,就无法弹出后面输入框‘不能为空’的提示
所以我们需要运行数组里面的所有函数,然后再去判断是否通过。这时候我们就把every改为map,改成map后会生成一个运行函数以后最终生成一个布尔数组,所以map(func=>func)就生成了一个装满布尔值的数组,然后我们再使用every就可以解决,every就去判断这些布尔值中有没有false即可
如下,得到所有input组件中的验证函数后都存放在一个数组中,然后遍历执行这个数组中的所有函数,然后得到所有input是否全部通过验证的结果即result,最后这个result被传回到了父组件App.vue中
这样就实现了,form组件中获取全部input组件的验证结果,然后判断出整个是否通过验证,并且整个form组件是否通过验证的结果还传回了App.vue组件中