如何在React中长时间运行的过程中向用户显示进度

时间:2018-10-23 18:00:10

标签: reactjs

我正在构建一个应用程序,其中用户提供文件和一些参数以执行长时间运行的任务。我有所有的工作。无法正常工作的是向用户显示当前的处理进度。我设置了a simple CodePen来说明。

在Pen中,我有一个按钮可以在while循环中运行任务。如果我正在看控制台,那么在逐步浏览过程中,可以看到打印的进度。但是,状态直到循环完成才更新,因此在UI中,进度从0跳到5而不显示中间值。在这里,我使用睡眠功能模拟任务,实际上我没有在应用程序中使用睡眠功能。

我已经做过一些研究,我知道这与setState异步以及React批处理更新一起使用,以提高呈现UI的效率有关。

话虽如此,我想知道向用户显示进度的最佳方法是什么。使用React的状态是行不通的,我已经尝试直接写入DOM了,但是那是行不通的(而且这样做似乎不是一种干净的方法)。我是否需要使用一些其他库来执行此操作,或者我缺少什么?我当时正在考虑将其移到一个单独的过程中,然后将进度传达回应用程序,但是我是否会遇到未更新UI的同一问题?

也可能很重要,我正在使用while循环,因为我正在使用生成器,所以我知道我不会收到太多进度更新,因为yield将每个百分比从0扩展到100。我也很容易如果更好,请删除生成器/产量部分。

我的代码在CodePen中以及以下版本中:

--- HTML ---

<div id="app"></app>

--- JSX ---

class Application extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      progress: 0
    };

    this.doTask = this.doTask.bind(this);
    this.sleep = this.sleep.bind(this);
  }

  sleep(milliseconds) {
    var start = new Date().getTime();
    for (var i = 0; i < 1e7; i++) {
      if ((new Date().getTime() - start) > milliseconds){
        break;
      }
    }
  }

  doTask() {
    let count = 0;
    while(count<5) {
      count++;
      console.log(count);
      this.setState({
        progress: count
      });
      this.sleep(500);
    }
  }

  render() {
    return <div>
      <button onClick={this.doTask}>Do Task</button>
      <div>Progress: {this.state.progress}</div>
    </div>;
  }
}

/*
 * Render the above component into the div#app
 */
React.render(<Application />, document.getElementById('app'));

3 个答案:

答案 0 :(得分:0)

这是开发中经常发生的问题,所以我希望这个问题和解决方案可以帮助您:Calling setState in a loop only updates state 1 time

我还将看一下https://jsbin.com/kiyaco/edit?js,output,其中使用了this.setState的替代形式。它本质上传入了可以访问当前状态的函数。

答案 1 :(得分:0)

我最终要做的是创建一个基本上充当服务器的后台进程(又名隐藏窗口,因为我正在使用电子)。我长时间运行的进程的信息通过websocket发送到后台进程,进度信息也通过websocket发送回我的主要组件。在我看来,从循环更新状态看起来更直观,但是在后台运行代码不会冻结UI,现在我可以实现所需的行为。

答案 2 :(得分:0)

我有同样的问题,并使用了解决方法。由于某些原因,在使用setTimeout()时,页面会正确进行setState()处理。它使我想起了JavaFX中的runLater

这是我使用的解决方法:

// a wrapper function for setState which uses `Promise` and calls `setTimeout()`
setStatePromise = (state) => new Promise(resolve => {
    this.setState(state, () => {
        setTimeout(() => resolve(), 1);
    });
});

现在您可以改为调用此包装器。但是您需要使用异步功能:

  async doTask() {
    let count = 0;
    while(count<5) {
      count++;
      console.log(count);
      await this.setStatePromise({
        progress: count
      });
      this.sleep(500);
    }
  }