lazy를 컴포넌트 안에서 선언했더니 입력값이 계속 초기화된 이유
문제 발생
무거운 편집기를 지연 로딩으로 바꿨습니다.
function Editor() {
const MarkdownPreview = lazy(() => import("./MarkdownPreview.js")); // 컴포넌트 안
return <Suspense fallback={<Skeleton />}><MarkdownPreview /></Suspense>;
}동작은 했지만 부모가 리렌더될 때마다 미리보기 상태가 초기화되고 로딩 스켈레톤이 다시 나타났습니다.
원인 분석
선언 위치가 컴포넌트의 정체성을 결정합니다. React 문서가 이 실수를 그대로 예시로 들어 경고합니다 — 컴포넌트 안에서 lazy를 선언하면 리렌더마다 모든 상태가 초기화됩니다.
lazy()는 호출할 때마다 새로운 컴포넌트 타입을 만듭니다. 렌더마다 타입이 달라지면 React는 같은 컴포넌트가 갱신된 것이 아니라 다른 컴포넌트로 교체된 것으로 보고, 이전 트리를 버리고 새로 마운트합니다.
문서는 올바른 위치도 명시합니다 — 컴포넌트 바깥(모듈 스코프)에 선언하고, <Suspense> 경계로 감싸 로딩 표시를 띄웁니다. 그리고 로딩 함수의 성질도 적혀 있습니다 — load 함수는 한 번만 호출되며, React가 반환된 Promise와 그 결과를 캐시하므로 두 번 이상 호출되지 않습니다.
해결 방안
- 모듈 스코프로 옮깁니다.
const MarkdownPreview = lazy(() => import("./MarkdownPreview.js"));
function Editor() {
return (
<Suspense fallback={<Skeleton />}>
<MarkdownPreview />
</Suspense>
);
}-
Suspense 경계를 의미 있는 위치에 둡니다. 페이지 전체를 감싸면 작은 조각 하나 때문에 화면 전체가 스켈레톤이 됩니다.
-
지연 로딩이 이득인지 확인합니다. 첫 화면에 항상 필요한 컴포넌트를 분할하면 요청만 하나 늘어납니다. 모달, 편집기, 차트처럼 조건부로 열리는 것이 대상입니다.
-
Next.js에서는
next/dynamic도 있습니다. 문서 설명대로next/dynamic은React.lazy()와 Suspense의 합성이며, 클라이언트 전용 렌더가 필요할 때ssr: false를 줄 수 있습니다 — 다만 그 옵션은 Server Component에서는 지원되지 않습니다. -
로딩 상태를 성의 있게 만듭니다. 실제 레이아웃과 크기가 비슷한 스켈레톤이어야 전환이 튀지 않습니다.
-
실패도 처리합니다. 청크 로딩은 네트워크 문제로 실패할 수 있습니다 — Error Boundary와 재시도 경로를 함께 둡니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.