hydrateRoot๋Š” react-dom/server๋ฅผ ํ†ตํ•ด ์‚ฌ์ „์— ๋งŒ๋“ค์–ด์ง„ HTML๋กœ ๊ทธ๋ ค์ง„ ๋ธŒ๋ผ์šฐ์ € DOM ๋…ธ๋“œ ๋‚ด๋ถ€์— ๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.

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

Reference

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์„ ์ง์ ‘ ๊ด€๋ฆฌํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. App์„ React๋กœ ์ „๋ถ€ ๋งŒ๋“ค์—ˆ๋‹ค๋ฉด ๋ณดํ†ต์€ ๋‹จ ํ•˜๋‚˜์˜ root component์™€ ํ•จ๊ป˜ hydrateRoot๋ฅผ ํ•œ ๋ฒˆ ํ˜ธ์ถœํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์•„๋ž˜ ์—ฌ๋Ÿฌ ์˜ˆ์‹œ๋ฅผ ํ™•์ธํ•ด๋ณด์„ธ์š”.

Parameters

  • domNode: ์„œ๋ฒ„์—์„œ root element๋กœ ๋ Œ๋”๋ง๋œ DOM element

  • reactNode: ์•ž์„œ ์กด์žฌํ•˜๋Š” HTML์— ๋ Œ๋”๋งํ•˜๊ธฐ ์œ„ํ•œ โ€œ๋ฆฌ์•กํŠธ ๋…ธ๋“œโ€ ์ž…๋‹ˆ๋‹ค. ์ฃผ๋กœ ReactDOM Server์˜ renderToPipeableStream(<App />)์™€ ๊ฐ™์€ ๋ฉ”์†Œ๋“œ๋กœ ๋ Œ๋”๋ง๋œ <App />๊ฐ™์€ JSX ์กฐ๊ฐ๋“ค์ž…๋‹ˆ๋‹ค.

  • optional options: ๋ฆฌ์•กํŠธ root์— ์˜ต์…˜์„ ์ฃผ๊ธฐ ์œ„ํ•œ ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.

    • optional onRecoverableError: ๋ฆฌ์•กํŠธ๊ฐ€ ์—๋Ÿฌ์—์„œ ์ž๋™์œผ๋กœ ํšŒ๋ณต๋˜์—ˆ์„ ๋•Œ ํ˜ธ์ถœํ•˜๋Š” ์ฝœ๋ฐฑํ•จ์ˆ˜.
    • optional identifierPrefix: ๋ฆฌ์•กํŠธ๊ฐ€ ID๋กœ ์‚ฌ์šฉํ•˜๋Š” ์ ‘๋‘์‚ฌ๋กœ useId๋กœ ๋งŒ๋“ค์–ด์ง„ ๊ฐ’์ž…๋‹ˆ๋‹ค. ๊ฐ™์€ ํŽ˜์ด์ง€์—์„œ ์—ฌ๋Ÿฌ root๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์ถฉ๋Œ์„ ํ”ผํ•  ๋•Œ ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์„œ๋ฒ„์—์„œ ์‚ฌ์šฉํ•œ ๊ฐ’๊ณผ ๋ฐ˜๋“œ์‹œ ๋™์ผํ•œ ๊ฐ’์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Returns

hydrateRoot๋Š” 2๊ฐ€์ง€ ๋ฉ”์†Œ๋“œ๊ฐ€ ํฌํ•จ๋œ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค : render ๊ทธ๋ฆฌ๊ณ  unmount.

Caveats

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

root.render(reactNode)

hydrate๋œ ๋ฆฌ์•กํŠธ root๋ถ€ํ„ฐ ๋‚ด๋ถ€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ƒˆ๋กœ์šด ๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ๋กœ ๊ฐฑ์‹ ํ•˜๊ธฐ ์œ„ํ•ด root.render๋ฅผ ํ˜ธ์ถœํ•ด์ฃผ์„ธ์š”. ๋ธŒ๋ผ์šฐ์ € DOM ์š”์†Œ๋“ค๋„ ํ•จ๊ป˜ ๊ฐฑ์‹ ๋ฉ๋‹ˆ๋‹ค.

root.render(<App />);

๋ฆฌ์•กํŠธ๋Š” hydrate๋œ root๋ถ€ํ„ฐ ๋‚ด๋ถ€๋ฅผ <App />์œผ๋กœ ๊ฐฑ์‹ ํ•ฉ๋‹ˆ๋‹ค.

์•„๋ž˜ ์˜ˆ์‹œ๋ฅผ ํ™•์ธํ•ด๋ณด์„ธ์š”.

