递归`setTimeout`没有以预期的方式参与

时间:2017-06-20 11:43:22

标签: javascript reactjs settimeout mobx

我有一个范围输入字段,其数值在我拖动时会发生变化:

enter image description here

当我一直向右拖动时,输入的最大值会增加。然后,当我释放(onMouseUp或onTouchEnd)时,最大值减小,以便我可以进一步拖动并通过拖动继续增加最大值:

enter image description here

当我一直向左拖动时,输入的最小值减小。然后,当我释放(onMouseUp或onTouchEnd)时,最小值增加,以便我可以进一步拖动并通过拖动继续减少min:

enter image description here

我应该总是有99的范围。例如,如果我将最大值增加到530,则最小值将为431.

问题:

我为setTimeoutmin值更改设置了两个递归max

当用户第一次拖动到任何一侧时,该号码应该慢慢改变。如果他们持有2秒钟,数字应该会更快地增加。相关代码:

// After arbitrary period, increase the rate at which the max value increments
this.fasterChangeStake = setTimeout(() => {
  this.stakeChangeTimeout = this.FAST_STAKE_CHANGE_TIMEOUT;
}, 2000);

这是第一次使用。但随后,它首先锁定了更快的超时:

enter image description here

尽管在拖拽结束时我清除了超时:

clearTimeout(this.increaseStakeLimits);
clearTimeout(this.decreaseStakeLimits);
clearTimeout(this.timer);

为什么第一个(较慢的)超时没有参与?

Codepen:https://codepen.io/alanbuchanan/pen/NgjKMa?editors=0010

JS:

const {observable, action} = mobx
const {observer} = mobxReact
const {Component} = React

@observer
class InputRange extends Component {
  constructor() {
    super();
    this.INITIAL_STAKE_CHANGE_TIMEOUT = 200;
    this.FAST_STAKE_CHANGE_TIMEOUT = 20;
    this.SNAP_PERCENT = 10;
    this.increaseStakeLimits = this.increaseStakeLimits.bind(this);
    this.decreaseStakeLimits = this.decreaseStakeLimits.bind(this);
  }

  @observable min = 0;
  @observable max = 99;
  @observable stakeChangeTimeout = this.INITIAL_STAKE_CHANGE_TIMEOUT;
  @observable isIncreasing = false;
  @observable isDecreasing = false;
  @observable stake = 0;
  @action updateStake = (amount) => {
    if (amount > -1) {
      this.stake = amount
    }
  };

  increaseStakeLimits() {
    const { updateStake } = this;

    this.max = this.max += 1;
    this.min = this.max - 99
    updateStake(this.max);

    // After arbitrary period, increase the rate at which the max value increments
    this.fasterChangeStake = setTimeout(() => {
      this.stakeChangeTimeout = this.FAST_STAKE_CHANGE_TIMEOUT;
    }, 2000);

    // Recursive call, like setInterval
    this.timer = setTimeout(this.increaseStakeLimits, this.stakeChangeTimeout);
    this.isIncreasing = true;
  }

  decreaseStakeLimits() {
    console.warn('this.stake:', this.stake)
    const { stake } = this
    const { updateStake } = this;

    this.min = this.min -= 1;
    this.max = this.min + 99
    updateStake(this.min);

    // After arbitrary period, increase the rate at which the max value increments
    this.fasterChangeStake = setTimeout(() => {
      this.stakeChangeTimeout = this.FAST_STAKE_CHANGE_TIMEOUT;
    }, 2000);

    // Recursive call, like setInterval
    this.timer = setTimeout(this.decreaseStakeLimits, this.stakeChangeTimeout);
    this.isDecreasing = true;

  }

  handleStakeChange = e => {
    clearTimeout(this.increaseStakeLimits);
    clearTimeout(this.decreaseStakeLimits);
    clearTimeout(this.timer);

    const { updateStake } = this;
    const { stake } = this;

    const val = Number(e.target.value)

    // User has scrolled all the way to the right
    if (val >= this.max) {
      console.warn("scrolled to right")
      this.increaseStakeLimits();

    // User has scrolled all the way to the left
    } else if (val <= this.min) {
      console.warn("scrolled to left")
      if (val > -1) {
        this.decreaseStakeLimits();
      }
    } else {
      updateStake(val);
    }
  };

  handleRelease = () => {
    console.warn("RANGE:", this.max - this.min)
    console.warn("released");
    clearTimeout(this.fasterChangeStake);
    clearTimeout(this.timer);
    // Reset the timeout value to the initial one
    this.stakeChangeTimeout = this.INITIAL_STAKE_CHANGE_TIMEOUT;
    this.SNAP_PERCENT = 10
    const snapAmount = this.SNAP_PERCENT
    if (this.isIncreasing) {
      this.max += snapAmount
    }

    if(this.isDecreasing && this.min > 0) {
      this.min -= snapAmount
    }

    this.isIncreasing = false;
    this.isDecreasing = false;
  };

  render() {
    const { stake } = this;

    const style = {
      backgroundSize:
        (stake - this.min) * 100 / (this.max - this.min) + "% 100%"
    };

    return (
      <div className="rangeContainer">
        <div>{this.stake}</div>
        <div className="inputContainer">
          <input
            id="betRangeId"
            type="range"
            min={this.min}
            max={this.max}
            step="1"
            ref={input => {
              this.textInput = input;
            }}
            value={this.stake}
            onChange={this.handleStakeChange}
            onTouchEnd={this.handleRelease}
            onMouseUp={this.handleRelease}
            style={style}
          />
        </div>
      </div>
    );
  }
}

ReactDOM.render(<InputRange />, document.getElementById('root'))

1 个答案:

答案 0 :(得分:2)

这是一个非常聪明的用户界面!

increaseStakeLimits()在用户将滑块保持在最右侧的整个时间内持续闪光,因此您需要不断设置新的setTimeouts,以便在两秒后将this.stakeChangeTimeout更改为较短的间隔(并且持续不断)为this.fasterChangeStake设置新值。即使在handleRelease()尝试将间隔重置为较长的​​值之后,这些仍继续触发,这会强制它重新进入快速模式,直到所有这些都被触发(clearTimeout中的handleRelease()仅捕获一个他们。)

您可以通过仅设置一次超时来解决此问题:

if (!this.fasterChangeStake) {
  this.fasterChangeStake = setTimeout(() => {
    this.stakeChangeTimeout = this.FAST_STAKE_CHANGE_TIMEOUT;
    this.fasterChangeStake = false; // <-- also do this in handleRelease() after clearing the timeout
  }, 2000);
}

https://codepen.io/anon/pen/OgmMNq?editors=0010