我正在 React 中执行繁重的计算,并且我 (1) 不希望 UI 阻塞,并且 (2) 希望显示有关进程的信息。我认为
useEffect
钩子是解决此问题的正确方法,但我没有得到正确的结果。
我期望发生的是“正在加载”部分将显示,百分比将从 0 逐渐运行到 100,完成后显示“已完成”。实际发生的情况是从 0 开始,然后在整个计数过程中什么都没有,然后不知从何跳到“已完成”。
执行此操作的正确方法是什么,既可以完成繁重的工作,又可以正确显示 UI 而不会阻塞任何内容?
import { useEffect, useRef, useState } from "react";
export default function App() {
const [isLoading, setIsLoading] = useState<boolean>(true);
const [loadingPercentage, setLoadingPercentage] = useState<number>(0);
const loadingRef = useRef<any>();
useEffect(() => {
if (!loadingRef.current) {
return;
}
const MAX_COUNT = 1000000000;
let threshold = 0;
for (let count = 0; count < MAX_COUNT; ++count) {
///
/// Simulate some heavy-lifting process with process counter
///
if (count > threshold) {
const percentage = (count / MAX_COUNT) * 100;
setLoadingPercentage(percentage);
// console.log(percentage); <-- This just demonstrates that the % set actually gets hit
threshold += MAX_COUNT / 100;
}
}
setLoadingPercentage(100);
setIsLoading(false);
}, [loadingRef.current]);
return (
<div className="App">
<h1>Counting a whole lot of numbers</h1>
{isLoading ? (
<div>
<h2 ref={loadingRef}>{"Loading... " + loadingPercentage + "%"}</h2>
</div>
) : (
<h2>Finished counting!</h2>
)}
</div>
);
}
以下内容不一定是“好”答案,但就本问题而言,它可能是“最不坏”的答案。 正如 @matt-morgan 指出的,useEffect 中的循环阻止了 UI 线程上发生的任何事情,这就是中间阶段不显示的原因。正如 @David 所说,“正确”的方法可能是使用 Web Workers 来实现此目的。 更简单但不完美的解决方案可能是使用异步函数,并在某些阶段构建一些延迟,以便为 UI 线程提供一些空间来完成它的工作。这并不像后台线程那样完全解锁 UI,但至少它确实为 UI 提供了一些空间,因此它实际上会完成所需的 UI 更新。
import { useEffect, useRef, useState } from "react";
export default function App() {
const [isLoading, setIsLoading] = useState<boolean>(true);
const [loadingPercentage, setLoadingPercentage] = useState<number>(0);
useEffect(() => {
function delay(delayMs: number) {
return new Promise((res) => setTimeout(res, delayMs));
}
const doHeavyLiftingAsync = async () => {
const MAX_COUNT = 1000000000;
let threshold = 0;
for (let count = 0; count < MAX_COUNT; ++count) {
if (count > threshold) {
const percentage = (count / MAX_COUNT) * 100;
setLoadingPercentage(percentage);
console.log(percentage);
threshold += MAX_COUNT / 100;
await delay(10);
}
}
setLoadingPercentage(100);
setIsLoading(false);
};
doHeavyLiftingAsync();
}, []);
return (
<div className="App">
<h1>Counting a whole lot of numbers</h1>
{isLoading ? (
<div>
<h2>{"Loading... " + loadingPercentage + "%"}</h2>
</div>
) : (
<h2>Finished counting!</h2>
)}
</div>
);
}
import React from 'react';
export function App(props) {
const [isLoading, setIsLoading] = React.useState(true);
const [progressPercentage, setProgressPercentage] = React.useState(0);
const step = 1
const interval = 10
const maxProgress = 100
React.useEffect(() => {
const updateProgress = () => setProgressPercentage(progressPercentage + step)
if (progressPercentage < maxProgress) {
setTimeout(updateProgress, interval)
} else {
setIsLoading(false);
}
},[progressPercentage]);
return (
<div className='App'>
<h2>Example of a progress bar while loading content</h2>
{isLoading ? (
<div style={{borderColor:'red',borderWidth:2,borderStyle:'solid',backgrounColor:'black',padding:10,margin:50}} onClick={() => setProgressPercentage(0)}>
<div
style={{ backgroundColor:'white', color:'cyan', padding:10,width: progressPercentage + "%" }}
/>
<p>{props.name}</p>
</div>
) : (
<h2>Finished loading!</h2>
)}
</div>
);
}