Parameters

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

Returns

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

Caveats

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

root.unmount()

root.unmount๋ฅผ ํ˜ธ์ถœํ•ด ๋ฆฌ์•กํŠธ root๋ถ€ํ„ฐ ๊ทธ ํ•˜์œ„์— ๋ Œ๋”๋ง๋œ ํŠธ๋ฆฌ๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค.

root.unmount();

์ฒ˜์Œ๋ถ€ํ„ฐ ๋๊นŒ์ง€ ๋ฆฌ์•กํŠธ๋กœ ๋งŒ๋“  ์•ฑ์€ root.unmount๋ฅผ ํ˜ธ์ถœํ•  ๊ฒฝ์šฐ๊ฐ€ ๊ฑฐ์˜ ์—†์Šต๋‹ˆ๋‹ค.

์ฃผ๋กœ ๋ฆฌ์•กํŠธ root๋ถ€ํ„ฐ ํ˜น์€ ๊ทธ ์ƒ์œ„์—์„œ๋ถ€ํ„ฐ ์‹œ์ž‘๋œ DOM node๋“ค์„ ๋‹ค๋ฅธ ์ฝ”๋“œ์— ์˜ํ•ด DOM์—์„œ ์‚ญ์ œ๋˜์–ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, jQuery ํƒญ ํŒจ๋„์ด ํ™œ์„ฑํ™” ๋˜์–ด ์žˆ์ง€ ์•Š์€ ํƒญ์„ DOM์—์„œ ์ง€์šด๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ด…์‹œ๋‹ค. ํƒญ์ด ์ง€์›Œ์ง€๋ฉด, ๋ฆฌ์•กํŠธ root์™€ ๊ทธ ๋‚ด๋ถ€๋ฅผ ํฌํ•จํ•ด ๊ทธ ์•ˆ์˜ ๋ชจ๋“  ๊ฒƒ์ด ์ง€์›Œ์ง€๊ฒŒ ๋˜๊ณ  DOM์—์„œ ๋˜ํ•œ ์ง€์›Œ์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. root.unmount๋ฅผ ํ˜ธ์ถœํ•ด ๋ฆฌ์•กํŠธ์—๊ฒŒ ์‚ญ์ œ๋œ ์ปจํ…์ธ ๋“ค์„ โ€œ๊ทธ๋งŒโ€ ๋‹ค๋ฃจ๋ผ๊ณ  ์•Œ๋ ค์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์‚ญ์ œ๋˜์–ด๋ฒ„๋ฆฐ ๋ฆฌ์•กํŠธ root ๋‚ด๋ถ€์˜ ์ปดํฌ๋„ŒํŠธ๋“ค์€ ์‚ญ์ œ๋˜์ง€ ์•Š์„ ๊ฒƒ์ด๋ฉฐ, โ€œ๊ตฌ๋…โ€์ฒ˜๋Ÿผ ์ปดํ“จํŒ… ์ž์›์„ ์ž์œ ๋กญ๊ฒŒ ๋†“์•„์ฃผ์ง€ ๋ชปํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

root.unmount๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด root ๋‚ด๋ถ€์˜ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๋ฅผ unmountํ•˜๊ณ  root DOM node์—์„œ ๋ฆฌ์•กํŠธ๋ฅผ โ€œ๋–ผ์–ดโ€๋ƒ…๋‹ˆ๋‹ค. root ๋‚ด๋ถ€์˜ event handler์™€ state๊นŒ์ง€ ๋ชจ๋‘ ํฌํ•จํ•ด unmount ๋ฐ ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค.

Parameters

root.unmount๋Š” ๊ทธ ์–ด๋–ค ํŒŒ๋ผ๋ฏธํ„ฐ๋„ ๋ฐ›์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

Returns

render๋Š” null์„ ๋ฆฌํ„ดํ•ฉ๋‹ˆ๋‹ค.

Caveats

  • root.unmount๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด root๋ถ€ํ„ฐ ๊ทธ ์•ˆ์˜ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๊ฐ€ unmount๋˜๊ณ  root DOM node์—์„œ ๋ฆฌ์•กํŠธ๋ฅผ ๋–ผ์–ด๋ƒ…๋‹ˆ๋‹ค.

  • root.unmount๋ฅผ ํ•œ๋ฒˆ ํ˜ธ์ถœํ•œ ์ดํ›„์—” root.render๋ฅผ root์— ๋‹ค์‹œ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. unmount๋œ root์— ๋‹ค์‹œ root.render๋ฅผ ํ˜ธ์ถœํ•˜๋ ค๊ณ  ํ•œ๋‹ค๋ฉด โ€œCannot update an unmounted rootโ€ ์—๋Ÿฌ๋ฅผ throwํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.


