[React 19] 공식문서 톺아보기 - React DOM API
• Study
createPortal, flushSync, preinit와 같은 리액트 DOM api에 대해서 공부하였습니다.
createPortal
createPortal
을 사용하면 일부 자식을 DOM의 다른 부분으로 렌더링할 수 있습니다.
createPortal(children, domNode, key?)
createPortal(children, domNode, key?)
<div>
<SomeComponent />
{createPortal(children, domNode, key?)}
</div>
<div>
<SomeComponent />
{createPortal(children, domNode, key?)}
</div>
- Portal은 DOM 노드의 물리적 배치만 변경합니다.
- Portal로 렌더링된 JSX는 이를 렌더링하는 React 컴포넌트의 자식 노드처럼 동작합니다.
- 자식은 부모 트리가 제공하는 Context에 접근할 수 있으며, 이벤트는 React 트리를 따라 자식에서 부모로 전파됩니다.
- 물리적 배치가 달라졌으므로 스타일은 부모 컴포넌트의 영향을 받지 않는다.
domNode
에는 요소를 띄울 컨테이너를 넘긴다. 이때 넘기는 노드는 이미 존재해야한다. React 앱 외부에 미리 정의되어 있어야 합니다. JS로 생성하는 것도 가능하다.- React는 렌더링 도중
createPortal
를 발견하면children
을 제공된domNode
안에 배치합니다. - Portal을 사용하면 컴포넌트가 일부 자식을 DOM의 다른 위치로 렌더링할 수 있습니다. 이를 통해 컴포넌트의 일부가 어떤 컨테이너에 있든 그 컨테이너에서 “탈출”할 수 있습니다.
flushSync
flushSync
는 React에 제공된 콜백 내부의 모든 업데이트를 동기적으로 처리하도록 강제합니다. DOM이 즉시 업데이트되는 것을 보장합니다.flushSync
를 사용하는 것은 일반적이지 않고 자주 사용하면 애플리케이션의 성능이 크게 저하될 수 있습니다.- 브라우저 API와 같은 서드 파티 코드와 통합할 때 유용할 수 있습니다. 일부 브라우저 API는 콜백 내부의 결과가 DOM에서 동기적으로 사용될 것으로 예상하므로 콜백이 완료될 때까지 렌더링된 DOM을 사용해서 브라우저가 작업할 수 있습니다.
preconnect
preconnect
를 사용하면 리소스를 가져올 것으로 예상하는 서버에 연결할 수 있습니다.
-
동일한 서버에 대해 여러 번의
preconnect
호출은 한 번의 호출과 동일한 효과를 갖습니다. -
“연결한다” - 일반적인 리소스 요청시 발생하는 과정을 미리 수행한다. 100ms~500ms의 지연시간을 줄일 수 있다.
- DNS 조회: 도메인 이름을 IP 주소로 변환
- TCP 핸드셰이크: 서버와의 연결 설정
- TLS 협상(HTTPS인 경우): 보안 연결 설정
-
한번 연결하면 보통 10초~30초(브라우저마다 다르다) 정도 연결되며 시간내에 리소스 요청이 발생하면 기존 연결을 재사용한다. 지정된 시간 동안 아무 요청이 발생하지 않으면 자동으로 닫힌다.
-
link 태그를 이용해서도 연결할 수 있다.
<link rel="preconnect" href="https://example.com" crossorigin>
<link rel="preconnect" href="https://example.com" crossorigin>
html 파싱단계에서 적용되므로 js를 이용한것보다 적용시점이 더 빨르다.
prefetchDNS
prefetchDNS
는 리소스를 가져올 것으로 예상하는 서버의 IP를 미리 조회할 수 있게 해줍니다.
preconnect
와 비교했을 때,prefetchDNS
는 여러 도메인에 대해 사전 연결을 시도하는 경우, 사전 연결의 오버헤드가 이점을 상쇄할 수 있다는 점에서 더 나을 수 있습니다.- DNS 조회를 미리 수행하여 리소스 요청시 발생하는 지연을 줄인다.
- 주로 외부 도메인 연결할때 유용하다.
// prefetchDNS("https://example.com");
<link rel="prefetchDNS" href="https://cdn.example.com">
// prefetchDNS("https://example.com");
<link rel="prefetchDNS" href="https://cdn.example.com">
preinit
preinit
스타일시트나 외부 스크립트를 바로 가져와서 평가할 수 있습니다.
preinit('https://example.com/script.js', { as: 'script' });
preinit('https://example.com/script.js', { as: 'script' });
preload
는 다운로드만,preinit
는 실행까지 처리.
일반 script 태그
<script src='main.js'></script>
<script src='main.js'></script>
- 다운로드 → 파싱 → 실행의 단계를 필요한 시점에 각각 진행
preinit 적용
<link rel="preinit" href="main.js" as="script">
<link rel="preinit" href="main.js" as="script">
- 즉시 다운로드 → 즉시 파싱/실행 (페이지 로드 초기에 강제 실행) 코드의 위치에서 실행!!
어자피 가장먼저 실행되는 script 태그라면?
- 일반적인 script
<head> <script src="heavy-library.js"></script> <!-- 다운로드 + 파싱/실행이 동기적으로 발생 --> </head>
<head> <script src="heavy-library.js"></script> <!-- 다운로드 + 파싱/실행이 동기적으로 발생 --> </head>
- preinit + script
<head> <link rel="preinit" href="heavy-library.js" as="script"> <!-- 백그라운드에서 미리 처리 --> </head> <body> <script src="heavy-library.js"></script> <!-- 이미 준비됨 → 즉시 실행 --> </body>
<head> <link rel="preinit" href="heavy-library.js" as="script"> <!-- 백그라운드에서 미리 처리 --> </head> <body> <script src="heavy-library.js"></script> <!-- 이미 준비됨 → 즉시 실행 --> </body>
- 그럼에도 가장먼저 백그라운드에서 다운로드, 파싱이 진행되고 script 태그 위치에 실행된다.
- 백그라운드에서 진행되기 때문에 원래 head의 script는 DOM생성과 렌더링이 차단되던것을 방지
- 즉 preinit을 쓰는게 조금더 빠르다.
preinitModule
preinitModule
는 ESM 모듈을 즉시 가져와서 평가할 수 있습니다.
preinitModule('https://example.com/module.js', { as: 'script' });
preinitModule('https://example.com/module.js', { as: 'script' });
- ES Module에 특화된 최적화 기술이다. 의존성 트리 전체를 최적화 한다.
preinit vs preinitModule
preinitModule
선택 시- ESM 기반 프로젝트 (Vite, Rollup, Webpack ESM 모드).
- 깊은 의존성 트리를 가진 모듈 (예: 컴포넌트 라이브러리).
- 코드 스플리팅과 함께 사용할 때.
preinit
선택 시- 일반
<script>
번들 (React, jQuery). - 간단한 스크립트 또는 폴리필.
- 일반
preload
preload
를 사용하면 사용할 스타일시트, 글꼴 또는 외부 스크립트와 같은 리소스를 미리 가져올 수 있습니다.
preload('https://example.com/font.woff2', { as: 'font' });
preload('https://example.com/font.woff2', { as: 'font' });
preload
는 리소스를 최우선으로 다운로드하고 캐시하지만, 실행은 원래 위치(HTML/CSS/JS)에서 따로 처리됩니다.- 폰트 최적화, LCP 이미지 가속, 주요 청크 미리 로드 등에 쓰인다.
preinit vs preload
- preinit
- 파싱, 컴파일까지 이루어지고 실행은 코드에서 진행된다.
- 실행준비완료까지 달성한다.
- 네트워크 우선순위가 높고 cpu에 부하를 줄 수 있다. 따라서 LCP에 영향을 주는 핵심 스크립트에만 사용한다.
<head> <!-- 1. 다운로드 + 파싱/컴파일 완료 --> <link rel="preinit" href="app.js" as="script"> </head> <body> <!-- 2. 즉시 실행 --> <script src="app.js"></script> <!-- 지연 없음 --> </body>
<head> <!-- 1. 다운로드 + 파싱/컴파일 완료 --> <link rel="preinit" href="app.js" as="script"> </head> <body> <!-- 2. 즉시 실행 --> <script src="app.js"></script> <!-- 지연 없음 --> </body>
- preload
- 다운로드만 진행하고 파싱, 컴파일은 진행하지 않는다.
- 리소스 캐싱의 목적을 가진다.
<head> <!-- 1. 다운로드만 미리 수행 --> <link rel="preload" href="app.js" as="script"> </head> <body> <!-- 2. 실행 시 파싱/컴파일 시작 --> <script src="app.js"></script> <!-- 약간의 지연 발생 가능 --> </body>
<head> <!-- 1. 다운로드만 미리 수행 --> <link rel="preload" href="app.js" as="script"> </head> <body> <!-- 2. 실행 시 파싱/컴파일 시작 --> <script src="app.js"></script> <!-- 약간의 지연 발생 가능 --> </body>
preloadModule
preloadModule
을 사용하면 사용할 ESM 모듈을 미리 가져올 수 있습니다.- React 기반 프레임워크는 리소스 로딩을 대신 처리하는 경우가 많으므로 이 API를 직접 호출할 필요가 없을 수도 있습니다.
import { preloadModule } from 'react-dom';
function AppRoot() {
preloadModule('https://example.com/module.js', { as: 'script' });
// ...
}
import { preloadModule } from 'react-dom';
function AppRoot() {
preloadModule('https://example.com/module.js', { as: 'script' });
// ...
}
특징
- 동일한
href
로preloadModule
을 여러 번 호출하는 것은 한 번 호출하는 것과 동일한 효과가 있습니다. - 브라우저에서는 컴포넌트를 렌더링하는 중이거나, Effect, 이벤트 핸들러 등 어떤 상황에서도
preloadModule
을 호출할 수 있습니다. - 서버 측 렌더링이나 서버 컴포넌트를 렌더링할 때,
preloadModule
은 컴포넌트를 렌더링하는 동안 호출하거나, 컴포넌트 렌더링에서 시작된 비동기 컨텍스트 내에서 호출할 때만 효과가 있습니다. 그 외의 호출은 무시됩니다. - 다른 API와의 차이점
preinitModule
: 브라우저에서 모듈을 즉시 실행할때 사용 (단순히 다운로드하는 것 대신)preload
: ESM 모듈이 아닌 스크립트를 로드할때 사용
미리 불러오기
import { preloadModule } from 'react-dom';
function CallToAction() {
const onClick = () => {
preloadModule('https://example.com/module.js', { as: 'script' });
startWizard();
};
return <button onClick={onClick}>Start Wizard</button>;
}
import { preloadModule } from 'react-dom';
function CallToAction() {
const onClick = () => {
preloadModule('https://example.com/module.js', { as: 'script' });
startWizard();
};
return <button onClick={onClick}>Start Wizard</button>;
}
- 모듈이 필요한 페이지나 State를 전환되기 전에 이벤트 핸들러에서
preloadModule
을 호출하세요. 이렇게 하면 새로운 페이지나 State를 렌더링할 때 호출하는 것보다 더 일찍 프로세스를 시작할 수 있습니다.