hydrateRoot
은 이전에 react-dom/server
에 의해 생성된 HTML 컨텐츠를 가지고 있는 브라우저 DOM node 내부에 리액트 컴포넌트들을 보여질 수 있도록 합니다.
const root = hydrateRoot(domNode, reactNode, options?)
hydrateRoot(domNode, reactNode, options?)
서버 환경에서 이미 렌더링 된 존재하고 있는 HTML을 리액트에 "부착"하기 위해서 hydrateRoot을 호출하세요.
import { hydrateRoot } from 'react-dom/client';
const domNode = document.getElementById('root');
const root = hydrateRoot(domNode, reactNode);
리액트는 domNode
안에 존재하는 HTML에 부착하고, DOM 관리를 인계합니다.
리액트로 만들어진 앱은 root 컴포넌트와 함께 보통 하나의 hydrateRoot
을 가지고 있습니다.
domNode
: 서버에서 루트 요소로서 렌더링 된 DOM 요소.reactNode
: 기존에 존재하는 HTML을 렌더링 하기 위해 사용되는 "리액트 노드". 이것은 일반적으로 renderToPipeableStream(<App />)
같은 ReactDOM Server
메서드로 렌더링 된 App 컴포넌트 같은 JSX 조각들 입니다.options
: 이 리액트 root을 위한 옵션들을 가진 객체입니다.onRecoverableError
: 에러들을 리액트가 자동으로 해결하려 할때 호출 되는 콜백 함수.identifierPrefix
: 리액트가 useId
를 사용해서 만들기 위한 ID에 쓰이는 문자열 접두사. 다수의 같은 페이지의 root들을 사용할때 충돌을 피하기 위해 사용됩니다. 서버에서 사용되는 접두사와 같아야합니다. hydrateRoot
은 render
와 unmount
라는 두가지의 메서드 객체를 반환합니다.
hydrateRoot()
은 렌더링 된 컨텐츠가 서버에서 렌더링 된 컨텐츠들과 동일할 것이라고 예상합니다. 만약 불일치 하다면 버그로 취급하고 수정해야합니다.hydrateRoot
을 앱 안에서 호출할 것입니다. 만약에 프레임워크를 사용한다면, 자동으로 불려집니다.hydrateRoot
은 지원되지 않습니다. 대신 createRoot()
을 사용하세요. 브라우저 돔요소를 위한 hydrate 된 리액트 루트 안에 있는 리액트 컴포넌트를 업데이트 하기 위해 root.render
를 호출합니다
root.render(<App />);
리액트는 hydrate 된 root 안에 있는 App 컴포넌트를 업데이트 할 것입니다.
reatNode
: "React node"는 당신이 업데이트 하고 싶은 노드입니다. 이는 보통 App 컴포넌트 같은 JSX 조각이지만, 당신은 createElement()
로 만들어진 문자열이나 숫자, null이나 undefined 같은 리액트 요소들도 전달할 수 있습니다. root.render
는 undefined
를 반환합니다.
root.render
를 호출한다면, 리액트는 이미 존재하고 있는 서버 사이드 렌더링된 HTML을 삭제하고 전체 요소들을 클라이언트 렌더링으로 전환합니다. 리액트 루트 안에 렌더링 된 트리를 제거하기 위해서 root.unmount
를 호출하세요.
root.unmount();
리액트로 만들어진 앱은 보통 root.unmount
를 호출하지 않습니다.
이것은 당신의 리액트 root DOM 노드가 만약ㅇ 다른 코드에 의해서 DOM으로부터 제거 된다 하면 유용합니다.
예를 들어서, DOM으로부터 비활성화 탭들을 제거하는 제이쿼리 탭 패널을 생각해봅시다.
만약에 탭이 제거 된다면, 그 안에 있는 (안에 있는 리액트 root도 포함하여) 내용물들이 DOM으로 부터 제거 될 것입니다.
당신은 root.unmount
를 호출함으로써 제거된 루트의 컨텐츠들을 관리하는 것을 그만하도록 리액트에게 말할 수 있습니다.
그렇지 않으면, 제거된 루트 안에 있는 컴포넌트들은 구독과 같은 리소스들을 정리하지 않고 계속 둘 것입니다.
root.unmount
를 호출하는 것은 트리 안에 있는 상태나 이벤트 핸들러들을 제거하는 것을 포함하여 root DOM 요소로부터 리액트를 분리 시키고 모든 컴포넌트들을 해제합니다.
root.unmount
는 파라미터를 받지 않습니다.
render
는 null
을 반환합니다.
root.unmount
를 호출하는 것은 트리 안에 있는 모든 컴포넌트들을 언마운트 시키고, root DOM 노드로부터 리액트를 분리 시킵니다.
만약 당신이 root.unmount
를 호출한다면, 다시 root.render
를 재호출 할수는 없습니다. 언마운트 된 루트에서 root.render
를 호출하려고 시도하면 “Cannot update an unmounted root” 에러를 띄울 것 입니다.
만약에 당신의 앱의 HTML 이 서버측에서 생성됐다면, 당신은 이것을 클라이언트측에서 hydrate 해야합니다.
import { hydrateRoot } from 'react-dom/client';
hydrateRoot(document.getElementById('root'), <App />);
이것은 당신의 앱 안에 있는 리액트 컴포넌트와 browser DOM node 안에 있는 서버 HTML을 hydrate 할 것입니다.
대개로, 시작할때 한번 수행할것입니다. 만약에 프레임워크를 사용한다면, 이것은 백그라운드 환경에서 실행됩니다.
당신의 앱을 hydrate 하기 위해서, 리액트는 서버로부터 생성된 초기 HTML에 당신의 컴포넌트 로직을 "부착" 합니다.
Hydration 은 서버로부터 온 초기 HTML 스냅샷을 브라우저 안에서 작동되는 상호작용적인 완벽한 앱으로 변화 시킵니다.
<!--
HTML content inside <div id="root">...</div>
was generated from App by react-dom/server.
-->
<div id="root"><h1>Hello, world!</h1><button>You clicked me <!-- -->0<!-- --> times</button></div>
import './styles.css';
import { hydrateRoot } from 'react-dom/client';
import App from './App.js';
hydrateRoot(
document.getElementById('root'),
<App />
);
import { useState } from 'react';
export default function App() {
return (
<>
<h1>Hello, world!</h1>
<Counter />
</>
);
}
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
You clicked me {count} times
</button>
);
}
당신은 더 많은 공간들에서 hydrateRoot을 호출할 필요가 없습니다. 이 시점에서, 리액트는 당신의 어플리케이션의 DOM을 관리합니다. UI를 업데이트 하기 위해서는 당신의 컴포넌트는 state를 사용해야합니다.
hydrateRoot
에 전달하는 리액트 트리는 서버에서 만들어내는 결과물과 같아야합니다.
이것은 사용자 경험에서 상당히 중요합니다.
사용자는 당신의 자바스크립트 코드가 로딩 되기 전에 서버에서 생성된 HTML을 바라보는데에 시간을 사용할 것입니다.
서버 사이드 렌더링은 결과물의 HTML 스냅샷을 보여줌으로써 앱이 실제 로딩되는 속도보다 더 빨리 로딩 되는 것처럼 보이게 합니다.
갑자기 다른 결과물을 보여줌으로써 더 빨리 로딩 되는 것처럼 보이게 하는것을 깨뜨립니다.
이것이 바로 왜 서버사이드 렌더링 된 결과물이 클라이언트 측에서 초기에 렌더링 되는 결과물과 일치해야하는 이유입니다.
이러한 hydration 오류를 일으키는 흔한 원인들은 다음과 같습니다.
typeof window !== "undefined"
같은 식으로 체크하는 경우 window.matchMedia
같은 브라우저에서만 사용되는 API를 렌더링 로직 안에서 사용했을 경우리액트는 몇몇 hydration 에러로부터 해결을 하지만, 하지만 위와 같은 다른 버그들은 꼭 해결해야합니다.
속도가 저하되거나, 최악의 경우에서는 이벤트 핸들러가 다른 요소에 연결 될 수 있습니다.
리액트로 만들어진 앱은 html 태그를 포함해서 JSX 같은 전체 문서를 렌더링 할 수 있습니다.
function App() {
return (
<html>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/styles.css"></link>
<title>My app</title>
</head>
<body>
<Router />
</body>
</html>
);
}
전체 문서를 hydrate를 하기 위해서는, hydrateRoot 의 첫번째 인자로서 document를 전달합니다.
import { hydrateRoot } from 'react-dom/client';
import App from './App.js';
hydrateRoot(document, <App />);
만약 단일 요소 속성이나 문구 컨텐츠가 불가피하게 서버와 클라이언트 사이에서 불일치가 일어난다면,
이 불일치 경고 문구를 끌 수 있습니다.
hydration 경고 문구를 끄고 싶다면, suppressHydrationWarning={true}
로 설정하세요 .
<!--
HTML content inside <div id="root">...</div>
was generated from App by react-dom/server.
-->
<div id="root"><h1>Current Date: <!-- -->01/01/2020</h1></div>
import './styles.css';
import { hydrateRoot } from 'react-dom/client';
import App from './App.js';
hydrateRoot(document.getElementById('root'), <App />);
export default function App() {
return (
<h1 suppressHydrationWarning={true}>
Current Date: {new Date().toLocaleDateString()}
</h1>
);
}
이것은 한단계 더 깊은 단계에서 작동하며, 임시 방편으로 사용됩니다. 남용하지마세요.
텍스트 컨텐츠가 아니라면, 리액트는 패치를 시도하지 않으므로 후에 업데이트까지 일관성이 유지되지 않을 수 있습니다.
만약 당신이 의도적으로 서버와 클라이언트 사이에서 다르게 렌더링 해야할 필요가 있다면, 당신은 두개의 렌더링을 수행할 수 있습니다.
클라이언트 측에서 무언가 다르게 렌더링하는 컴포넌트들은 isClient
같은 상태 변수들을 읽을 수 있으며, Effect 내에서 true로 변화시킬 수 있다.
<!--
HTML content inside <div id="root">...</div>
was generated from App by react-dom/server.
-->
<div id="root"><h1>Is Server</h1></div>
import './styles.css';
import { hydrateRoot } from 'react-dom/client';
import App from './App.js';
hydrateRoot(document.getElementById('root'), <App />);
import { useState, useEffect } from "react";
export default function App() {
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
}, []);
return (
<h1>
{isClient ? 'Is Client' : 'Is Server'}
</h1>
);
}
이 방법은 초기 렌더링 당시에는 서버 컴포넌트와 같은 컨텐츠로 렌더링 해서 불일치를 피한 후에, 추가적으로 hydration 후에 동기적으로 추가적인 상태 변화를 할 수 있습니다.
이 접근법은 렌더링이 2번 일어나기 때문에 hydration이 느리게 일어납니다. 느린 연결에서의 사용자 경험을 항상 상기하세요.
자바스크립트 코드는 초기 HTML 렌더링보다 늦게 로드 될수 있으므로 hydration 후에 다른 UI를 렌더링 하는것은 사용자에게 불편함을 느끼게 할 수 있ㅅㅂ니다.
root 가 hydrating 이 끝나면, 당신은 리액트 루트 컴포넌트를 업데이트 하기 위해 root.render
메서드를 호출할 수 있습니다. createRoot
와 다르게, 이미 초기에 HTML로써 컨텐츠가 생성되었기 때문에 이것을 할필요가 없습니다.
만약 당신이 hydration 후에 어떤 시점에 root.render
를 호출하고, 컴포넌트 트리 구조가 이전에 렌더링 된 요소들과 일치한다면, 리액트는 이 state 를 보존합니다.
어떻게 당신이 인풋안에 값을 입력하는지 알아둬야합니다. 이것은 매초마다 예세어 보여지는 것처럼 render
메서드를 호출해서 업데이트 하는것은 파괴적이지 않습니다.
<!--
All HTML content inside <div id="root">...</div> was
generated by rendering <App /> with react-dom/server.
-->
<div id="root"><h1>Hello, world! <!-- -->0</h1><input placeholder="Type something here"/></div>
import { hydrateRoot } from 'react-dom/client';
import './styles.css';
import App from './App.js';
const root = hydrateRoot(
document.getElementById('root'),
<App counter={0} />
);
let i = 0;
setInterval(() => {
root.render(<App counter={i} />);
i++;
}, 1000);
export default function App({counter}) {
return (
<>
<h1>Hello, world! {counter}</h1>
<input placeholder="Type something here" />
</>
);
}
hydrate 된 root 에서 root.render
를 호출하는 것은 흔하지 않습니다. 대개로, 당신은 대신에 컴포넌트들 안에서 상태를 업데이트 합니다.