有什么好方法可以避免用户点击频繁触发JS事件

比如,不停点"下一页",不停点"前1小时",不停的点"升序降序"。
我希望用户点后只触发一次,等Ajax请求结束了才能再点,或者隔一段时间才允许再点。

当然,我看到了部分项目正在用的方法

  • 点一下按钮,按钮变禁用
  • 设置一个JS全局变量,某方法每调一次都会检查和该方法上次运行时间的间隔
  • 触发一个屏幕遮罩,用户的点击被透明的DIV墙挡住

我不想为每个需要用到的地方写特例,那样业务代码太多了点。
有没有现成的...设计模式?或是什么方法

阅读 8.7k
10 个回答

给按钮一个事件 onClick ,可以用 async 和 await 配合,等待请求完毕也就是 onClick 绑定的事件执行完毕,才能再次点击。 这样即可以代码公用,而且防止多次点击触发。

例如:
image.png

使用方式:
image.png

“防抖节流”,常见问题,也是高频面试题,原理上面的大佬已经讲了,短时间调用一次,自己可是试着去写写,setTimeout或者设置开关。

参考淘宝的置灰,让她不可用即可

我目前的做法是把ajax或者axios封装一下,然后加入loading。
这样每次请求前都会弹出loading,等请求回调回来loading消失。
伪代码,以axios为例

//请求拦截器
service.interceptors.request.use(
  config => {
    showLoading()
    return config
  },
  error => {
    hideLoading()
    return Promise.reject(error)
  }
)
//响应拦截器
service.interceptors.response.use(
 response => {
    hideLoading()
    return response
  },
  error => {
    hideLoading()
    return Promise.reject(error)
  }
)  

看到有建议加 loading 的,其实是实现了一个简单的状态机:

image.png

  • 当处于 IDLE 状态时,只能发起 load 事件。点击按钮后发起请求,同时状态转为 LOADING;
  • success/failed 之前再次点击按钮,因为状态处于 LOADING,无法发起 load 事件,点击无效;

如果是这种简单的场景用一个变量没什么问题,但如果有更加复杂的状态,比如:

image.png

就必须要有状态机的意识,才好控制复杂度,也可以借助 xstate/robot 这类第三方库。

  • 研究一下“节流”,
  • 禁用是最直接的解决办法,等 Ajax 处理完成之后再解禁就好

从你的问题来看,其实你问题的核心不是解决重复点,而是解决重复提交的问题。
你前面自己提到的其实都是从解决重复点出发的方法,而且都是有效的方法,边城提到的有一定解决重复提交。

这个其实根本的有这样一些思路:

  1. 在获取到返回或者超时前禁用来解决重复提交(或者重复点)
  2. 让提交幂等,并在服务器端处理,使得只有一个有效返回(多的被忽略)

加个loading加载,看起来效果会好一点,也可以解决Ajax请求结束了才能再点,,,和按钮变禁用原原理是一样的,不过感觉loading效果要比按钮禁用要好的多,,,,大部分像表格上一页下页面一般也都加loading吧

除了防抖和节流

你也可以在点击按钮的时候先置按钮为disable状态,然后ajax有返回了,再移除disable

这实际上是一个很有意思的问题,并且值得我们去开发一个通用的“好方法”来解决这类问题。
可以参照节流函数的写法,写一个“异步节流”,在上一个任务尚未结束前,对下一任务的请求直接拒绝:

const asyncThrottle = fn => {
    let lock = false;
    return function(...args){
        return new Promise((resolve, reject) => {
            if(lock) reject('asyncThrottle: 上一任务进行中');
            lock = true;
            fn.apply(this, args).then(res => {
                lock = false;
                resolve(res);
            }).catch((err) => {
                lock = false;
                reject(err);
            });
        });
    }
}

使用的代价就是需要把涉及的函数的返回值写成 thenable 风格。
如果这都算特例的话,我的建议是从更底层的地方做起。如果你用了 axios ,那么写一个拦截器,为每一个 API 创建一个请求状态字,调用某一个 API 的时候,先检查该 API 的状态,如果是 PENDING ,那就不发起请求。但是这样做是有风险的,因为一个 API 可以被多个地方调用,拦截器可能无法分辨。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题