| title | hydrateRoot |
|---|
hydrateRoot๋ ์ด์ ์ react-dom/server๋ก ์์ฑ๋ HTML ์ฝํ
์ธ ๋ฅผ ๊ฐ์ง ๋ธ๋ผ์ฐ์ DOM ๋
ธ๋ ์์ React ์ปดํฌ๋ํธ๋ฅผ ํ์ํ ์ ์๊ฒ ํด์ค๋๋ค.
const root = hydrateRoot(domNode, reactNode, options?)hydrateRoot๋ฅผ ํธ์ถํ์ฌ ์ด๋ฏธ ์๋ฒ ํ๊ฒฝ์์ ๋ ๋๋ง๋ ๊ธฐ์กด HTML์ React๋ฅผ "๋ถ์ฌ๋ฃ๊ธฐ" ํฉ๋๋ค.
import { hydrateRoot } from 'react-dom/client';
const domNode = document.getElementById('root');
const root = hydrateRoot(domNode, reactNode);React๋ domNode ๋ด๋ถ์ ์กด์ฌํ๋ HTML์ ์ฐ๊ฒฐ๋์ด, ๊ทธ ๋ด๋ถ์ DOM ๊ด๋ฆฌ๋ฅผ ๋งก๊ฒ ๋ฉ๋๋ค. React๋ก ์์ ํ ๊ตฌ์ถ๋ ์ฑ์ ์ผ๋ฐ์ ์ผ๋ก ๋ฃจํธ ์ปดํฌ๋ํธ์ ํจ๊ป ํ๋์ hydrateRoot ํธ์ถ๋ง ๊ฐ์ง๋๋ค.
์๋ ์์๋ฅผ ์ฐธ๊ณ ํ์ธ์.
-
domNode: ์๋ฒ์์ ๋ฃจํธ ์์Element๋ก ๋ ๋๋ง๋ DOM ์์. -
reactNode: ๊ธฐ์กด HTML์ ๋ ๋๋งํ๊ธฐ ์ํ "React ๋ ธ๋" ์ ๋๋ค. ์ฃผ๋กReactDOM Server์renderToPipeableStream(<App />)์ ๊ฐ์ ๋ฉ์๋๋ก ๋ ๋๋ง๋<App />๊ณผ ๊ฐ์ JSX ์กฐ๊ฐ๋ค์ ๋๋ค. -
optional
options: React ๋ฃจํธ์ ๋ํ ์ต์ ์ ๊ฐ์ง ๊ฐ์ฒด์ ๋๋ค.- optional
onCaughtError: React๊ฐ Error Boundary์์ ์ค๋ฅ๋ฅผ ์ก์์ ๋ ํธ์ถ๋๋ ์ฝ๋ฐฑ์ ๋๋ค. Error Boundary์์ ์ก์error์componentStack์ ํฌํจํ๋errorInfo๊ฐ์ฒด์ ํจ๊ป ํธ์ถ๋ฉ๋๋ค. - optional
onUncaughtError: ์ค๋ฅ๊ฐ Error Boundary์ ์ํด ์กํ์ง ์์์ ๋ ํธ์ถ๋๋ ์ฝ๋ฐฑ์ ๋๋ค. ๋ฐ์ํerror์componentStack์ ํฌํจํ๋errorInfo๊ฐ์ฒด์ ํจ๊ป ํธ์ถ๋ฉ๋๋ค. - optional
onRecoverableError: React๊ฐ ์ค๋ฅ๋ก๋ถํฐ ์๋์ผ๋ก ๋ณต๊ตฌ๋ ๋ ํธ์ถ๋๋ ์ฝ๋ฐฑ์ ๋. React๊ฐ ๋์ง๋error์componentStack์ ํฌํจํ๋errorInfo๊ฐ์ฒด์ ํจ๊ป ํธ์ถ๋ฉ๋๋ค. ๋ณต๊ตฌ ๊ฐ๋ฅํ ์ค๋ฅ๋ ์๋ณธ ์ค๋ฅ ์์ธ์error.cause๋ก ํฌํจํ ์ ์์ต๋๋ค. - optional
identifierPrefix: React๊ฐuseId์ ์ํด ์์ฑ๋ ID์ ์ฌ์ฉํ๋ ๋ฌธ์์ด ์ ๋์ฌ. ๊ฐ์ ํ์ด์ง์์ ์ฌ๋ฌ๊ฐ์ ๋ฃจํธ๋ฅผ ์ฌ์ฉํ ๋ ์ถฉ๋์ ํผํ๋ ๋ฐ ์ ์ฉํฉ๋๋ค. ์๋ฒ์์ ์ฌ์ฉํ ๊ฐ๊ณผ ๋ฐ๋์ ๋์ผํ ๊ฐ์ด์ด์ผ ํฉ๋๋ค.
- optional
hydrateRoot๋ render์ unmount ๋ ๊ฐ์ง ๋ฉ์๋๋ฅผ ํฌํจํ ๊ฐ์ฒด๋ฅผ ๋ฐํํฉ๋๋ค.
hydrateRoot()๋ ๋ ๋๋ง๋ ์ปจํ ์ธ ๊ฐ ์๋ฒ์์ ๋ ๋๋ง๋ ์ปจํ ์ธ ์ ๋์ผํ ๊ฒ์ ๊ธฐ๋ํฉ๋๋ค. ๋ฐ๋ผ์ ๋ถ์ผ์น ์ฌํญ์ ๋ฒ๊ทธ๋ก ์ทจ๊ธํ๊ณ ์์ ํด์ผ ํฉ๋๋ค.- ๊ฐ๋ฐ ๋ชจ๋์์๋ React๊ฐ Hydration ์ค ๋ถ์ผ์น์ ๋ํด ๊ฒฝ๊ณ ํฉ๋๋ค. ๋ถ์ผ์น๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ ์์ฑ ์ฐจ์ด๊ฐ ์์ ๋ ๊ฒ์ด๋ผ๋ ๋ณด์ฅ์ ์์ต๋๋ค. ์ด๋ ์ฑ๋ฅ์์ ์ด์ ๋ก ์ค์ํ๋ฐ, ๋๋ถ๋ถ์ ์ฑ์์ ๋ถ์ผ์น๋ ๋๋ฌผ๊ธฐ ๋๋ฌธ์ ๋ชจ๋ ๋งํฌ์ ์ ๊ฒ์ฆํ๋ ๊ฒ์ ๋งค์ฐ ๋นํจ์จ์ ์ด๊ธฐ ๋๋ฌธ์ ๋๋ค.
- ์ฑ์์
hydrateRootํธ์ถ์ด ๋จ ํ๋ฒ๋ง ์์ ๊ฐ๋ฅ์ฑ์ด ๋์ต๋๋ค. ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ๋ค๋ฉด, ํ๋ ์์ํฌ๊ฐ ์ด ํธ์ถ์ ๋์ ์ํํ ์๋ ์์ต๋๋ค. - ์ฑ์ ์ฌ์ ์ ๋ ๋๋ง๋ HTML ์์ด ํด๋ผ์ด์ธํธ์์ ์ง์ ๋ ๋๋งํ๋ค๋ฉด,
hydrateRoot()๋ ์ง์๋์ง ์์ต๋๋ค.createRoot()๋ฅผ ๋์ ์ฌ์ฉํด์ฃผ์ธ์.
๋ธ๋ผ์ฐ์ DOM ์์ ๋ด์์ Hydrate๋ React ๋ฃจํธ ์์ React ์ปดํฌ๋ํธ๋ฅผ ์
๋ฐ์ดํธ ํ๋ ค๋ฉด root.render๋ฅผ ํธ์ถํ์ธ์.
root.render(<App />);React๋ Hydrate๋ root์์ <App />์ ์
๋ฐ์ดํธํฉ๋๋ค.
์๋ ์์๋ฅผ ์ฐธ๊ณ ํ์ธ์.
reactNode: ์ ๋ฐ์ดํธํ๊ณ ์ถ์ "React ๋ ธ๋"์ ๋๋ค. ์ฃผ๋ก<App />๊ฐ์ JSX๋ฅผ ๋งค๊ฐ๋ณ์๋ก ๋๊ธฐ์ง๋ง,createElement()๋ก ๋ง๋ React ์์ ํน์ ๋ฌธ์์ด, ์ซ์,null,undefined๋ฅผ ๋๊ฒจ๋ ๋ฉ๋๋ค.
root.render๋ undefined๋ฅผ ๋ฐํํฉ๋๋ค.
- ๋ฃจํธ๊ฐ Hydrate๋ฅผ ์๋ฃํ๊ธฐ ์ ์
root.render๋ฅผ ํธ์ถํ๋ฉด, React๋ ์๋ฒ์์ ๋ ๋๋ง๋ HTML์ ๋ชจ๋ ์์ ๊ณ ํด๋ผ์ด์ธํธ์์ ๋ ๋๋ง๋ ์ปดํฌ๋ํธ๋ค๋ก ์์ ํ ๊ต์ฒดํฉ๋๋ค.
root.unmount๋ฅผ ํธ์ถํ๋ฉด React ๋ฃจํธ ๋ด๋ถ์์ ๋ ๋๋ง๋ ํธ๋ฆฌ๋ฅผ ์ญ์ ํฉ๋๋ค.
root.unmount();์จ์ ํ React๋ง์ผ๋ก ์์ฑ๋ ์ฑ์๋ ์ผ๋ฐ์ ์ผ๋ก root.unmount์ ๋ํ ํธ์ถ์ด ์์ต๋๋ค.
์ด ํจ์๋ ์ฃผ๋ก React ๋ฃจํธ์ DOM ๋
ธ๋(๋๋ ๊ทธ ์กฐ์ ๋
ธ๋)๊ฐ ๋ค๋ฅธ ์ฝ๋์ ์ํด DOM์์ ์ ๊ฑฐ๋ ์ ์๋ ๊ฒฝ์ฐ์ ์ ์ฉํฉ๋๋ค. ์๋ฅผ ๋ค์ด DOM์์ ๋นํ์ฑ ํญ์ ์ ๊ฑฐํ๋ jQuery ํญ ํจ๋์ ์์ํด ๋ณด์ธ์. ํญ์ด ์ ๊ฑฐ๋๋ฉด ๊ทธ ์์ ์๋ ๋ชจ๋ ๊ฒ(๋ด๋ถ์ React ๋ฃจํธ๋ฅผ ํฌํจ)์ด DOM์์ ์ ๊ฑฐ๋ฉ๋๋ค. ์ด ๊ฒฝ์ฐ root.unmount๋ฅผ ํธ์ถํ์ฌ ์ ๊ฑฐ๋ ๋ฃจํธ์ ์ฝํ
์ธ ๊ด๋ฆฌ๋ฅผ "์ค์ง"ํ๋๋ก React์ ์ง์ํด์ผ ํฉ๋๋ค. ๊ทธ๋ ์ง ์์ผ๋ฉด ์ ๊ฑฐ๋ ๋ฃจํธ ๋ด๋ถ์ ์ปดํฌ๋ํธ๋ ๊ตฌ๋
๊ณผ ๊ฐ์ ์ ์ญ ๋ฆฌ์์ค๋ฅผ ์ ๋ฆฌํ๊ณ ํ๋ณดํ๋ ๋ฒ์ ๋ชจ๋ฅด๋ ์ฑ๋ก ์๊ฒ ๋ฉ๋๋ค.
root.unmount๋ฅผ ํธ์ถํ๋ฉด ๋ฃจํธ์ ์๋ ๋ชจ๋ ์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ ํด์ ๋๊ณ , ํธ๋ฆฌ์์ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ State๊ฐ ์ ๊ฑฐ๋๋ฉฐ, ๋ฃจํธ DOM ๋
ธ๋์์ React๊ฐ "๋ถ๋ฆฌ"๋ฉ๋๋ค.
Calling root.unmount will unmount all the components in the root and "detach" React from the root DOM node, including removing any event handlers or state in the tree.
root.unmount๋ ๋งค๊ฐ๋ณ์๋ฅผ ๋ฐ์ง ์์ต๋๋ค.
root.unmount returns undefined.
-
root.unmount๋ฅผ ํธ์ถํ๋ฉด ํธ๋ฆฌ์ ๋ชจ๋ ์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ ํด์ ๋๊ณ ๋ฃจํธ DOM ๋ ธ๋์์ React๊ฐ "๋ถ๋ฆฌ"๋ฉ๋๋ค. -
root.unmount๋ฅผ ํ ๋ฒ ํธ์ถํ ํ์๋ ๊ฐ์ ๋ฃจํธ์์root.render๋ฅผ ๋ค์ ํธ์ถํ ์ ์์ต๋๋ค. ๋ง์ดํธ ํด์ ๋ ๋ฃจํธ์์root.render๋ฅผ ํธ์ถํ๋ ค๊ณ ํ๋ฉด "๋ง์ดํธ ํด์ ๋ ๋ฃจํธ๋ฅผ ์ ๋ฐ์ดํธํ ์ ์์ต๋๋ค.Cannot update an unmounted root" ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค.
react-dom/server๋ก ์ฑ์ HTML์ ์์ฑํ๋ค๋ฉด, ํด๋ผ์ด์ธํธ์์ Hydrate ํด์ฃผ์ด์ผ ํฉ๋๋ค.
import { hydrateRoot } from 'react-dom/client';
hydrateRoot(document.getElementById('root'), <App />);์ ์ฝ๋๋ฅผ ํตํด ์๋ฒ HTML์ ๋ธ๋ผ์ฐ์ DOM ๋ ธ๋์์ React ์ปดํฌ๋ํธ๋ฅผ ์ด์ฉํด Hydrate ํด์ค ๊ฒ ์ ๋๋ค. ์ฃผ๋ก ์ฑ์ ์์ํ ๋ ๋จ ํ ๋ฒ ์คํํ ๊ฒ์ ๋๋ค. ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉ์ค์ด๋ผ๋ฉด ํ๋ ์์ํฌ๊ฐ ๋์ ์คํํด ์ค ๊ฒ์ ๋๋ค.
์ฑ์ Hydrate ํ๊ธฐ ์ํด์ React๋ ์ปดํฌ๋ํธ์ ๋ก์ง์ ์ฌ์ ์ ์๋ฒ์์ ๋ง๋ค์ด ์ง HTML์ "๋ถ์ฌ๋ฃ์"๊ฒ ์ ๋๋ค. Hydration์ ํตํด ์๋ฒ์์ ๋ง๋ค์ด์ง ์ต์ด์ HTML ์ค๋ ์ท์ ๋ธ๋ผ์ฐ์ ์์ ์์ ํ ์ธํฐ๋ํฐ๋ธํ ์ฑ์ผ๋ก ๋ฐ๊ฟ์ฃผ๊ฒ ๋ฉ๋๋ค.
<!--
<div id="root">...</div> ์์ HTML ๋ด์ฉ๋ค์
react-dom/server์ผ๋ก ๋ง๋ค์ด์ง App์
๋๋ค.
-->
<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๋ฅผ ๋ค์ ํธ์ถํ๊ฑฐ๋ ๋ค๋ฅธ ๊ณณ์์ ๋ ํธ์ถํ ํ์๋ ์์ต๋๋ค. ์ด ์์ ๋ถํฐ React๊ฐ ์ ํ๋ฆฌ์ผ์ด์
์ DOM์ ๋ค๋ฃจ๊ฒ ๋ฉ๋๋ค. ๋์ UI๋ฅผ ๊ฐฑ์ ํ๊ธฐ ์ํด์ State๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
hydrateRoot์ ์ ๋ฌํ React ํธ๋ฆฌ๋ ์๋ฒ์์ ๋ง๋ค์๋ React ํธ๋ฆฌ ๊ฒฐ๊ณผ๋ฌผ๊ณผ ๋์ผํด์ผ ํฉ๋๋ค.
์ด๋ ์ฌ์ฉ์ ๊ฒฝํ์ ์ํด์ ์ค์ํฉ๋๋ค. ์ฌ์ฉ์๋ ์๋ฒ์์ ๋ง๋ค์ด์ง HTML์ ์๋ฐ์คํฌ๋ฆฝํธ ์ฝ๋๊ฐ ๋ก๋๋ ๋๊น์ง ๋๋ฌ๋ณด๊ฒ ๋ฉ๋๋ค. ์ฑ์ ๋ก๋ฉ์ ๋ ๋น ๋ฅด๊ฒ ํ๊ธฐ ์ํด ์๋ฒ๋ ์ผ์ข ์ ์ ๊ธฐ๋ฃจ๋ก์ React ๊ฒฐ๊ณผ๋ฌผ์ธ HTML ์ค๋ ์ท์ ๋ง๋ค์ด ๋ณด์ฌ์ค๋๋ค. ๊ฐ์๊ธฐ ๋ค๋ฅธ ์ปจํ ์ธ ๋ฅผ ๋ณด์ฌ์ฃผ๊ฒ ๋๋ฉด ์ ๊ธฐ๋ฃจ๊ฐ ๊นจ์ ธ๋ฒ๋ฆฌ๊ฒ ๋ฉ๋๋ค. ์ด๋ฐ ์ด์ ๋ก ์๋ฒ์์ ๋ ๋๋งํ ๊ฒฐ๊ณผ๋ฌผ๊ณผ ํด๋ผ์ด์ธํธ์์ ์ต์ด๋ก ๋ ๋๋งํ ๊ฒฐ๊ณผ๋ฌผ์ด ๊ฐ์์ผ ํฉ๋๋ค.
์ฃผ๋ก ์๋์ ๊ฐ์ ์์ธ๋ค๋ก Hydration ์ค๋ฅ๊ฐ ์ผ์ด๋ฉ๋๋ค.
- React๋ฅผ ํตํด ๋ง๋ค์ด์ง HTML์ ๋ฃจํธ ๋ ธ๋์์ ๊ณต๋ฐฑ ํน์ ๊ฐํ๊ฐ์ ์ถ๊ฐ์ ์ธ ๊ณต๋ฐฑ.
typeof window !== 'undefined'๊ณผ ๊ฐ์ ์กฐ๊ฑด์ ๋ ๋๋ง ๋ก์ง์์ ์ฌ์ฉ.window.matchMedia๊ฐ์ ๋ธ๋ผ์ฐ์ ์์๋ง ์ฌ์ฉ๊ฐ๋ฅํ API๋ฅผ ๋ ๋๋ง ๋ก์ง์ ์ฌ์ฉ.- ์๋ฒ์ ํด๋ผ์ด์ธํธ์์ ์๋ก ๋ค๋ฅธ ๋ฐ์ดํฐ๋ฅผ ๋ ๋๋ง.
React๋ Hydration ์ค๋ฅ์์ ๋ณต๊ตฌ๋ฉ๋๋ค, ํ์ง๋ง ๋ค๋ฅธ ๋ฒ๊ทธ๋ค๊ณผ ๊ฐ์ด ๋ฐ๋์ ๊ณ ์ณ์ค์ผ ํฉ๋๋ค. ๊ฐ์ฅ ๋์ ๊ฒฝ์ฐ๋ ๊ทธ์ ๋๋ ค์ง๊ธฐ๋ง ํ ๋ฟ์ด์ง๋ง, ์ต์ ์ ๊ฒฝ์ฐ์ ์ด๋ฒคํธ ํธ๋ค๋ฌ๊ฐ ๋ค๋ฅธ ์์Element์ ๋ถ์ด๋ฒ๋ฆฝ๋๋ค.
React๋ก ์ฑ์ ๋ชจ๋ ๋ง๋ค์์ ๊ฒฝ์ฐ <html> ํ๊ทธ๋ฅผ ํฌํจํด JSX๋ก ๋ ์ ์ฒด document๋ฅผ ๋ ๋๋งํ ์ ์์ต๋๋ค.
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>
);
}์ ์ฒด document๋ฅผ Hydrateํ๊ธฐ ์ํด์ ์ ์ญ ๋ณ์์ธ document๋ฅผ hydrateRoot์ ์ฒซ๋ฒ์งธ ์ธ์๋ก ๋๊น๋๋ค.
import { hydrateRoot } from 'react-dom/client';
import App from './App.js';
hydrateRoot(document, <App />);์ด์ฉ ์ ์๋ Hydration ๋ถ์ผ์น ์ค๋ฅ ์ต์ ํ๊ธฐ {/suppressing-unavoidable-hydration-mismatch-errors/}
์ด๋ค ์์Element์ ์์ฑ์ด๋ ํ ์คํธ ์ปจํ ์ธ ๊ฐ ์๋ฒ์ ํด๋ผ์ด์ธํธ์์ ์ด์ฉ ์ ์์ด ๋ค๋ฅผ ๋(์๋ฅผ ๋ค์ด, timestamp๋ฅผ ์ด์ฉํ๋ค๊ฑฐ๋), Hydration ๋ถ์ผ์น ๊ฒฝ๊ณ ๋ฅผ ์๋ณด์ด๊ฒ ํ ์ ์์ต๋๋ค.
ํด๋น ์์์์ Hydration ๊ฒฝ๊ณ ๋ฅผ ๋๊ธฐ ์ํด์ suppressHydrationWarning={true}๋ฅผ ์ถ๊ฐํ๋ฉด ๋ฉ๋๋ค.
<!--
<div id="root">...</div> ์์ HTML ๋ด์ฉ๋ค์
react-dom/server์ผ๋ก ๋ง๋ค์ด์ง App์
๋๋ค.
-->
<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>
);
}์ด๊ฒ์ ํ ๋จ๊ณ ์๋๊น์ง๋ง ์ ์ฉ๋๋ฉฐ ํ์ถ๊ตฌEscape Hatch๋ฅผ ์๋ํ ๊ฒ์ ๋๋ค. ๋จ์ฉํ์ง ๋ง์ธ์. ํ ์คํธ ์ปจํ ์ธ ๊ฐ ์๋ ํ React๋ ์๋ชป๋ ๋ถ๋ถ์ ์์ ํ์ง ์์ ๊ฒ์ด๋ฉฐ, ๊ฐฑ์ ์ด ์ผ์ด๋๊ธฐ ์ ๊น์ง๋ ๋ถ์ผ์น ์ํ๋ก ๋จ์์์ ๊ฒ์ ๋๋ค.
์๋ก ๋ค๋ฅธ ํด๋ผ์ด์ธํธ์ ์๋ฒ ์ปจํ ์ธ ๋ค๋ฃจ๊ธฐ {/handling-different-client-and-server-content/}
์๋์ ์ผ๋ก ์๋ฒ์ ํด๋ผ์ด์ธํธ์์ ์๋ก ๋ค๋ฅธ ๋ด์ฉ์ ๋ ๋๋งํ๊ธธ ์ํ๋ค๋ฉด, ์๋ฒ์ ํด๋ผ์ด์ธํธ์์ ์๋ก ๋ค๋ฅธ ๋ฐฉ๋ฒ์ผ๋ก ๋ ๋๋งํ๋ฉด ๋ฉ๋๋ค. ํด๋ผ์ด์ธํธ์์ ์๋ฒ์๋ ๋ค๋ฅธ ๊ฒ์ ๋ ๋๋งํ ๋ ํด๋ผ์ด์ธํธ์์ Effect์์ true๋ก ํ ๋น๋๋ isClient๊ฐ์ State ๋ณ์๋ฅผ ์ฝ์ ์ ์์ต๋๋ค.
<!--
<div id="root">...</div> ์์ HTML ๋ด์ฉ๋ค์
react-dom/server์ผ๋ก ๋ง๋ค์ด์ง App์
๋๋ค.
-->
<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 ํ์ ์๋ก์ด ๊ฒฐ๊ณผ๋ฌผ์ด ๋๊ธฐ์ ์ผ๋ก ๋ ๋๋ง๋ฉ๋๋ค.
์ด ๋ฐฉ๋ฒ์ ๋ ๋ฒ ๋ ๋๋งํด์ผ ํ๊ธฐ ๋๋ฌธ์ Hydration์ ๋๋ฆฌ๊ฒ ํฉ๋๋ค. ๋๋ฆฐ ํต์ ์ํ์ผ ๊ฒฝ์ฐ์ ์ฌ์ฉ์ ๊ฒฝํ์ ์ผ๋ํ์ธ์. ์ด๊ธฐ HTML์ด ๋ ๋๋ง๋ ํ์ฐธ ํ์์ผ ์๋ฐ์คํฌ๋ฆฝํธ ์ฝ๋๋ฅผ ๋ถ๋ฌ์ต๋๋ค. ๋ฐ๋ผ์ Hydration ์ดํ์ ๋ฐ๋ก ๋ค๋ฅธ UI๋ฅผ ๋ ๋๋งํ๋ ๊ฒ์ ์ฌ์ฉ์์๊ฒ UI๊ฐ ์๊ฑฑ๊ฑฐ๋ฆฌ๋ ๊ฒ์ฒ๋ผ ๋ณด์ผ ์ ์์ต๋๋ค.
๋ฃจํธ์ Hydration์ด ๋๋ ํ์, root.render๋ฅผ ํธ์ถํด React ์ปดํฌ๋ํธ์ ๋ฃจํธ๋ฅผ ์
๋ฐ์ดํธ ํ ์ ์์ต๋๋ค. createRoot์๋ ๋ค๋ฅด๊ฒ HTML๋ก ์ต์ด์ ์ปจํ
์ธ ๊ฐ ์ด๋ฏธ ๋ ๋๋ง ๋์ด ์๊ธฐ ๋๋ฌธ์ ์์ฃผ ์ฌ์ฉํ ํ์๋ ์์ต๋๋ค.
Hydration ํ ์ด๋ค ์์ ์ root.render๋ฅผ ํธ์ถํ๋ค๋ฉด, ๊ทธ๋ฆฌ๊ณ ์ปดํฌ๋ํธ์ ํธ๋ฆฌ ๊ตฌ์กฐ๊ฐ ์ด์ ์ ๋ ๋๋งํ๋ ๊ตฌ์กฐ์ ์ผ์นํ๋ค๋ฉด, React๋ State๋ฅผ ๊ทธ๋๋ก ๋ณด์กดํฉ๋๋ค. ์
๋ ฅ ์ฐฝInput์ ์ด๋ป๊ฒ ํ์ดํํ๋ ์ง ๊ฐ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์์ต๋๋ค. ์ฆ, ์๋ ์์์์์ฒ๋ผ ๋งค์ด ๋ง๋ค ์ํ๋ฅผ ์
๋ฐ์ดํธํ๋ ๋ฐ๋ณต์ ์ธ render๋ฅผ ๋ฌธ์ ์์ด ๋ ๋๋ง ํ๋ค๋ ๊ฒ์ ์ ์ ์์ต๋๋ค.
<!--
<div id="root">...</div>์์ ๋ชจ๋ HTML ์ปจํ
์ธ ๋ react-dom/server๋ฅผ ํตํด ๋ง๋ค์ด ๋ ๋๋งํ <App />์
๋๋ค.
-->
<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" />
</>
);
}Hydration๋ ๋ฃจํธ์์ root.render๋ฅผ ํธ์ถํ๋ ๊ฒ์ ํํ ์ผ์ ์๋๋๋ค. ๋ด๋ถ ์ปดํฌ๋ํธ ์ค ํ ๊ณณ์์ useState๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ผ๋ฐ์ ์
๋๋ค.
React๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ชจ๋ ์ค๋ฅ๋ฅผ ์ฝ์์ ์ถ๋ ฅํฉ๋๋ค. ์ฌ์ฉ์ ์ ์ ์ค๋ฅ ๋ณด๊ณ ๊ธฐ๋ฅ์ ๊ตฌํํ๊ธฐ ์ํด์ onUncaughtError, onCaughtError, onRecoverableError์ ๊ฐ์ ์๋ฌ ํธ๋ค๋ฌ ๋ฃจํธ ์ต์
์ ์ ๊ณตํ ์ ์์ต๋๋ค.
import { hydrateRoot } from "react-dom/client";
import App from "./App.js";
import { reportCaughtError } from "./reportError";
const container = document.getElementById("root");
const root = hydrateRoot(container, <App />, {
onCaughtError: (error, errorInfo) => {
if (error.message !== "Known error") {
reportCaughtError({
error,
componentStack: errorInfo.componentStack,
});
}
},
});onCaughtError ์ต์ ์ ๋ค์ ๋ ๊ฐ์ ์ธ์๋ฅผ ๋ฐ๋ ํจ์์ ๋๋ค.
- ๋ฐ์ํ error ๊ฐ์ฒด.
- ์ค๋ฅ์ componentStack ์ ๋ณด๋ฅผ ํฌํจํ errorInfo ๊ฐ์ฒด.
onUncaughtError์ onRecoverableError๋ฅผ ํจ๊ป ์ฌ์ฉํ๋ฉด, ์ฌ์ฉ์ ์ ์ ์ค๋ฅ ๋ณด๊ณ ์์คํ
์ ๊ตฌํํ ์ ์์ต๋๋ค.
function reportError({ type, error, errorInfo }) {
// ๊ตฌ์ฒด์ ์ธ ๊ตฌํ์ ์ฌ๋ฌ๋ถ์๊ฒ ๋งก๊น๋๋ค.
// `console.error()`๋ ์ค๋ช
์ ์ํ ์ฉ๋์
๋๋ค.
console.error(type, error, "Component Stack: ");
console.error("Component Stack: ", errorInfo.componentStack);
}
export function onCaughtErrorProd(error, errorInfo) {
if (error.message !== "Known error") {
reportError({ type: "Caught", error, errorInfo });
}
}
export function onUncaughtErrorProd(error, errorInfo) {
reportError({ type: "Uncaught", error, errorInfo });
}
export function onRecoverableErrorProd(error, errorInfo) {
reportError({ type: "Recoverable", error, errorInfo });
}import { hydrateRoot } from "react-dom/client";
import App from "./App.js";
import {
onCaughtErrorProd,
onRecoverableErrorProd,
onUncaughtErrorProd,
} from "./reportError";
const container = document.getElementById("root");
hydrateRoot(container, <App />, {
// ๊ฐ๋ฐ ํ๊ฒฝ์์๋ ์ด ์ต์
๋ค์ ์ ๊ฑฐํ๊ณ
// React์ ๊ธฐ๋ณธ ํธ๋ค๋ฌ๋ฅผ ์ฌ์ฉํ๊ฑฐ๋ ์ง์ ์ค๋ฒ๋ ์ด๋ฅผ ๊ตฌํํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค.
// ์ฌ๊ธฐ์๋ ํธ์๋ฅผ ์ํด ์กฐ๊ฑด ์์ด ํธ๋ค๋ฌ๋ฅผ ์ง์ ํ์ต๋๋ค.
onCaughtError: onCaughtErrorProd,
onRecoverableError: onRecoverableErrorProd,
onUncaughtError: onUncaughtErrorProd,
});import { Component, useState } from "react";
function Boom() {
foo.bar = "baz";
}
class ErrorBoundary extends Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
export default function App() {
const [triggerUncaughtError, settriggerUncaughtError] = useState(false);
const [triggerCaughtError, setTriggerCaughtError] = useState(false);
return (
<>
<button onClick={() => settriggerUncaughtError(true)}>
Trigger uncaught error
</button>
{triggerUncaughtError && <Boom />}
<button onClick={() => setTriggerCaughtError(true)}>
Trigger caught error
</button>
{triggerCaughtError && (
<ErrorBoundary>
<Boom />
</ErrorBoundary>
)}
</>
);
}<!DOCTYPE html>
<html>
<head>
<title>My app</title>
</head>
<body>
<!--
Purposefully using HTML content that differs from the server-rendered content to trigger recoverable errors.
-->
<div id="root">Server content before hydration.</div>
</body>
</html>๋ค์๊ณผ ๊ฐ์ ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค: "You passed a second argument to root.render" {/im-getting-an-error-you-passed-a-second-argument-to-root-render/}
hydrateRoot ์ต์
์ root.render(...)์ ์ ๋ฌํ๋ ์ค์๊ฐ ํํ ์ผ์ด๋๊ณค ํฉ๋๋ค.
Warning: You passed a second argument to root.render(โฆ) but it only accepts one argument.
์์ ํ๋ ค๋ฉด ๋ฃจํธ ์ต์
์ root.render(...) ๋์ hydrateRoot(...)์ ์ ๋ฌํ์ธ์.
// ๐ฉ ์๋ชป๋ ๋ฐฉ๋ฒ: `root.render`๋ ํ๋์ ์ธ์๋ง ๋ฐ์ต๋๋ค.
root.render(App, {onUncaughtError});
// โ
์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ: ์ต์
์ `createRoot`์ ์ ๋ฌํ์ธ์.
const root = hydrateRoot(container, <App />, {onUncaughtError});