多次点击解决方案

概述

社区vue基础教程用到自定义的v-validator 指令, 首次加载表单登录或注册页,都需要点击两次才能正常跳转
对该问题,本文提供了两个解决办法,一个是settimeout,另一个则利用js事件流原理,仅仅改变模板提交事件的绑定节点就可以了。

使用setTimeout延迟调用

  • 之前方案
    vue最新版2.6.10,用登录或注册方法,也隐式调用了验证指令中绑定的点击事件处理逻辑,而nextTick回调函数块内,有依赖该指令处理逻辑的代码 canSubmit。只要nextTick回调函数体内的代码在指令事件逻辑调用之后执行,就不需要点击两次了。例 如,用setTimeout建个宏任务(目的让验证指令内中的事件绑定逻辑先执行),代码如下
register(e) {
      this.$nextTick(() => {
          const target = e.target.type === "submit" ? e.target : e.target.parentElement;
           setTimeout(() => {
              if (target.canSubmit) {
                 this.submit();
              }
            });
      });
    }

分析

开始的时候也认为与 nexTick 有关,查阅了大量资料,发现vuejs 的nexttick ,与nodejs 中的 nextick 理念一致的,都是在下一次宏任务事件循环中,先处理掉当批次的微任务,再接着干,没毛病。vuejs 中的 nextick 存在的意义在于,确保其回调内的逻辑是在调用者对应的节点渲染完毕之后,再执行

虽然上述方案,也解决了两次点击的问题,但始终不优雅。方案很难让人信服,问题源自点击事件,理应从事件流的角度去解决,而非setTimeout。

事件绑定

自定义的 validator 指令,在v-validator被绑定元素插入父节点时,会调用该指令的钩子函数 inserted。该钩子内,会寻找表单内[type=submit] 元素节点,即提交按钮 button,并在该按钮上动态绑定了监听器

const submitBtn = form ? form.querySelector('[type=submit]') : null
if (submitBtn) {
            const submitHandler = () => {
                validate(el, modifiers, value)
                const errors = form.querySelectorAll('.has-error')
                if (!errors.length) {
                    submitBtn.canSubmit = true
                } else {
                    submitBtn.canSubmit = false
                }
            }
            submitBtn.addEventListener('click', submitHandler, false)
....

以登录组件,提交按钮为例,显然也绑定了一个click事件

<button @click="login" type="submit" class="btn btn-lg btn-success btn-block">
     <i class="fa fa-btn fa-sign-in"></i> 登录
  </button>

使用了v-validator 自定义指令的表单,在执行验证规则时,会隐式的在表单button提交按钮上添加点击事件
该点击事件内,会在表单验证不通过时,给 button 节点对象添加一个canSubmit标识 ,一个布尔值。
而该canSubmit标识 值是模板上绑定的vue点击 @click=“login"事件,能否正常提交表单的条件

成因

  • 提交button元素节点上,同时注册有两个点击事件,一个隐式的指令事件,一个模板绑定的@click事件

  • 后者的处理逻辑依赖于前者,因此正常情况下,二者要有序,必须是先指令事件再模板事件

  • 但对于教程代码而言其,同元素buttonclick类型的可能存在两个点击事件监听器,这样首次加载组件,总是先绑定了模板点击事件,验证指令在button上的点击事件监听器只在input输入验证存在错误时注册,这样button点击

    • 第一次点击时,模板监听执行,canSubmit布尔值不存在(false) —> 验证指令监听执行,button添加cansubmit标识。
    • 第二次点击时,模板监听执行 由于第一次cansubmit已添加(若验证通过它值是true), 执行表单提交

对于dom2级事件addEventListener添加,其本身节点的事件处理逻辑由注册顺序所决定,即对同一节点元素上绑定的多个同类事件,触发该节点事件,同类事件逻辑会依次执行。示例代码如下

<button id="myBtn">点我</button>
<script>
var x = document.getElementById("myBtn");
x.addEventListener("click", myFunction);
x.addEventListener("click", someOtherFunction);
function myFunction() {
    alert ("Hello World!")
}
function someOtherFunction() {
    alert ("函数已执行!")
}
</script>
  • 同一节点一次点击,依次弹出Hello World! ---> 函数已执行!

原理 事件流

向下捕获 –> 目标事件 —>向上冒泡

  • 由成因可知,若都在button上注册点击事件,显然模板上注册的点击事件监听器肯定是第一个执行,需要点击两次是必然。因此,利用事件流特点,分开绑定才是正常解决方案,让button的验证指令点击事件冒泡,在他的上层节点捕获执行表单提交事件回调即可
  • 既然顺序,如此重要,改变模板同节点上的事件布局,就可以实现事件有序执行。

推荐方案

  • ** 仅仅只需要将模板提交按钮上的点击事件绑定到提交按钮的父节点上!!!**
  • 将模板上绑定的按钮提交点击事件,绑定在 [type="submit"]父节点上

这样当点击登录时,产生点击事件,冒泡顺序,会先触发button按钮上的点击事件,先调用执行按钮上的隐式指令点击事件逻辑得到canSubmit值,后调用冒泡父节点span上的click事件,执行login 回调。

  <span @click="login">
         <button type="submit" class="btn btn-lg btn-success btn-block">
              <i class="fa fa-btn fa-sign-in"></i>登录
          </button>
   </span>

上述增加无语义span标签,可能会带来一些副作用,破坏已有的dom结构。通常作法是,在符合条件的既有Dom节点元素上进行,比如将login提交事件逻辑绑定在表单form节点上。

小结

  • 对于vuejs组件模板节点绑定的事件,在当前节点的同类事件中,模板绑定回调最先注册到当前队列
  • 同样的在编辑个人用户资源时,首次进入该页保存也可以用同样的方法解决

补充

在看react,有所悟,重读了vue事件系统api。不同于原生的js事件源绑定,vue和react的事件,本质上都是用了事件代理,即在当前根节点上代理监听内部事件,然后根据不同的事件源,作执行不同的回调。

[基于vue-cli3.x对vuejs实践教程进行重构,零配置,方便新手入门]gitee.com/pardon110/vuejs-demo)

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 4年前 自动加精
pardon110
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
开发者 @ 社科大
文章
134
粉丝
24
喜欢
101
收藏
55
排名:106
访问:8.9 万
私信
所有博文
社区赞助商