hydration

hongยท2026๋…„ 4์›” 7์ผ

javascript

๋ชฉ๋ก ๋ณด๊ธฐ
5/12

๐Ÿข hydration์— ๋Œ€ํ•ด์„œ

์„œ๋ฒ„๊ฐ€ ๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด์„œ ๋ณด๋‚ด์ค€ HTML์— ๋ธŒ๋ผ์šฐ์ €๊ฐ€ javascript ๊ธฐ๋Šฅ์„ ๋‹ค์‹œ ๋ถ™์—ฌ์„œ
์ธํ„ฐ๋ž™์…˜ ๊ฐ€๋Šฅํ•œ ์ƒํƒœ๊ฐ€ ๋˜๋Š” ๊ฒƒ

๊ณผ์ • :
1. ์„œ๋ฒ„๊ฐ€ html์„ ๋ Œ๋”๋งํ•ด์„œ ๋ณด๋ƒ„
2. js๊ฐ€ ๋‹ค ์•ˆ๋– ๋„ ์‚ฌ์šฉ์ž๋Š” ์ผ๋‹จ ํ™”๋ฉด์„ ๋นจ๋ฆฌ ๋ณด๋Š”๊ฒŒ ๊ฐ€๋Šฅ
3. js๊ฐ€ ๋กœ๋“œ๋จ
4. ๊ธฐ์กด html์„ ๋ณด๊ณ  ์—ฐ๊ฒฐ ์ž‘์—…์„ ํ•จ
5. ํด๋ฆญ, ์ž…๋ ฅ, ์ƒํƒœ ๋ณ€๊ฒฝ๋“ฑ์ด ์ •์ƒ ์ž‘๋™ํ•จ

-> ์ฒ˜์Œ ํ™”๋ฉด์€ ๋น ๋ฅด๊ฒŒ ๋ณด์—ฌ์ฃผ๊ณ , ๋‚˜์ค‘์— ๋™์ž‘์„ ๋ถ™์ด๋Š” ๋ฐฉ์‹!

๋งŒ์•ฝ์— CSR๋งŒ ์“ด๋‹ค๋ฉด,
์ฒ˜์Œ์—” ๋นˆ div๋งŒ ๋ฐ›๊ณ  js ๋‹ค ๋ฐ›์•„์•ผ ํ™”๋ฉด์ด ๋œฐ ์ˆ˜ ์žˆ์Œ.
๊ทผ๋ฐ SSR + hydration์ด๋ฉด? ์ผ๋‹จ ๋น ๋ฅด๊ฒŒ ํ™”๋ฉด ํ™•์ธ์„ ํ•  ์ˆ˜ ์žˆ๋Š”๊ฑฐ์ž„

https://react.dev/reference/react-dom/client/hydrateRoot

hydrateRoot๋กœ ๋ฆฌ์•กํŠธ์—์„œ ์–ด๋–ป๊ฒŒ html์— ๋ฆฌ์•กํŠธ ๋ถ™์ด๋Š”์ง€ ์„ค๋ช…ํ•ด์คŒ..
์ฒ˜์Œ์—” ์ดํ•ด๊ฐ€ ์ข€ ์–ด๋ ค์› ์Œ

์˜ˆ์‹œ )

//index.js

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

hydrateRoot(
  document.getElementById('root'),
  <App />
);
//index.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>
//App.js

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

์ด๊ฑธ๋ณด๋ฉด ์•Œ์ˆ˜์žˆ๋Š”๊ฑด
๋ณดํ†ต CSR์ด๋ผ๋ฉด ์ฒจ์— root์•ˆ์— ๋นˆ๊ป๋ฐ๊ธฐ๊ฐ€ ๋“ค์–ด์žˆ๊ฒ ์ง€๋งŒ hydrateRoot๋ฅผ ์“ด๋‹ค๋ฉด ์ด๋ฏธ Hello, world! ์–ด์ฉŒ๊ณ ๊ฐ€ ์žˆ๋Š” html์ด ๋“ค์–ด์™€์žˆ๋Š”๊ฑฐ์ž„

๋‚ด๊ฐ€ ์ฒ˜์Œ์— hydration๊ฐœ๋…์„ ๋“ค์—ˆ์„ ๋•Œ๋Š” ์•„ ์ฒจ์— ์„œ๋ฒ„์—์„œ ๋ณด๋‚ด์ค€ html์—์„œ id๋‚˜ class๋ฅผ ๊ฐ€์ ธ์˜จ๋’ค ์ปดํฌ๋„ŒํŠธ๋ž‘ ์—ฐ๊ฒฐํ•˜๋Š” ์‹์ด๊ฒ ๊ตฌ๋‚˜... ์ด๋ ‡๊ฒŒ ์˜ˆ์ƒํ–ˆ๋Š”๋ฐ ๊ทธ๊ฒŒ ์•„๋‹ˆ์—ˆ์Œ

๊ทธ๋ƒฅ CSR๊ณผ ๋น„์Šทํ•˜๊ฒŒ ์ฝ”๋“œ์ž‘์„ฑํ•˜๊ณ  hydrateRoot๋งŒ ํ•˜๋ฉด, ๊ฑฐ๊ธฐ์— ๋งž์ถฐ์„œ ๋จผ์ € html๋กœ ๋‚ด๋ ค์ฃผ๊ณ  js๊ฐ€ ๋กœ๋“œ๋˜๊ณ  ๋‚˜์„œ ๊ทธ DOM ๊ตฌ์กฐ, ํ…์ŠคํŠธ, ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง ๋“ฑ์„ ๋น„๊ตํ•˜๋ฉด์„œ ์—ฐ๊ฒฐ๋˜๋Š” ๊ฑฐ์ž„.