์‚ฌ์šฉ ์˜ˆ์‹œ

์„œ๋ฒ„์—์„œ ๋ Œ๋”๋ง๋œ HTML์„ hydrateํ•˜๊ธฐ

react-dom/server๋กœ ์•ฑ์˜ HTML์„ ์ƒ์„ฑํ–ˆ๋‹ค๋ฉด, ํด๋ผ์ด์–ธํŠธ์—์„œ hydrateํ•ด์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

import { hydrateRoot } from 'react-dom/client';

hydrateRoot(document.getElementById('root'), <App />);

์œ„ ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด ์„œ๋ฒ„ HTML์„ ๋ธŒ๋ผ์šฐ์ € DOM node์—์„œ ๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ด์šฉํ•ด hydrate ํ•ด์ค„ ๊ฒƒ ์ž…๋‹ˆ๋‹ค. ์ฃผ๋กœ ์•ฑ์„ ์‹œ์ž‘ํ•  ๋•Œ ๋‹จ ํ•œ ๋ฒˆ ์‹คํ–‰ํ•˜๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์‚ฌ์šฉ์ค‘์ด๋ผ๋ฉด ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์•Œ์•„์„œ ์‹คํ–‰ํ•ด ์ค„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์•ฑ์„ hydrateํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋ฆฌ์•กํŠธ๋Š” ์ปดํฌ๋„ŒํŠธ์˜ ๋กœ์ง์„ ์‚ฌ์ „์— ์„œ๋ฒ„์—์„œ ๋งŒ๋“ค์–ด ์ง„ HTML์— โ€œ๋ถ™์ผโ€๊ฒƒ ์ž…๋‹ˆ๋‹ค. Hydration์„ ํ†ตํ•ด ์„œ๋ฒ„์—์„œ ๋งŒ๋“ค์–ด์ง„ ์ตœ์ดˆ์˜ HTML ์Šค๋ƒ…์ƒท์„ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์™„์ „ํžˆ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒํ•œ ์•ฑ์œผ๋กœ ๋ฐ”๊ฟ”์ฃผ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

import './styles.css';
import { hydrateRoot } from 'react-dom/client';
import App from './App.js';

hydrateRoot(
  document.getElementById('root'),
  <App />
);

hydrateRoot๋ฅผ ๋‹ค์‹œ ํ˜ธ์ถœํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ๊ณณ์—์„œ ๋” ํ˜ธ์ถœํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. ์ด ์‹œ์ ๋ถ€ํ„ฐ ๋ฆฌ์•กํŠธ๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ DOM์„ ๋‹ค๋ฃจ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. UI๋ฅผ ๊ฐฑ์‹ ํ•˜๊ธฐ ์œ„ํ•ด์„  use state๋ฅผ ๋Œ€์‹  ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ฃผ์˜ํ•˜์„ธ์š”!

