首頁  >  問答  >  主體

重寫後的標題為:在React Promise.all().then()之前呼叫每個來自promise.then()的setState被渲染

<p>我正在嘗試呼叫200個遠端資源以在我的表格中顯示,同時顯示一個進度條來顯示剩餘的呼叫次數。 </p> <p>使用這個範例來示範如何使用<code>Fetch()</code>和<code>Promise.all()</code>來呼叫<code>setState()</ code>來更新新資料。 </p> <p>我的問題在於每個promise的<code>.then()</code>,它計算一些邏輯,然後呼叫<code>setState()</code>來更新資料。 </p> <p>我的進度列使用<code>Object.keys(data).length</code>來顯示進度。 </p> <p>在<code>Promise.all()</code>觸發「完成」狀態後,移除進度條,promise本身仍在呼叫它們的<code>then()</code> ,這導致進度條在顯示所有已解決的promise之前就被隱藏了。 </p> <p>如何正確處理這個問題? </p> <hr /> <p>演示,使用<code>setTimeout()</code>來模擬昂貴的邏輯。 </p> <p>問題在於<code>Promise.all.then: 20</code>應該在<code>Render 20</code>之後。 </p> <pre class="brush:none;toolbar:false;">Render 0 … Render 12 Promise.all.then: 20 # 我需要這個在每個Render之後記錄 Render 13 … Render 19 Render 20 </pre> <p>為了讓演示顯示出問題,進度條在完全填滿之前就被移除(變成紅色)。</p> <p><br />></p>
const { useState } = React;

const 範例 = () => {

    const [完成,setDone] = useState(false);
    const [數據,setData] = useState({});
      
    const demoData = Array.from(Array(20).keys());
    const demoResolver = (x) =>;新的 Promise(res => setTimeout(() => res(x), Math.random() * 1250))
    
    const loadData = () =>; {
        
        const Promise = demoData.map(c => demoResolver(c));
          
        Promise.forEach(promise => {
            承諾
                .then(r => {
                    setTimeout(() => {
                        setData(p => ({ ...p, [r]: r }));
                    }, 500);
                })
        });
         
        Promise.all(承諾)
            .then(r => {
                console.log('Promise.all.then: ', r.length)
                設定完成(真);
            })
    }
    
    console.log('渲染', Object.keys(data).length);
  
    const ProgressBarIsShownDebugColor =(完成)
      ? '是危險'
      : '是信息';
    
    返回 (
        <節類別名稱='節'>
            

{'範例'}

; <進展 最大值={demoData.length} value={Object.keys(data).length} className={'progress my-3'progressBarIsShownDebugColor} >> </節> ) } ReactDOM.render(<範例/>, document.getElementById("react"));
;
.as-console-wrapper { max-height: 50px !important; }</pre>
<pre class="brush:html;toolbar:false;"><script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min .js"></腳本>
<腳本 src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<連結 rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
<div id="react"></div></pre>
<p><br />></p>
P粉162773626P粉162773626427 天前375

全部回覆(1)我來回復

  • P粉426780515

    P粉4267805152023-08-21 00:02:16

    上面程式碼中顯示的問題是,在取得資料後,在設定狀態之前有額外的500ms非同步延遲。在實際程式碼中,聽起來有額外的處理(可能是同步的)導致setData.all之後被呼叫。

    最好的做法是將done作為一個計算屬性而不是一個單獨的狀態,因為在那個點上,您不需要依賴狀態設定競爭,並且Object.keys( data).length足夠便宜,不會降低效能(而且您在其他區域使用它,如果它成為一個問題,您可以將其緩存到一個變數中)。

    const [data, setData] = useState({});
    const done = Object.keys(data).length === 20; // 在实际代码中为200
    

    const { useState } = React;
    
    const Example = () => {
    
        const [data, setData] = useState({});
        const done = Object.keys(data).length === 20; // 在实际代码中为200
          
        const demoData = Array.from(Array(20).keys());
        const demoResolver = (x) => new Promise(res => setTimeout(() => res(x), Math.random() * 1250))
        
        const loadData = () => {
            
            const promises = demoData.map(c => demoResolver(c));
              
            promises.forEach(promise => {
                promise
                    .then(r => {
                        setTimeout(() => {
                            setData(p => ({ ...p, [r]: r }));
                        }, 500);
                    })
            });
        }
        
        console.log('Render', Object.keys(data).length);
      
        const progressBarIsShownDebugColor = (done)
          ? 'is-danger'
          : 'is-info';
        
        return (
            <section className='section'>
                <h1 className='title is-3'>{'Example'}</h1>
                <progress 
                    max={demoData.length}
                    value={Object.keys(data).length} 
                    className={'progress my-3 ' + progressBarIsShownDebugColor}
                />
                <button onClick={loadData}>Start</button>
            </section>
        )
    }
    ReactDOM.render(<Example />, document.getElementById("react"));
    .as-console-wrapper { max-height: 50px !important; }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
    <div id="react"></div>

    回覆
    0
  • 取消回覆