我有这个问题的复制品这里。
我正在玩 NextJS 应用程序路由器和 Suspense。
我有两个使用 React Query 获取数据的简单客户端组件的实现。一个使用
useSuspenseQuery
,另一个使用常规查询。
export function TodosRq() {
const query = useQuery<Array<{id: number, title: string}>>({ queryKey: ['todos'], queryFn: async () => {
await new Promise((res) => setTimeout(res, 10000));
const res = await fetch("https://jsonplaceholder.typicode.com/todos")
return res.json();
} })
return <div>
{query.data?.map((v) => {
return <div>
RQ
{v.id} {v.title}
</div>
})}
</div>
}
export function TodosRqSuspense() {
const query = useSuspenseQuery<Array<{id: number, title: string}>>({ queryKey: ['todos'], queryFn: async () => {
await new Promise((res) => setTimeout(res, 10000));
const res = await fetch("https://jsonplaceholder.typicode.com/todos")
return res.json();
} })
return <div>
{query.data.map((v) => {
return <div>
RQ
{v.id} {v.title}
</div>
})}
</div>
}
在我的应用程序路由器页面中,我可以渲染以下任一组件:
{/* nb. this suspense boundary won't do anything */}
<Suspense fallback={'rq loading'}>
<h2>Todos RQ</h2>
<TodosRq/>
</Suspense>
或
<Suspense fallback={'rq loading'}>
<h2>Todos RQ</h2>
<TodosRqSuspense/>
</Suspense>
直观地说,我在这里期望的是服务器渲染将在加载状态下渲染应用程序,将其流式传输到客户端,然后客户端接管并进行 API 调用。
但是,我实际观察到,在使用悬念查询的情况下,实际上 NextJS 将 静态渲染 应用于
TodosRqSuspense
组件。也就是说,在生产版本中,它返回预渲染的 HTML,从不等待 10 秒。
观察开发服务器的行为以及生产构建非常重要。
开发服务器 | 生产构建 | |
---|---|---|
TodosRq | 我们看不到悬念的边界。我们等待 10 秒直到内容出现。内容不会出现在根文档中。 | 我们看不到悬念的边界。我们等待 10 秒直到内容出现。内容不会出现在根文档中。 |
TodosRq悬念 | 我们看到了悬念的边界。我们等待 10 秒直到内容出现。内容出现在根文档中。 (开发服务器似乎做了一些有趣的事情,它可以修改网络请求的响应正文) | 我们立即获取内容。内容在根文档中。 |
我在这里缺少什么?
以下是文档的相关部分以及我的理解:
静态与动态渲染 - 对于服务器组件,默认行为是所有内容都将静态渲染(即在构建时渲染),即使涉及数据获取,除非它适合其中之一例外,例如使用
cookies
或 connection
方法。
客户端组件 - 客户端组件在服务器上预渲染(首先在服务器上渲染),然后当它们到达客户端时,工作就会在客户端上完成。
Suspense - 允许“第一次渲染”显示所有加载骨架等,然后如果使用 RSC,那么它们会流入,而且,这就是我有一些误解的地方,我本以为客户端组件会仍然在客户端获取数据,然后在完成时显示。
NextJS 建议什么 - NextJS 建议您在服务器组件中获取数据,只是为了清楚起见。然而,在某种迁移的背景下,我们可能将某些东西保留为客户端组件是有意义的。无论如何,我试图理解这里的细微差别。
nb。如果我们将
useSearchParams
添加到我们的客户端组件,那么这种行为就不会发生,并且其行为符合我的直觉。