React 18: 서스펜스가 있는 스트리밍 SSR 및 서버에서 데이터 가져오기(방법)
문제
React 18에서 Suspense로 스트리밍 SSR을 사용하여 서버에서 데이터 가져오기를 시도하면 문제에 직면하게 되는데, 바로 수화 불일치입니다. 여기에서 이를 해결하는 방법을 설명합니다(here에서 추출한 솔루션).
해결책
다음은 서버 앱의 코드입니다.
import express from "express";
import { renderToPipeableStream } from "react-dom/server";
import React from "react";
import AppServer from "../src/components/AppServer";
import path from "path";
import { DataProvider, data } from "../src/providers/data";
import { createServerData } from "../src/api/resource";
import { Writable } from "node:stream";
const app = express();
const port = 3000;
app.get("/", (req, res) => {
const stream = new Writable({
write(chunk, _encoding, cb) {
res.write(chunk, cb);
},
final() {
res.write(
`<script>
window.globalCache={comments:[${data.comments.map((c) => `'${c}'`)}]}
</script>`
);
res.end("</body></html>");
},
});
const { pipe } = renderToPipeableStream(
<DataProvider data={createServerData()}>
<AppServer />
</DataProvider>,
{
bootstrapScripts: ["/main.js"],
onShellReady() {
res.write("<html><body>");
pipe(stream);
},
}
);
});
app.use(express.static(path.join(__dirname, "/../dist")));
app.listen(port, () => {
console.log(`app running on port ${port}`);
});
여기서 핵심은 다음과 같습니다.
const stream = new Writable({
write(chunk, _encoding, cb) {
res.write(chunk, cb);
},
final() {
res.write(
`<script>
window.globalCache={comments:[${data.comments.map((c) => `'${c}'`)}]}
</script>`
);
res.end("</body></html>");
},
});
우리는 브라우저의
globalCache
변수를 서버의 데이터로 채우기 위해 스트리밍 끝에 스크립트를 작성하고 있습니다.data
의 출처는 다음과 같습니다.import React, { createContext, useContext } from "react";
export let data;
const DataContext = createContext(null);
export function DataProvider({ children, data }) {
return <DataContext.Provider value={data}>{children}</DataContext.Provider>;
}
export function useData() {
const ctx = useContext(DataContext);
if (ctx) {
data = ctx.read();
} else {
data = window.globalCache;
}
return data;
}
서버에서는
data
를 컨텍스트에서 읽고 브라우저에서는 globalCache
변수에서 읽습니다. 그것이 수화 불일치 문제를 피하는 방법입니다.createServerData
함수를 살펴보겠습니다.export function createServerData() {
let done = false;
let promise = null;
let value
return {
read: ()=> {
if (done) {
return value
}
if (promise) {
throw promise;
}
promise = new Promise((resolve) => {
setTimeout(() => {
done = true;
promise = null;
value={comments:['a','b','c']}
resolve()
}, 6000);
});
throw promise;
}
};
}
6000
ms 내에 해결되는 약속입니다.이제
useData
구성 요소에서 Comments
후크를 사용하는 위치를 살펴보겠습니다.import React from "react";
import { useData } from "../providers/data";
export default function Comments() {
const { comments } = useData();
return (
<ul>
{comments && comments.map((comment, i) => <li key={i}>{comment}</li>)}
</ul>
);
}
서버에서는
data
에서 Context
를 읽고 브라우저에서는 전역 변수 data
에서 globalCache
를 읽습니다. 이는 브라우저에서 컨텍스트가 정의되지 않기 때문입니다. 즉, 브라우저의 경우 App
구성 요소를 DataProvider
로 래핑하지 않기 때문입니다.import React from "react";
import { hydrateRoot } from "react-dom/client";
import App from "./components/App";
hydrateRoot(document.getElementById("root"), <App />);
App
구성 요소는 다음과 같습니다.import React, { Suspense, lazy } from "react";
const Comments = lazy(() => import("./Comments"));
const App = () => (
<>
<Suspense fallback={<div>loading...</div>}>
<Comments />
</Suspense>
</>
);
export default App;
그리고 위에서 (서버에서) 사용된
AppServer
구성 요소는 다음과 같습니다.import React from "react";
import App from "./App";
const AppServer = () => (
<div id="root">
<App />
</div>
);
export default AppServer;
이것으로 Suspense로 스트리밍 SSR을 수행하는 방법과 React 18의 서버에서 데이터 가져오기를 수행하여 수화 불일치 문제를 피하는 방법에 대한 이 예제의 모든 코드를 보았습니다.
Reference
이 문제에 관하여(React 18: 서스펜스가 있는 스트리밍 SSR 및 서버에서 데이터 가져오기(방법)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/roggc/react-18-streaming-ssr-with-suspense-and-data-fetching-on-the-server-how-to-39jh텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)