优雅解决按钮”重复点击“问题

1、对思否的一点吐槽

思否上的许多按钮都没有作重复点击检测的问题,每每致使点击没反应,屡次点击后忽然发表多条相同内容,好比和一位同窗的私信:
image.pngjavascript

又如一个问题下的评论:
image.pngjava

若是你在这篇文章下发表评论,能够不当心多点几下"提交评论"按钮,会发现也存在相同的问题~~~ios


这个问题怎么解决呢?
简单点,使用一个lock标记,在请求发出时上锁,上锁后就不能够再发请求,能够在请求结束后解锁:axios

let clickButton = (function () {
  let lock = false
  return function (postParams) {
    if (lock) return
    lock = true
    // 假设使用axios发送请求
    axios.post('urlxxx', postParams).then(
      // 表单提交成功
    ).catch(error => {
      // 表单提交出错
      console.log(error)
    }).finally(() => {
      // 无论成功失败 都解锁
      lock = false
    })
  }
})()

button.addEventListener('click', clickButton)

固然对于button按钮,可使用setAttribute('disabled', xxx)和removeAttribute('disabled')来代替lock标记。promise

这个方案问题在于,对于每一次按钮点击,咱们都要写个lock标记,至关于重复的逻辑会出如今代码的各个地方——是否是能够封装一下呢?函数

2、封装按钮锁定、解锁逻辑

写一个装饰器将逻辑封装起来:post

function ignoreMultiClick(func, manual = false) {
  let lock = false
  return function (...args) {
    if (lock) return
    lock = true
    let done = () => (lock = false)
    if (manual) return func.call(this, ...args, done)
    let promise = func.call(this, ...args)
    Promise.resolve(promise).finally(done)
    return promise
  }
}

将想监听点击回调函数func做为传递给ignoreMultiClick进行装饰,会返回一个新的函数,使用该函数做为点击的回调事件便可。
这里一样用了一个标记lock来上锁,有两种方法解锁:this

  1. 手动解锁:能够给ignoreMultiClick传递一个参数manual,意思是主动调用解锁。若该参数为truthy,则点击事件触发时会给原始的点击回调func传递一个参数done,done是一个函数,调用它能够解锁。
  2. 自动解锁:可使原监听函数func返回一个promise,在该promise决议后自动执行解锁操做。由于Promise管理回调函数很是方便,而且像axios这样很是经常使用的请求库返回值自己也是一个promise,因此默认状况使用这种方式。固然返回promise并非必须的,有时候咱们在发请求前会进行一些验证,验证没经过则直接return,此时装饰器函数也能正常处理,由于使用Promise.resolve包裹了一下promise: Promise.resolve(promise).finally(done)

3、使用实例

自动解锁使用例子:url

let clickButton = ignoreMultiClick(function (postParams) {
  if (!checkForm()) return // 假设有一些检测表单的操做,检查不经过则直接返回
  // 返回promise
  return axios.post('urlxxx', postParams).then(
    // 表单提交成功
  ).catch(error => {
    // 表单提交出错
    console.log(error)
  })
})
button.addEventListener('click', clickButton)

手动解锁:spa

let clickButton = ignoreMultiClick(function (postParams, done) {
  if (!checkForm()) return done() // 表单验证不经过解锁
  axios.post('urlxxx', postParams).then(
    // 表单提交成功
  ).catch(error => {
    // 表单提交出错
    console.log(error)
  }).finally(() => done()) // 请求结束解锁
})
button.addEventListener('click', clickButton)

普通场景下仍是自动解锁比较简单,由于可能有多个条件分支,手动解锁须要在每个返回的地方都调用done。

相关文章
相关标签/搜索