簡體   English   中英

事件偵聽器中的多個 state 更改,如何不批處理 DOM 更新?

[英]Multiple state changes in event listener, how to NOT batch the DOM updates?

我正在構建一個組件來測試不同算法的性能。 算法返回它們運行所需的毫秒數,這就是我想要顯示的。 “fastAlgorithm”大約需要半秒,“slowAlgorithm”大約需要 5 秒。

我的問題是,在兩種算法都完成之前,UI 不會使用結果重新呈現。 我想在完成后立即顯示快速算法的結果,並在完成時顯示慢速算法的結果。

我讀過 React 如何在重新渲染之前批量更新,但是有什么辦法可以改變這種行為嗎? 或者有沒有更好的方法來組織我的組件來實現我想要的?

我正在使用反應 16.13.1

這是我的組件:

import { useState } from 'react'
import { fastAlgorithm, slowAlgorithm } from '../utils/algorithms'

const PerformanceTest = () => {

  const [slowResult, setSlowResult] = useState(false)
  const [fastResult, setFastResult] = useState(false)

  const testPerformance = async () => {
    fastAlgorithm().then(result => {
      setFastResult(result)
    })
    slowAlgorithm().then(result => {
      setSlowResult(result)
    })
  }

  return (
    <div>
      <button onClick={testPerformance}>Run test!</button>
      <div>{fastResult}</div>
      <div>{slowResult}</div>
    </div>
  )
}

export default PerformanceTest

我在某處讀到 ReactDOM.flushSync() 會在每次 state 更改時觸發重新渲染,但它沒有任何區別。 這是我試過的:

const testPerformance = async () => {
  ReactDOM.flushSync(() =>
    fastAlgorithm().then(result => {
      setFastResult(result)
    })
  )
  ReactDOM.flushSync(() => 
    slowAlgorithm().then(result => {
      setSlowResult(result)
    })
  )
}

還有這個:

const testPerformance = async () => {
  fastAlgorithm().then(result => {
    ReactDOM.flushSync(() =>
      setFastResult(result)
    )
  })
  slowAlgorithm().then(result => {
    ReactDOM.flushSync(() =>
      setSlowResult(result)
    )
  })
}

我還嘗試重組算法,使它們不使用 Promises 並嘗試了這個,但沒有成功:

 const testPerformance = () => {
   setFastResult(fastAlgorithm())
   setSlowResult(slowAlgorithm())
 }

編輯

正如Sujoy Saha在下面的評論中所建議的那樣,我使用 setTimeout() 將我的算法替換為簡單的算法,並且一切都按預期進行。 首先顯示“Fast”,兩秒后顯示“Slow”。

但是,如果我執行類似下面的代碼的操作,它就不起作用。 當較慢的 function 完成時,“快速”和“慢速”都會出現......有誰知道 React 中的批處理渲染何時/如何發生,以及如何避免它?

export const slowAlgorithm  = () => {
  return new Promise((resolve, reject) => {
    const array = []
    for(let i = 0; i < 9000; i++) {
      for(let y = 0; y < 9000; y++) {
        array.push(y);
      }
    }
    resolve('slow')
  })
}

您的初始PerfomanceTest組件是正確的。 該組件將為每個 state 更改重新呈現。 我認為問題出在您的算法中。 請告訴我們您是如何返回 promise 的。 按照下面的代碼片段供您參考。

export const fastAlgorithm  = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('fast')
    }, 1000)
  })
}

export const slowAlgorithm  = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('slow')
    }, 3000)
  })
}

您是否在主線程上同步運行您的算法? 如果是這樣,那可能就是阻止 React 重新渲染的原因。 您可能需要將它們移動到工作線程

以下大致基於此答案,減去所有兼容性內容(假設您不需要 IE 支持):

 // `args` must contain all dependencies for the function. const asyncify = (fn) => { return (...args) => { const workerStr = `const fn = ${fn.toString()} self.onmessage = ({ data: args }) => { self.postMessage(fn(...args)) }` const blob = new Blob([workerStr], { type: 'application/javascript' }) const worker = new Worker(URL.createObjectURL(blob)) let abort = () => {} const promise = new Promise((resolve, reject) => { worker.onmessage = (result) => { resolve(result.data) worker.terminate() } worker.onerror = (err) => { reject(err) worker.terminate() } // In case we need it for cleanup later. // Provide either a default value to resolve to // or an Error object to throw abort = (value) => { if (value instanceof Error) reject(value) else resolve(value) worker.terminate() } }) worker.postMessage(args) return Object.assign(promise, { abort }) } } const multiplySlowly = (x, y) => { const start = Date.now() const arr = [...new Array(x)].fill([...new Array(y)]) return { x, y, result: arr.flat().length, timeElapsed: Date.now() - start, } } const multiplySlowlyAsync = asyncify(multiplySlowly) // rendering not blocked - just pretend this is React const render = (x) => document.write(`<pre>${JSON.stringify(x, null, 4)}</pre>`) multiplySlowlyAsync(999, 9999).then(render) multiplySlowlyAsync(15, 25).then(render)

請注意,這里的fn是在工作線程的上下文中有效地被eval ed,因此您需要確保代碼是可信的。 大概是這樣,因為您已經樂於在主線程上運行它。

為了完整起見,這里有一個 TypeScript 版本:

type AbortFn<T> = (value: T | Error) => void

export type AbortablePromise<T> = Promise<T> & {
    abort: AbortFn<T>
}

// `args` must contain all dependencies for the function.
export const asyncify = <T extends (...args: any[]) => any>(fn: T) => {
    return (...args: Parameters<T>) => {
        const workerStr =
            `const fn = ${fn.toString()}

            self.onmessage = ({ data: args }) => {
                self.postMessage(fn(...args))
            }`

        const blob = new Blob([workerStr], { type: 'application/javascript' })

        const worker = new Worker(URL.createObjectURL(blob))

        let abort = (() => {}) as AbortFn<ReturnType<T>>

        const promise = new Promise<ReturnType<T>>((resolve, reject) => {
            worker.onmessage = (result) => {
                resolve(result.data)
                worker.terminate()
            }

            worker.onerror = (err) => {
                reject(err)
                worker.terminate()
            }

            // In case we need it for cleanup later.
            // Provide either a default value to resolve to
            // or an Error object to throw
            abort = (value: ReturnType<T> | Error) => {
                if (value instanceof Error) reject(value)
                else resolve(value)

                worker.terminate()
            }
        })

        worker.postMessage(args)

        return Object.assign(promise, { abort }) as AbortablePromise<
            ReturnType<T>
        >
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM