由于setState是异步的,因此按钮onClick可以在被禁用之前触发两次吗?

时间:2018-01-04 09:20:59

标签: javascript reactjs

在下面的示例中,我们有一个启动和上传的按钮,并在上传过程中自行禁用。它在上传完成后重新启用。

由于React的setState的异步特性,对于点击速度非常快的用户,是否有可能在禁用该按钮之前触发两次onClick回调?

请不要回答有关如何避免这种情况的解决方案,我想知道这种情况是否可能在第一时间,如果可能,如何重现它。

import React from 'react';
import ReactDOM from 'react-dom';

class App extends React.Component {
  constructor (props) {
    super(props)

    this.state = {
      uploadDisabled: false
    }
  }


  upload = () => {
    this.setState({
      uploadDisabled: true
    })

    fetch('/upload').then(() => {
      this.setState({
        uploadDisabled: false
      })
    })
  }

  render () {
    return (
      <div>
        <button disabled={this.state.uploadDisabled} onClick={this.upload}>
          Upload
        </button>
      </div>
    )
  }
}

6 个答案:

答案 0 :(得分:1)

我不确定是否可能:

class Button extends React.Component {
  constructor(){
    super();
    this.state = {disabled: false};
    this.onClick = () => {
      this.setState({
        disabled: true
      });
      console.log('click')

      setTimeout(() => {
        this.setState({
          disabled: false
        });
      }, 1000)
    };
  }

  render() {
    return <button onClick={this.onClick} disabled={this.state.disabled}>foo</button>;
  }
}

ReactDOM.render(
  <Button/>,
  document.getElementById('container')
);

setTimeout(() => {
  const button = document.getElementsByTagName('button')[0]
  for(let i = 0; i < 2; i++) {
    button.click()
  }
}, 200)

https://jsbin.com/tamifaruqu/edit?html,js,console,output

只打印一次click

答案 1 :(得分:1)

我一直在尝试,并想知道我的解决方案是否可以解决问题。我在不在状态上的组件上定义了一个布尔属性  并在按钮点击时直接切换。这是代码:

class App extends React.Component {
  // not on the state, will be toggled directly when the user presses the button
  uploadInProgress = false;    
  // just for the purpose of experiment, has nothing to do with the solution
  uploadCalledCount = 0;    
  state = {
     uploadDisabled: false
  }
  upload = () => {
    if (this.uploadInProgress) return;

    this.uploadInProgress = true;
    this.uploadCalledCount++;

    // let's experiment and make setState be called after 1 second
    // to emulate some delay for the purpose of experiment
    setTimeout(() => {
      this.setState({ uploadDisabled: true })
    }, 1000);

    // emulate a long api call, for the purpose of experiment
    setTimeout(() => {
      this.setState(
          { uploadDisabled: false }, 
          () => { this.uploadInProgress = false; })
    }, 3000);
  }

  render() {
    return (
      <div>
        <button disabled={this.state.uploadDisabled} onClick={this.upload}>
          Upload
        </button>
        <br />
        {this.uploadCalledCount}
      </div>)
    }
}

以下是一个工作示例om codesandbox

检查方法:单击该按钮的次数与焦虑和不耐烦的用户一样多次,按钮将在setState呼叫之前设置一秒延迟后被禁用,然后是{的实际呼叫次数{1}}功能将出现在屏幕上(upload更改后),然后再次延迟3秒后按钮启用。

答案 2 :(得分:0)

    import debounce from "lodash/debounce"
//import debounce at the top
    upload = () => {
        this.setState({
          uploadDisabled: true
        })

        fetch('/upload').then(() => {
          this.setState({
            uploadDisabled: false
          })
        })
      }
    onClickUpload = () => {
      _debounce(upload, time) // time is the number of milliseconds to delay.
    }
   render () {
    return (
      <div>
        <button disabled={this.state.uploadDisabled} onClick={this.onClickUpload}>
          Upload
        </button>
      </div>
    )
  }

这可能会有所帮助

答案 3 :(得分:0)

最简单的方法是lodash使用_.debounce(func, [wait=0], [options={}]) 此功能忽略了等待&#39;等事件的事件触发器。一段时间

答案 4 :(得分:0)

这只是一个想法,但也许你可以使用onMouseDown和onMouseUp而不仅仅是onClick。 React的事件系统也提供了一个onDoubleClick事件,所以也许你可以尝试拦截它。

您可以在此处找到有关React事件的更多信息:https://reactjs.org/docs/events.html#mouse-events

答案 5 :(得分:0)

您可以为按钮创建参考,并使用其参考启用或禁用它。

<button ref="btn" onClick={this.onClick}>Send</button>

在onclick中,你可以禁用它

this.refs.btn.setAttribute("disabled", "disabled");

要重新启用,请执行

this.refs.btn.removeAttribute("disabled");

参考link