我正在使用 Reacts 进行 SSR
renderToPipeableStream
并使用 ApolloClient 来获取组件中的数据。为了补充客户端中的服务器端缓存,我需要提取缓存并使用服务器发送的 html 中的内联脚本将其公开在全局窗口对象上。
幸运的是,React 18 在 SSR 期间处理了
Suspense
,我可以简单地暂停渲染暴露缓存的脚本标签,直到所有请求都得到解决。
export const ApolloSSRCache = () => {
Object.values(renderCache).forEach((wrapped) => wrapped());
return (
<script
dangerouslySetInnerHTML={{
__html: `window.__APOLLO_STATE__=${JSON.stringify(
client.extract()
).replace(/</g, '\\u003c')};`,
}}
/>
);
};
请求被包裹在一个悬挂器中,如果未解决,则会抛出承诺。
export function wrapPromise<T>(promise: Promise<T>): () => T {
let status = 'pending';
let response: T;
const suspender = promise.then(
(res) => {
status = 'success';
response = res;
},
(err) => {
status = 'error';
response = err;
}
);
return () => {
switch (status) {
case 'pending':
throw suspender;
case 'error':
throw response;
default:
return response;
}
};
}
这是有效的,在服务器端渲染期间任何组件中发出的所有请求都会重新合并到客户端缓存中,这意味着客户端上的
useQuery
钩子可以使用缓存来立即提供响应。这是必要的,否则您会发出两倍的请求,并且如果在客户端获取数据时显示加载状态,可能会导致闪烁。
目前我必须手动跟踪请求/承诺并等待所有请求/承诺都得到解决。如果 Apollo 已经有一个知道每个请求状态的缓存,那么跟踪 Apollo 发出的所有请求似乎有点麻烦。
它需要与
Suspense
一起使用,因为我使用动态导入的代码分割..
React 目前还没有官方方法可以做到这一点。
如果你想了解更多有关背景和“React 中缺失的功能”的信息,我有一个关于此的会议演讲:https://portal.gitnation.org/contents/the-rocky-journey-of-data-在 Reacts-new-streaming-ssr 中获取库
也就是说,它在使用
@apollo/experimental-nextjs-app-support
包的 Next.js 中运行得很好(需要一些技巧),我们现在正在创建一个新的 @apollo/client-react-streaming
包,该包也允许在其他环境中使用它.
我们可能会在接下来的几周内发布它。
我建议您遵循 此 PR,其中包含 Vite +
renderToReadableStream
(以及其他 0.9.0 PR)的集成测试 - renderToPipeableStream
的使用方式略有不同,您可以看到使用React 已在此集成测试 PR 中通过一些“黑客钩子”进行了扩展。
综上所述:我们不会在流式 SSR 中支持
useQuery
。 Apollo 客户端现在附带支持悬念的钩子,例如 useSuspenseQuery
,因此我们将重点放在这些钩子上。 useQuery
只会渲染一次,因此始终会在服务器上渲染加载状态 - 如果您尝试为此传输数据,最终会在浏览器中出现补水不匹配的情况。