当我在多个 Suspense 边界内使用 use 钩子时,我在 React 19 中遇到了一个问题,我有两个组件 和 ,它们的行为应该相似,但是当渲染包裹在 Suspense 中的项目时,它们表现出不同的渲染行为
在下面的代码中,我有两个组件,并且渲染项目列表,每个项目都包裹在 Suspense 边界中,这些项目应该根据提供给每个 Suspense 组件的等待时间以不同的时间间隔渲染
import { Suspense, use, useState } from "react";
const f = async (wait) => {
await new Promise((resolve) => setTimeout(resolve, wait));
return new Date();
};
const App = () => {
const [open, setOpen] = useState(false);
return (
<>
<Expected />
<Unexpected />
</>
);
};
const Expected = () => {
const [open, setOpen] = useState(false);
return (
<>
<div>👍</div>
<button onClick={() => setOpen(!open)}>{open ? "close" : "open"}</button>
{open && <List wait={[1000, 2000, 3000, 4000, 5000]} />}
</>
);
};
const Unexpected = () => {
const [open, setOpen] = useState(false);
return (
<>
<div>🤔</div>
<button onClick={() => setOpen(!open)}>{open ? "close" : "open"}</button>
{open && <List wait={[1000, 2000, 3000, 3000, 5000]} />}
</>
);
};
const List = ({ wait }) => {
const [t1] = useState(f(wait[0]));
const [t2] = useState(f(wait[1]));
const [t3] = useState(f(wait[2]));
const [t4] = useState(f(wait[3]));
const [t5] = useState(f(wait[4]));
return (
<>
<Suspense fallback={<div>Loading No.1 ...</div>}>
<Item time={t1} />
</Suspense>
<Suspense fallback={<div>Loading No.2 ...</div>}>
<Item time={t2} />
</Suspense>
<Suspense fallback={<div>Loading No.3 ...</div>}>
<Item time={t3} />
</Suspense>
<Suspense fallback={<div>Loading No.4 & No.5 ...</div>}>
<Item time={t4} />
<Item time={t5} />
</Suspense>
</>
);
};
const Item = ({ time }) => {
const t = use(time);
console.log("render item");
return <div>{t.toISOString()}</div>;
};
export default App;
两个组件 和 的行为方式应相同:项目 No.1 到 No.3 应间隔一秒显示,No.4 和 No.5 应一起显示,在 No.3 之后两秒
两个组件之间的行为不一致,在 中,本应单独显示的第 3 项被延迟,仅在第 4 号和第 5 号之后才显示,这破坏了预期的 Suspense 行为
我期望两个组件以相同的方式渲染它们的项目,每个 Suspense 边界独立处理其各自的项目,具体来说,我期望 1 号到 3 号以一秒的延迟顺序渲染,而 4 号和不管等待时间有多么微小的差别,No.5都会在五秒后一起出现
相反,在组件中,No.3 似乎被延迟了,并且仅在 No.4 和 No.5 之后渲染,即使它位于单独的 Suspense 边界中
这种行为是 React 19 中的错误吗?还是我错过了有关 Suspense 和 use 在这个版本中如何协同工作的内容?如何确保跨不同 Suspense 边界的一致行为?
use 钩子是实验性的,可能是您问题的原因。
以下是更好的方法:
const Item = ({ time }) => {
const [t, setT] = useState(undefined);
useEffect(() => {
time.then((val) => setT(val));
}, []);
return <div>{t && t.toISOString()}</div>;
};