提问者:小点点

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


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

有没有可能,由于React的setState的异步特性,对于一个点击速度非常快的用户来说,在按钮被禁用之前触发两次onClark回调?

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

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>
    )
  }
}

共3个答案

匿名用户

我不确定这是否可能:

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,控制台,输出

它只打印一次点击

匿名用户

我一直在做一些实验,想知道我的解决方案是否能达到目的。我在组件上定义了一个布尔属性,该属性不在状态上,并且在单击按钮时直接切换。代码如下:

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功能的实际调用次数将出现在屏幕上(在state更改后),然后,该按钮在3秒延迟后再次启用。

匿名用户

    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>
    )
  }

这可能有帮助

相关问题