๊ทธ๋Ÿฐ๋ฐ, ์„œ๋ฒ„์—์„œ ๋‚ด๋ ค์ค€ html๊ณผ ํด๋ผ์ด์–ธํŠธ์˜ ์ฒซ๋ Œ๋” ๊ฒฐ๊ณผ๊ฐ€ ๋‹ค๋ฅผ ๋•Œ ์–ด๊ธ‹๋‚จ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Œ ์ด๊ฑธ hydration mismatch๋ผ๊ณ  ํ•œ๋‹ค๊ณ  ํ•จ

๐Ÿ€ hydration mismatch๋ž€ ?

์„œ๋ฒ„๊ฐ€ ๋งŒ๋“  html์ด๋ž‘ js๊ฐ€ ์ฒ˜์Œ ๋ Œ๋”๋งํ•œ ๊ฒฐ๊ณผ๊ฐ€ ๋‹ค๋ฅผ ๋•Œ ์ƒ๊น€.

ํ…์ŠคํŠธ๊ฐ€ ์กฐ๊ธˆ ๋‹ค๋ฅด๊ฑฐ๋‚˜, ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ณด๋Š” ๊ตฌ์กฐ๊ฐ€ ํฌ๊ฒŒ ๋‹ค๋ฅผ ๋•Œ hydration ๊ณผ์ •์—์„œ ์–ด๊ธ‹๋‚˜๋Š” ์ƒํƒœ๋ฅผ ๋งํ•จ

์ด ๊ฒฝ์šฐ ๊ฒฝ๊ณ ๊ฐ€ ๋œจ๊ฑฐ๋‚˜, ๊นจ์ง€๊ฑฐ๋‚˜, ์„œ๋ฒ„์—์„œ ๋‚ด๋ ค์ค€ html์„ ๋ฒ„๋ฆฌ๊ณ  ํด๋ผ์ด์–ธํŠธ ๋ Œ๋”๋ง์œผ๋กœ ์ „ํ™˜๋ ์ˆ˜์žˆ์Œ

์˜ˆ๋ฅผ๋“ค์–ด ํšŒ์› ์ข…๋ฅ˜์— ๋”ฐ๋ผ UI๋ฅผ ๋‹ค๋ฅด๊ฒŒ ๋ณด์—ฌ์ค˜์•ผํ•œ๋‹ค๊ณ  ํ•˜์ž

๊ณฐ ํšŒ์›์ธ ๊ฒฝ์šฐ BearPage๋ฅผ ๋ณด์—ฌ์ค˜์•ผํ•˜๊ณ 
๊ณฐํšŒ์›์ด ์•„๋‹Œ ๊ฒฝ์šฐ AllPage๋ฅผ ๋ณด์—ฌ์ค˜์•ผํ•จ

function Page() {
  const isBear = localStorage.getItem('isBear') === 'true';
  return isBear ? <BearPage /> : <AllPage />;
}

์ด๊ฑธ ์œ„ํ•ด ์ด๋ ‡๊ฒŒ ์ž‘์„ฑํ•ด๋‘”๋‹ค๋ฉด?
์„œ๋ฒ„๋Š” ๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€์—์„œ ๊ฐ’์„ ๋ชป์ฝ์–ด์„œ AllPage ๋ Œ๋”ํ• ์ˆ˜๋„ ์žˆ์Œ

๊ทผ๋ฐ ์‚ฌ์‹ค ๊ณฐ์ด์—ˆ๋”๋ผ๋ฉด..?
์„œ๋ฒ„ html๊ณผ ํด๋ผ์ด์–ธํŠธ์—์„œ์˜ ๋ Œ๋”๊ฒฐ๊ณผ์˜ ๋ถˆ์ผ์น˜๋กœ ๊ณ ์žฅ๋‚ ์ˆ˜์žˆ์Œ

์ด๋ฅผ ์œ„ํ•ด์„  ์„œ๋ฒ„์—์„œ๋„ isBear๊ฐ’์„ ์ •ํ™•ํžˆ ์•Œ์ˆ˜์žˆ๊ฒŒํ•˜๊ฑฐ๋‚˜ ์•„์˜ˆ ๊ณตํ†ต ํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“ค์–ด์„œ fallback UI๋กœ ์“ฐ๊ฑฐ๋‚˜ ์ด๋Ÿฐ ์ „๋žต์„ ํŽผ์น  ์ˆ˜ ์žˆ์„๊ฑฐ์ž„

์•„๋ฌดํŠผ UX์— ์ •๋ง ์ข‹์€ ๋ฐฉ๋ฒ•๊ฐ™์Œ
์ธํ„ฐ๋„ท์ด ๋А๋ฆฐํ™˜๊ฒฝ์—์„œ๋„ ์–ด์ฐŒ๋๋“  ๋ญ”๊ฐ€ ๋ณด์ธ๋‹ค๋Š”๊ฑด ๋„ˆ๋ฌด์ข‹์€๊ฑฐ๊ณ ..
์–ธ์  ๊ฐ„ ํ•ด๋ด์•ผ์ง€

๋

profile
ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์„ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค โŒจ๏ธ

0๊ฐœ์˜ ๋Œ“๊ธ€