내가 작업할 소스에 express-session
, cookie
, next.js
가 적용되어 있다. 그래서 리액트를 다루는 기술
SSR 부분을 본다. 그런데, preloadContext
부분이 한번에 이해가 안 간다.
아 그냥 비동기 작업 요청의 결과인 promise 들을 한꺼번에 확인하기 위한 객체구나. 모을 때 컴포넌트 깊이에 구애받지 않기 위해 ContextAPI 의 Provider 를 사용한 거고.
Next.js
쓰면 이런 구현 고민할 필요가 없어지나?
SSR 전 비동기 요청 수신 완료 여부 필요
SSR 은 미리 컴포넌트를 렌더링해서 브라우저에 내려보내주는 것. 렌더링에 필요한 자료는 미리 받아져 있어야 함. 그런데 이 자료는 비동기 요청으로 얻어오게 됨. 그럼 비동기 요청이 다 완료된 후에 컴포넌트 렌더링이 되어야 한다는 전제 필요 발생. 여튼 사전에 자료 를 적재하는 게 필요하고 영어로 하면 preload.
사전적재-SSR-CSR 흐름
출처 : https://www.reactpwa.com/docs/en/feature-ssr.html. 일부 편집 및 추가
아래 등장하는 코드는
리액트를 다루는 기술
깃 저장소 20장 4절 에서 발췌및 수정되었습니다.
PreloadContext.js
import { createContext, useContext } from 'react';
const PreloadContext = createContext(null);
export default PreloadContext;
해당 ContextProvider 에 기본값 넣어줌
index.server.js
const preloadContext = {
done: false, // 완료여부
promises: [] // 적재된 작업들
};
const jsx = (
<PreloadContext.Provider value={preloadContext}>
<Provider store={store}>
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
</Provider>
</PreloadContext.Provider>
);
비동기작업, 여기서는 Saga 인데, Saga는 작업의 결과가 promise 가 아니라서, 강제로 promise 를 반환하도록 설정한다.
index.server.js
// Promise { <pending> } 을 결과로 리턴
const sagaPromise = sagaMiddleware.run(rootSaga).toPromise();
// 참조1
참조1
task.toPromise() 는fork
,middleware.run
,runSaga
를 사용한 실행 결과를 구체화하는 Task 인터페이스중 하나다. 실행 결과를 Promise 로 반환한다.
출처 : https://redux-saga.js.org/docs/api/
사전적재 컨텍스트에 작업을 쌓을 수 있는 함수준비
PreloadContext.js
import { createContext, useContext } from 'react';
export const Preloader = ({ resolve }) => {
const preloadContext = useContext(PreloadContext);
// 클라이언트에서 실행을 막도록 하는 코드
if (!preloadContext) return null;
if (preloadContext.done) return null;
// 사전적재 컨텍스트가 미완료라면
// 주어진 resolve() 반환값으로 then 문을 실행할 수 있는 Promise 객체를 생성해 PreloadContext.promises 에 쌓기
preloadContext.promises.push(Promise.resolve(resolve()));
return null;
};
위에서 만든 컴포넌트로 PreloadContext.promises 에다가 작업완료 Promise 를 적재
UsersContainer.js
const UsersContainer = ({ users, getUsers }) => {
// CSR - 컴포넌트 마운트될 때 호출
// SSR 에서는 useEffect 작동 안함
useEffect(() => {
// PreloadContext 실행으로 자료가
// 이미 있다면 또 요청하지 않음.
if (users) return;
getUsers();
}, [getUsers, users]);
return (
<>
<Users users={users} />
// PreloadContext 완료 아니면 getUsers 라는 비동기 작업을 실행하고 결과를 PreloadContext.promises 에 적재
// 무조건 null 리턴하니 화면에 보이지는 않음
<Preloader resolve={getUsers} />
</>
);
};
렌더링 시도해서 각 컴포넌트에서 비동기 요청 & 적재가 일어나도록 함
index.server.js
// renderToStaticMarkup으로 한번 렌더링합니다.
ReactDOMServer.renderToStaticMarkup(jsx); // 렌더 과정 중 비동기작업(여기서는 saga)이 실행되며 그 결과가 PreloaderContext.promises 에 적재됨
추가 비동기 요청이 일어나지 않게 한 뒤. 적재된 작업의 결과를 확인한다.
index.server.js
store.dispatch(END); // redux-saga 의 END 액션을 발생시키면 saga 들이 모두 종료된다. saga가 종료되면 액션도 모니터링되지 않는다.
try {
// 사가 Promise { <pending> } 이 resolved 되길 기다림
await sagaPromise;
// sagaPromise 외 다른 비동기 요청 promise가 있으면 기다린다.
await Promise.all(preloadContext.promises);
} catch (e) {
return res.staus(500);
}
// preloadContext 완료로 해서,
// CSR API 요청 시 있는 자료 재요청 안하도록 함
preloadContext.done = true;
};