React - 如何正确地渲染一个Promise数组的进度?

3
假设我有一个promise数组(总共12个promise),我想在页面上呈现已解决的promise的进度,例如:1/12、2/12、3/12等。所以我从这个答案中得到了做法:链接
我成功地计算了progressNum或百分比,并可以console.log它们。
问题在于,当我尝试使用setState设置progressNum时,只有在所有promise都得到解决时才会显示12/12。或者随机渲染一些数字,例如4/12,然后是12/12,但我想呈现的是从1/12,2/12,3/13 ... 12/12的内容。 我能够正确地console.log进度不能正确地渲染
我知道setState是异步的,所以我尝试使用react ref来操作元素。但也没有什么运气。
到目前为止,我的代码是:
class App extends Component {
  state = {
   progress: 0,
  };

  handleResize = async () => {
   ...

   // imgFiles is an array 12 File object

   this.allProgress(imgFiles.map(this.resizeImg));

   ...
  };


   allProgress(promArray) {
    let progress = 0;
    promArray.forEach((p) => {
      p.then(()=> {    
        progress += 1;
        console.log(`${progress}/12`);
        this.setState({ progress });
      });
    });
    return Promise.all(promArray);
  }

 // I use Jimp package to resize the img, and then return promise

   resizeImg = imgFile => new Promise((resolve, reject) => {
   const reader = new FileReader();
   reader.onloadend = () => {
     Jimp.read(reader.result)
         .then(async (jimpImg) => {
         const normalImg = await this.resizeMain(jimpImg, true, 
         imgFile.name);
         const largeImg = await this.resizeMain(jimpImg, false, 
         imgFile.name);
        resolve([normalImg, largeImg]);
      })
      .catch(reject);
     };
     reader.readAsArrayBuffer(imgFile);
   });

 render() {
  return (
    <div>
     <p>{this.state.progress}</p>
     <button onClick={this.handleResize} >Resize</button>
    </div> )
 }

我也尝试使用Ref。
class App extends Component {
 state = {
  progress: 0,
 };

 indicator = React.createRef();

 changeProgress = (num) => {
    this.indicator.current.innerText = `${num}/12`;
  };

 ...

 allProgress(promArray) {
  let progress = 0;
  promArray.forEach((p) => {
   p.then(()=> {    
    progress += 1;
    console.log(`${progress}/12`);

    // the only logic that I changed:        

    this.changeProgress(progress);
   });
  });
   return Promise.all(promArray);
 } 

 ...

 render() {
  return (
   <div>
    <p ref={this.indicator} />
    <button onClick={this.handleResize} >Resize</button>
   </div> )
 }
}

你能否持续地重现这个问题,或者最好提供一个能够重现该问题的代码片段?(类似的逻辑在这里运行良好:https://jsfiddle.net/mgduf70k/)非常猜测,CPU使用是否一致,或者可能有太多资源被用于其他地方,导致浏览器无法重新绘制自身? - CertainPerformance
更新:我可以在控制台中输出状态,状态已经相应地改变。但是p标签元素没有渲染。 - plat123456789
你能否提供一个最小可复现代码(MCVE)来重现这个问题?正如下面两个答案的评论所示,目前不清楚实际问题出在哪里。 - CertainPerformance
2个回答

2

当您想要更新进度以确保不尝试使用旧值进行更新时,可以使用setState的回调版本。

示例

class App extends React.Component {
  state = {
    done: 0,
    total: 12,
    data: []
  };

  componentDidMount() {
    Promise.all(
      Array.from({ length: this.state.total }, () => {
        return new Promise(resolve => {
          setTimeout(() => {
            this.setState(
              prevState => ({ done: prevState.done + 1 }),
              () => resolve(Math.random())
            );
          }, Math.random() * 3000);
        });
      })
    ).then(data => {
      this.setState({ data });
    });
  }

  render() {
    const { done, total, data } = this.state;
    return (
      <div>
        <div>
          {done} / {total} done
        </div>
        <div>{data.join(", ")}</div>
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

<div id="root"><div>


1
你能解释一下为什么OP使用的progress++方法不起作用(在视频中表现为批处理行为),或者他的代码如何“用旧值”更新吗?这可能是React中我不熟悉的东西,但正如console.log视频所示,setState({ progress }) 看起来 使用了正确的progress数字,并且它似乎在这里 https://jsfiddle.net/mgduf70k/ 中工作得很好。 - CertainPerformance
@CertainPerformance 嗯,是的,你说得对。我的解释有些误导人,因为它应该是有效的。使用回调版本的 setState 更新从已经在状态中的数据派生出来的状态通常是一个好习惯,因为变异可能会引入微妙的错误。不过我现在不太确定在 OP 的问题中可能发生了什么。 - Tholle

2
问题在于您使用了Promise.all,它只会在所有承诺都被解决时才被调用。因此,所有的then方法将一起被调用。因此,在回调函数中进行多个setState调用将被批处理在一起,这就是为什么您没有看到完整的计数。不要使用Promise.all来解决问题,并让承诺按照它们的自然顺序解决。

class App extends React.Component{
  constructor(props){
    super(props)
    this.state = {progress: 0, total: 12}
  }

  allProgress(promArray) {
    let progress = 0;
    //dont use Promise.all
    promArray.forEach((p) => {
      p.then(()=> {    
        progress += 1;
        console.log(`${progress}/12`);
        this.setState({ progress });
      });
    });
  }

  componentDidMount(){
    const p = []
    for(let i =0;i<12;i++){
      p.push(new Promise((resolve, reject)=>{setTimeout(resolve, Math.random() * 5000)}))
    }
    this.allProgress(p)
  }
  render(){
    return (
      <div>{this.state.progress} / {this.state.total}</div>
    )
  }
}

ReactDOM.render(<App />, document.getElementById("app"))
<div id="app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>


但是allProgress内的setStatePromise.all没有关联吗?(从OP的代码中看来,调用setState.then发生在单个promise解决之后,那么是什么导致了批处理行为?)OP的结果Promise.all没有被任何地方使用。真诚的问题,我对React不是很熟悉。 - CertainPerformance
@CertainPerformance 是的,你说得对,Promise.all 没有被使用。我的推理是错误的。 - Anurag Awasthi
我将标题从Promise.all更改为数组到Promise。 - plat123456789
@plat123456789 移除 Promise.all 会有什么影响吗? - Anurag Awasthi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接