美文网首页每日一记让前端飞Web 前端开发
#每日一记#防止按钮在短时间内重复点击

#每日一记#防止按钮在短时间内重复点击

作者: 罗小黑写写文字 | 来源:发表于2018-03-14 15:32 被阅读61次
    每日一记 - 但并不日更

    很多时候我们点击按钮来提交数据,但是在网络条件不好或者交互提示不明确的情况下,用户会在段时间内多次点击按钮,如果没有对按钮做保护就会造成重复的数据提交,造成数据异常,今天就分享一个比较通用的解决方案。

    解决这个问题的思路就是增加一个变量来维护现有按钮的状态,但是一个页面里如果有很多按钮,那么就要申明同等数量的变量,这对维护来说很不友好。

    现有问题

    <div class="button">
      提交
    </div>
    
    var button = document.querySelector('.button');
    
    button.onclick = submit;
    
    function submit (e) {
      // 模拟异步
      var promiseCb = new Promise(function (resolve, reject) {
        setTimeout(function () {
          resolve('提交成功');
        }, 1000);
      })
      
      return promiseCb.then(
        function (res) {
          // 处理回调
          console.log(res);
        }
      )
    }
    
    没有提交中的保护

    解决方法

    为了更好的封装旧的代码,就必须避免增加额外的变量,所以写了一个 actionDelegate 函数来对原有的 action 进行封装,而原有的代码只需要修改一下就可以了

    button.onclick = submit;
    ↓
    button.onclick = actionDelegate(submit);
    
    function actionDelegate (action) {
      // do something
    }
    

    然后我们要对 action 进行类型的判断,如果 action 返回的是普通的对象,那么我们认为这个 action 是一个同步的函数;如果 action 返回的是一个 promsie,那么我们认为我们需要等待这个 promise 状态结束后才能让这个 action 再次执行

    function actionDelegate (action) {
      // 获取函数返回值
      var returnValue = action(e);
    
      // 判断返回值是 promise
      if (returnValue && returnValue.constructor && 
        returnValue.constructor.name === 'Promise') {
          // 保护按钮不被狂点
      }
      else {
        // let it go
      }
    }
    

    所以在 submit 函数中最终必须要返回一个 promise 就变得很重要

    function submit (e) {
      // 模拟异步
      var promiseCb = new Promise(...)
      
      return promiseCb
    }
    

    接着我们就要处理最重要的部分了,那就是保存按钮的状态,在这个案例里我通过 event 获取到了按钮的 node,并且把状态保存在 node 的 attr 上,这边也可以使用其他的方式去储存状态

    function actionDelegate (action) {
      return function (e) {
        if (e.target.getAttribute('progress-status') === 'processing') {
          // 如果按钮上有处理中的状态则跳过后续逻辑
          return false;
        }
        
        // 获取函数返回值
        var returnValue = action(e);
    
        // 判断返回值是 promise
        if (returnValue && returnValue.constructor && 
            returnValue.constructor.name === 'Promise') {
          
          // 关键点 把按钮状态保存在 node 的属性上 
          e.target.setAttribute('progress-status', 'processing')
        
          return returnValue.then(
            function () {
              // promise 结束后重置状态
              e.target.setAttribute('progress-status', 'initial');
            }
          )
        }
      }
    }
    

    最后代码组装起来就是下面的样子

    var button = document.querySelector('.button');
    
    button.onclick = actionDelegate(submit);
    
    function submit (e) {
      // 模拟异步
      var promiseCb = new Promise(function (resolve, reject) {
        setTimeout(function () {
          resolve('提交成功');
        }, 1000);
      })
      
      return promiseCb.then(
        function (res) {
          // 处理回调
          console.log(res);
        }
      )
    }
    
    function actionDelegate (action) {
      return function (e) {
        if (e.target.getAttribute('progress-status') === 'processing') {
          // 如果按钮上有处理中的状态则跳过后续逻辑
          return false;
        }
        
        // 获取函数返回值
        var returnValue = action(e);
    
        // 判断返回值是 promise
        if (returnValue && returnValue.constructor && 
            returnValue.constructor.name === 'Promise') {
    
          var originInnerHTML = e.target.innerHTML;
          
          // 关键点 把按钮状态保存在 node 的属性上 
          e.target.setAttribute('progress-status', 'processing')
          e.target.innerHTML = '提交中...';
        
          return returnValue.then(
            function () {
              // promise 结束后重置状态
              e.target.setAttribute('progress-status', 'initial');
              e.target.innerHTML = originInnerHTML;
            }
          )
        }
      }
    }
    
    执行中则忽略点击

    后记

    本来是用 angularjs 实现的一个指令,用来代替 ng-click 的,后来发现在别的项目里也要用,所以就用原生的代码重新实现了逻辑。在 angularjs 中可能会更好实现,因为指令本身会有独立的作用域就不需要重复申明变量了。今天在写教程的时候突然发现用 attr 来实现可能更方便,各位如果有更好的实现方式可以来交流。

    谢谢

    如果喜欢这篇文章 可以关注专栏
    也请点赞分享哦

    JSbin

    demo 源码

    相关文章

      网友评论

        本文标题:#每日一记#防止按钮在短时间内重复点击

        本文链接:https://www.haomeiwen.com/subject/hqjfqftx.html