hydrateRoot์— ์ „๋‹ฌํ•œ ๋ฆฌ์•กํŠธ ํŠธ๋ฆฌ๋Š” ์„œ๋ฒ„์—์„œ ๋งŒ๋“ค์—ˆ๋˜ ๋ฆฌ์•กํŠธ ํŠธ๋ฆฌ ๊ฒฐ๊ณผ๋ฌผ๊ณผ ๋™์ผํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ด๋Š” ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์œ„ํ•ด์„œ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ์œ ์ €๋Š” ์„œ๋ฒ„์—์„œ ๋งŒ๋“ค์–ด์ง„ HTML์„ JavaScript ์ฝ”๋“œ๊ฐ€ ๋กœ๋“œ๋  ๋•Œ๊นŒ์ง€ ๋‘˜๋Ÿฌ๋ณด๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์•ฑ์˜ ๋กœ๋”ฉ์„ ๋” ๋น ๋ฅด๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด ์„œ๋ฒ„๋Š” ์ผ์ข…์˜ ์‹ ๊ธฐ๋ฃจ๋กœ์„œ ๋ฆฌ์•กํŠธ ๊ฒฐ๊ณผ๋ฌผ์ธ HTML ์Šค๋ƒ…์ƒท์„ ๋งŒ๋“ค์–ด ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. ๊ฐ‘์ž๊ธฐ ๋‹ค๋ฅธ ์ปจํ…์ธ ๋ฅผ ๋ณด์—ฌ์ฃผ๊ฒŒ ๋˜๋ฉด ์‹ ๊ธฐ๋ฃจ๊ฐ€ ๊นจ์ ธ๋ฒ„๋ฆฌ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฐ ์ด์œ ๋กœ ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋งํ•œ ๊ฒฐ๊ณผ๋ฌผ๊ณผ ํด๋ผ์ด์–ธํŠธ์—์„œ ์ตœ์ดˆ๋กœ ๋ Œ๋”๋งํ•œ ๊ฒฐ๊ณผ๋ฌผ์ด ๊ฐ™์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ฃผ๋กœ ์•„๋ž˜์™€ ๊ฐ™์€ ์›์ธ์œผ๋กœ hydration ์—๋Ÿฌ๊ฐ€ ์ผ์–ด๋‚ฉ๋‹ˆ๋‹ค.

  • ๋ฆฌ์•กํŠธ๋ฅผ ํ†ตํ•ด ๋งŒ๋“ค์–ด์ง„ HTML์˜ root node์•ˆ์— ์ƒˆ ์ค„๊ฐ™์€ ์ถ”๊ฐ€์ ์ธ ๊ณต๋ฐฑ.
  • typeof window !== 'undefined'๊ณผ ๊ฐ™์€ ์กฐ๊ฑด์„ ๋ Œ๋”๋ง ๋กœ์ง์—์„œ ์‚ฌ์šฉํ•จ.
  • window.matchMedia๊ฐ™์€ ๋ธŒ๋ผ์šฐ์ €์—์„œ๋งŒ ์‚ฌ์šฉ๊ฐ€๋Šฅํ•œ API๋ฅผ ๋ Œ๋”๋ง ๋กœ์ง์— ์‚ฌ์šฉํ•จ.
  • ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ์—์„œ ์„œ๋กœ ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ๋ฅผ ๋ Œ๋”๋งํ•จ.

๋ฆฌ์•กํŠธ๋Š” hydration ์—๋Ÿฌ์—์„œ ๋ณต๊ตฌ๋ฉ๋‹ˆ๋‹ค, ํ•˜์ง€๋งŒ ๋‹ค๋ฅธ ๋ฒ„๊ทธ๋“ค๊ณผ ๊ฐ™์ด ๋ฐ˜๋“œ์‹œ ๊ณ ์ณ์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ€์žฅ ๋‚˜์€ ๊ฒฝ์šฐ๋Š” ๊ทธ์ € ๋Š๋ ค์ง€๊ธฐ๋งŒ ํ•  ๋ฟ์ด์ง€๋งŒ, ์ตœ์•…์˜ ๊ฒฝ์šฐ์—” ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ๋‹ค๋ฅธ element์— ๋ถ™์–ด๋ฒ„๋ฆฝ๋‹ˆ๋‹ค.


document ์ „์ฒด๋ฅผ hydrateํ•˜๊ธฐ

๋ฆฌ์•กํŠธ๋กœ ์•ฑ์„ ๋ชจ๋‘ ๋งŒ๋“ค์—ˆ์„ ๊ฒฝ์šฐ <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 ๋ถˆ์ผ์น˜ ์—๋Ÿฌ ์–ต์ œํ•˜๊ธฐ

์–ด๋–ค element์˜ ์†์„ฑ์ด๋‚˜ text content๊ฐ€ ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ์—์„œ ์–ด์ฉ” ์ˆ˜ ์—†์ด ๋‹ค๋ฅผ ๋•(์˜ˆ๋ฅผ ๋“ค์–ด, timestamp๋ฅผ ์ด์šฉํ–ˆ๋‹ค๊ฑฐ๋‚˜), hydration ๋ถˆ์ผ์น˜ ๊ฒฝ๊ณ ๋ฅผ ์•ˆ๋ณด์ด๊ฒŒ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•ด๋‹น element์—์„œ hydration ๊ฒฝ๊ณ ๋ฅผ ๋„๊ธฐ ์œ„ํ•ด์„  suppressHydrationWarning={true}๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

export default function App() {
  return (
    <h1 suppressHydrationWarning={true}>
      Current Date: {new Date().toLocaleDateString()}
    </h1>
  );
}

