Skip to content

Latest commit

ย 

History

History
528 lines (379 loc) ยท 20.9 KB

File metadata and controls

528 lines (379 loc) ยท 20.9 KB
title hydrateRoot

hydrateRoot๋Š” ์ด์ „์— react-dom/server๋กœ ์ƒ์„ฑ๋œ HTML ์ฝ˜ํ…์ธ ๋ฅผ ๊ฐ€์ง„ ๋ธŒ๋ผ์šฐ์ € DOM ๋…ธ๋“œ ์•ˆ์— React ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.

const root = hydrateRoot(domNode, reactNode, options?)

๋ ˆํผ๋Ÿฐ์Šค {/reference/}

hydrateRoot(domNode, reactNode, options?) {/hydrateroot/}

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 ํ˜ธ์ถœ๋งŒ ๊ฐ€์ง‘๋‹ˆ๋‹ค.

์•„๋ž˜ ์˜ˆ์‹œ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.

๋งค๊ฐœ๋ณ€์ˆ˜ {/parameters/}

  • 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์— ์‚ฌ์šฉํ•˜๋Š” ๋ฌธ์ž์—ด ์ ‘๋‘์‚ฌ. ๊ฐ™์€ ํŽ˜์ด์ง€์—์„œ ์—ฌ๋Ÿฌ๊ฐœ์˜ ๋ฃจํŠธ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์ถฉ๋Œ์„ ํ”ผํ•˜๋Š” ๋ฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. ์„œ๋ฒ„์—์„œ ์‚ฌ์šฉํ•œ ๊ฐ’๊ณผ ๋ฐ˜๋“œ์‹œ ๋™์ผํ•œ ๊ฐ’์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋ฐ˜ํ™˜๊ฐ’ {/returns/}

hydrateRoot๋Š” render์™€ unmount ๋‘ ๊ฐ€์ง€ ๋ฉ”์„œ๋“œ๋ฅผ ํฌํ•จํ•œ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

์ฃผ์˜ ์‚ฌํ•ญ {/caveats/}

  • hydrateRoot()๋Š” ๋ Œ๋”๋ง๋œ ์ปจํ…์ธ ๊ฐ€ ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋ง๋œ ์ปจํ…์ธ ์™€ ๋™์ผํ•  ๊ฒƒ์„ ๊ธฐ๋Œ€ํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋ถˆ์ผ์น˜ ์‚ฌํ•ญ์€ ๋ฒ„๊ทธ๋กœ ์ทจ๊ธ‰ํ•˜๊ณ  ์ˆ˜์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฐœ๋ฐœ ๋ชจ๋“œ์—์„œ๋Š” React๊ฐ€ Hydration ์ค‘ ๋ถˆ์ผ์น˜์— ๋Œ€ํ•ด ๊ฒฝ๊ณ ํ•ฉ๋‹ˆ๋‹ค. ๋ถˆ์ผ์น˜๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒฝ์šฐ ์†์„ฑ ์ฐจ์ด๊ฐ€ ์ˆ˜์ •๋  ๊ฒƒ์ด๋ผ๋Š” ๋ณด์žฅ์€ ์—†์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์„ฑ๋Šฅ์ƒ์˜ ์ด์œ ๋กœ ์ค‘์š”ํ•œ๋ฐ, ๋Œ€๋ถ€๋ถ„์˜ ์•ฑ์—์„œ ๋ถˆ์ผ์น˜๋Š” ๋“œ๋ฌผ๊ธฐ ๋•Œ๋ฌธ์— ๋ชจ๋“  ๋งˆํฌ์—…์„ ๊ฒ€์ฆํ•˜๋Š” ๊ฒƒ์€ ๋งค์šฐ ๋น„ํšจ์œจ์ ์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
  • ์•ฑ์—์„œ hydrateRoot ํ˜ธ์ถœ์ด ๋‹จ ํ•œ๋ฒˆ๋งŒ ์žˆ์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค. ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์ด ํ˜ธ์ถœ์„ ๋Œ€์‹  ์ˆ˜ํ–‰ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์•ฑ์„ ์‚ฌ์ „์— ๋ Œ๋”๋ง๋œ HTML ์—†์ด ํด๋ผ์ด์–ธํŠธ์—์„œ ์ง์ ‘ ๋ Œ๋”๋งํ•œ๋‹ค๋ฉด, hydrateRoot()๋Š” ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. createRoot()๋ฅผ ๋Œ€์‹  ์‚ฌ์šฉํ•ด์ฃผ์„ธ์š”.

root.render(reactNode) {/root-render/}

