为了理解 React 的
<Suspense>
组件,我尝试利用 setTimeout
编写一个简单的钩子,以在渲染之前触发一段时间的“挂起”状态。
在我的测试中,我使用以下设置:
import { Suspense, useMemo, useState } from "react";
function useSuspend(ms: number) {
const [isSuspended, setSuspended] = useState(true);
const promise = useMemo(
() =>
new Promise<void>((resolve) => {
setTimeout(() => {
setSuspended(false);
resolve();
}, ms);
}),
[ms]
);
console.table({ isSuspended, ms });
if (isSuspended) {
throw promise;
}
}
function Suspending() {
const [id] = useState(Math.random().toFixed(20));
console.log(id);
useSuspend(2500);
return "Done";
}
export default function Main() {
return (
<Suspense fallback={"Loading..."}>
<Suspending />
</Suspense>
);
}
但是,这会产生一些(对我而言)相当意外的日志打印:
0.91830134558829579206
┌─────────────┬────────┐
│ (index) │ Values │
├─────────────┼────────┤
│ isSuspended │ true │
│ ms │ 2500 │
└─────────────┴────────┘
(2.5s pause)
0.33716150767738661820
┌─────────────┬────────┐
│ (index) │ Values │
├─────────────┼────────┤
│ isSuspended │ true │
│ ms │ 2500 │
└─────────────┴────────┘
(continues infinitely every 2.5s forever)
“完成”文本也永远不会呈现。
这些日志似乎表明
<Suspending />
组件在 useSuspend
钩子完成后不会保留其状态,提示组件呈现“就像新的一样”,这对我来说是违反直觉的。有人可以解释一下这种行为吗?
这些日志似乎表明组件在 useSuspend 钩子完成后不会保留>其状态,提示组件呈现“就像新的一样”,这对我来说是违反直觉的。
是的。 Suspense 的设计是为了当它捕获到一个 Promise 时,它将卸载子树并渲染后备(如果有)。后来,诺言得到了解决,悬念将再次出现在孩子们身上。当孩子们安装时,它们是全新的组件。结果
isSuspended
被设置为其初始值 true
,并且 useMemo
运行并创建一个新的 Promise。然后这个承诺被抛出,并且这个过程重复
为了悬念而做出承诺实际上有点棘手。您通常需要拥有组件外部存在的一些值,您可以同步检查该值以查看异步工作是否已完成。如果没有,则抛出一个可以设置外部值并自行解决的承诺。例如:
let loaded = false;
let promise = null;
function useSuspend(ms: number) {
if (!loaded) {
if (!promise) {
promise = new Promise(resolve => {
setTimeout(resolve, ms);
});
}
throw promise;
}
}
请注意,由于加载的值是全局变量,因此应用程序中的每个组件都在共享它。无论哪个组件首先调用
useSuspend
都会启动超时,所有其他组件都会效仿。如果您希望不同的组件具有不同的超时,则需要创建一些更复杂的值存储来满足您的需求。