์ด๊ฒƒ์€ ํ•œ ๋‹จ๊ณ„ ์•„๋ž˜๊นŒ์ง€๋งŒ ์ ์šฉ๋˜๋ฉฐ ๋น„์ƒ ํƒˆ์ถœ๊ตฌ๋ฅผ ์˜๋„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋‚จ์šฉํ•˜์ง€ ๋งˆ์„ธ์š”. text context๊ฐ€ ์•„๋‹Œ ํ•œ, ๋ฆฌ์•กํŠธ๋Š” ์ž˜๋ชป๋œ ๋ถ€๋ถ„์„ ์ˆ˜์ •ํ•˜์ง€ ์•Š์„ ๊ฒƒ์ด๋ฉฐ ๊ฐฑ์‹ ์ด ์ผ์–ด๋‚˜๊ธฐ ์ „๊นŒ์ง€๋Š” ๋ถˆ์ผ์น˜ํ•œ ์ƒํƒœ๋กœ ๋‚จ์•„์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.


์„œ๋กœ ๋‹ค๋ฅธ ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ์ปจํ…์ธ  ๋‹ค๋ฃจ๊ธฐ

์˜๋„์ ์œผ๋กœ ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ์—์„œ ์„œ๋กœ ๋‹ค๋ฅธ ๋‚ด์šฉ์„ ๋ Œ๋”๋งํ•˜๊ธธ ์›ํ•œ๋‹ค๋ฉด, ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ์—์„œ ์„œ๋กœ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์œผ๋กœ ๋ Œ๋”๋งํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ์—์„œ ์„œ๋ฒ„์™€๋Š” ๋‹ค๋ฅธ ๊ฒƒ์„ ๋ Œ๋”๋งํ•  ๋•Œ ํด๋ผ์ด์–ธํŠธ์—์„  Effect์—์„œ true๋กœ ํ• ๋‹น๋˜๋Š” isClient๊ฐ™์€ ์ƒํƒœ ๋ณ€์ˆ˜๋ฅผ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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์ด ๋ Œ๋”๋œ ํ•œ์ฐธ ์ดํ›„์—์•ผ JavaScript ์ฝ”๋“œ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ hydration ์ดํ›„์— ๋ฐ”๋กœ ๋‹ค๋ฅธ UI๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ๊ฒƒ์€ ์œ ์ €์—๊ฒŒ UI๊ฐ€ ์‚๊ฑฑ๊ฑฐ๋ฆฌ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


hydrate๋œ root ์ปดํฌ๋„ŒํŠธ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ธฐ

root์˜ hydrating์ด ๋๋‚œ ์ดํ›„์—, root.render๋ฅผ ํ˜ธ์ถœํ•ด ๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ์˜ root๋ฅผ ์—…๋ฐ์ดํŠธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. createRoot์™€๋Š” ๋‹ค๋ฅด๊ฒŒ HTML๋กœ ์ตœ์ดˆ์˜ ์ปจํ…์ธ ๊ฐ€ ์ด๋ฏธ ๋ Œ๋”๋ง ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ž์ฃผ ์‚ฌ์šฉํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค.

hydration ํ›„ ์–ด๋–ค ์‹œ์ ์— root.render๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค๋ฉด, ๊ทธ๋ฆฌ๊ณ  ์ปดํฌ๋„ŒํŠธ์˜ ํŠธ๋ฆฌ ๊ตฌ์กฐ๊ฐ€ ์ด์ „์— ๋ Œ๋”๋งํ–ˆ๋˜ ๊ตฌ์กฐ์™€ ์ผ์น˜ํ•œ๋‹ค๋ฉด, ๋ฆฌ์•กํŠธ๋Š” ์ƒํƒœ๋ฅผ ๊ทธ๋Œ€๋กœ ๋ณด์กดํ•ฉ๋‹ˆ๋‹ค. input์— ์–ด๋–ป๊ฒŒ ํƒ€์ดํ•‘ํ•˜๋Š”์ง€์— ๋”ฐ๋ผ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ฆ‰, ์•„๋ž˜ ์˜ˆ์‹œ์—์„œ์ฒ˜๋Ÿผ ๋งค์ดˆ ๋งˆ๋‹ค ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋ฐ˜๋ณต์ ์ธ render๋Š” ๋ฌธ์ œ ์—†์ด ๋ Œ๋”๋ง ๋œ๋‹ค๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

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);

hydrate๋œ root์— root.render๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์€ ํ”ํ•œ ์ผ์€ ์•„๋‹™๋‹ˆ๋‹ค. ๋‚ด๋ถ€ ์ปดํฌ๋„ŒํŠธ ์ค‘ ํ•œ ๊ณณ์—์„œ ์ƒํƒœ ์—…๋ฐ์ดํŠธ๋ฅผ ํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ž…๋‹ˆ๋‹ค.