๋ธŒ๋ผ์šฐ์ € DOM ์š”์†Œ ๋‚ด์—์„œ Hydrate๋œ React ๋ฃจํŠธ ์•ˆ์˜ React ์ปดํฌ๋„ŒํŠธ๋ฅผ ์—…๋ฐ์ดํŠธ ํ•˜๋ ค๋ฉด root.render๋ฅผ ํ˜ธ์ถœํ•˜์„ธ์š”.

root.render(<App />);

React๋Š” Hydrate๋œ root์—์„œ <App />์„ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.

์•„๋ž˜ ์˜ˆ์‹œ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.

๋งค๊ฐœ๋ณ€์ˆ˜ {/root-render-parameters/}

  • reactNode: ์—…๋ฐ์ดํŠธํ•˜๊ณ  ์‹ถ์€ "React ๋…ธ๋“œ"์ž…๋‹ˆ๋‹ค. ์ฃผ๋กœ <App />๊ฐ™์€ JSX๋ฅผ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋„˜๊ธฐ์ง€๋งŒ, createElement()๋กœ ๋งŒ๋“  React ์š”์†Œ ํ˜น์€ ๋ฌธ์ž์—ด, ์ˆซ์ž, null, undefined๋ฅผ ๋„˜๊ฒจ๋„ ๋ฉ๋‹ˆ๋‹ค.

๋ฐ˜ํ™˜๊ฐ’ {/root-render-returns/}

root.render๋Š” undefined๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

์ฃผ์˜ ์‚ฌํ•ญ {/root-render-caveats/}

  • ๋ฃจํŠธ๊ฐ€ Hydrate๋ฅผ ์™„๋ฃŒํ•˜๊ธฐ ์ „์— root.render๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด, React๋Š” ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋ง๋œ HTML์„ ๋ชจ๋‘ ์—†์• ๊ณ  ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ Œ๋”๋ง๋œ ์ปดํฌ๋„ŒํŠธ๋“ค๋กœ ์™„์ „ํžˆ ๊ต์ฒดํ•ฉ๋‹ˆ๋‹ค.

root.unmount() {/root-unmount/}

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-parameters/}

root.unmount๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋ฐ›์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

Returns {/root-unmount-returns/}

root.unmount returns undefined.

์ฃผ์˜ ์‚ฌํ•ญ {/root-unmount-caveats/}

  • root.unmount๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ํŠธ๋ฆฌ์˜ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ ํ•ด์ œ๋˜๊ณ  ๋ฃจํŠธ DOM ๋…ธ๋“œ์—์„œ React๊ฐ€ "๋ถ„๋ฆฌ"๋ฉ๋‹ˆ๋‹ค.

  • root.unmount๋ฅผ ํ•œ ๋ฒˆ ํ˜ธ์ถœํ•œ ํ›„์—๋Š” ๊ฐ™์€ ๋ฃจํŠธ์—์„œ root.render๋ฅผ ๋‹ค์‹œ ํ˜ธ์ถœํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋งˆ์šดํŠธ ํ•ด์ œ๋œ ๋ฃจํŠธ์—์„œ root.render๋ฅผ ํ˜ธ์ถœํ•˜๋ ค๊ณ  ํ•˜๋ฉด "๋งˆ์šดํŠธ ํ•ด์ œ๋œ ๋ฃจํŠธ๋ฅผ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.Cannot update an unmounted root" ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.


์‚ฌ์šฉ๋ฒ• {/usage/}

์„œ๋ฒ„์—์„œ ๋ Œ๋”๋ง๋œ HTML์„ Hydrateํ•˜๊ธฐ {/hydrating-server-rendered-html/}

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์— ๋ถ™์–ด๋ฒ„๋ฆฝ๋‹ˆ๋‹ค.


document ์ „์ฒด๋ฅผ Hydrateํ•˜๊ธฐ {/hydrating-an-entire-document/}

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๋œ ๋ฃจํŠธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ธฐ {/updating-a-hydrated-root-component/}

๋ฃจํŠธ์˜ 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๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ž…๋‹ˆ๋‹ค.

ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ์˜ค๋ฅ˜ ๋กœ๊น…ํ•˜๊ธฐ {/error-logging-in-production/}

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 ์˜ต์…˜์€ ๋‹ค์Œ ๋‘ ๊ฐœ์˜ ์ธ์ž๋ฅผ ๋ฐ›๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.

  1. ๋ฐœ์ƒํ•œ error ๊ฐ์ฒด.
  2. ์˜ค๋ฅ˜์˜ 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>

๋ฌธ์ œ ํ•ด๊ฒฐ {/troubleshooting/}

๋‹ค์Œ๊ณผ ๊ฐ™์€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค: "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});