当主线程与其他操作排队时,React 不会在状态更改后重新渲染组件?



代码和更多说明-此处为

我正在开发一个计算量很大的网络应用程序,我想让用户知道计算的进展,这样应用程序就不会显得过时。基本上,我有三条消息要在计算状态下指示——

  • 加载
  • 50%已完成
  • 80%已完成

我在App组件中创建了一个displayMsg状态,该状态作为道具发送到子组件CopydisplayMsg的setter方法setDisplayMsg在不同的计算阶段被调用。然而,我只看到Loading和80%Finished,而跳过了中间阶段,50%Finished。

具体来说,我的App组件具有useEffect,如下所示-

React.useEffect(() => {
setDisplayMsg("50% Finished");
simulateHeavyComputation(3000);
setDisplayMsg("80% Finished");
}, []);

其中

const simulateHeavyComputation = (sleepDuration) => {
var now = new Date().getTime();
while (new Date().getTime() < now + sleepDuration) {
/* do nothing */
}
};

在子组件中,消息50% Finished作为prop成功发送,但从未呈现实际的DOM。

https://raw.githubusercontent.com/wchen408/js_react_status_indicator/main/asset/actual_behavior.gif

然而,我真正期待看到的是

https://raw.githubusercontent.com/wchen408/js_react_status_indicator/main/asset/expected_behavior.gif

我想知道是否有办法将simulateHeavyComputation中完成的工作委托给工作线程,这样主线程就不会被阻止渲染DOM。非常感谢您的阅读!

此部分在应用程序中创建无限循环:while (new Date().getTime() < now + sleepDuration)

我会通过使用async/await和超时Promise来解决这个问题

首先,创建一个sleep函数,该函数将在x毫秒后解析

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

添加一个功能来启动模拟:

const startSimulation = async () => {
await sleep(1000);
// update msg after 1s
setDisplayMsg("50% Finished");
// update msg after 3s
await sleep(3000);
setDisplayMsg("80% Finished");
};

并在useEffect中执行

React.useEffect(() => {
startSimulation();
}, []);

https://codesandbox.io/s/rough-wind-1k6r6?file=/src/App.js:991-1302

我认为您想要实现的是一个实时进度条。为此,您需要某种数据(可能来自服务器(,用作useEffect挂钩的依赖项。

在下面的代码中,我使用computation((和setTimeout模拟这些数据。

const [displayMsg, setDisplayMsg] = React.useState("Loading");
const [progress, setProgress] = React.useState(1);
computation();
function computation() {
const prog = setTimeout(() => {
setProgress(() => progress + 1);
}, 40);

if (progress === 100) {
clearTimeout(prog);
}
}

现在,您可以使用字符串模板返回进度,如下所示。它将显示实际百分比,而不是在每个步骤之间等待50%>80%。因此,用户总是知道应用程序仍在处理:

return (
<div className="App">
<h1>Message</h1>
<h2>`Progress: ${progress}%`</h2>
</div>
);
}

如果你想使用消息系统或其他副作用,比如填充视觉进度条,那么你可以使用类似于你的例子的useEffect挂钩。但是,如果将[]填充为依赖项,则useEffect挂钩将仅在第一次渲染时执行。在我上面的例子中,您可以使用进度useState作为依赖项,并根据状态显示消息。见下文:

React.useEffect(() => {
if (progress <= 50) {
setDisplayMsg(() => "Working up to 50%");
} else if (progress >= 50 && progress <= 80) {
setDisplayMsg(() => "Working up to 80%");
} else if (progress >= 80 && progress <= 99) {
setDisplayMsg(() => "Finishing up");
} else if (progress === 100) {
setDisplayMsg(() => "DONE");
}
}, [progress]);

你可以在这里测试代码

如果你在后端使用socket.io,那么你可以将包分成数字块,并在客户端接收数据,如下所示,同时设置进度:

socket.on("countedData", (count)=> {
setProgress(() => count)
})

我希望这能有所帮助。这是一个进度条包,你可以使用它来获得一个视觉进度条

最新更新