Skip to content

Latest commit

Β 

History

History
1217 lines (959 loc) Β· 37.8 KB

File metadata and controls

1217 lines (959 loc) Β· 37.8 KB
title createRoot

createRoot둜 λΈŒλΌμš°μ € DOM λ…Έλ“œ μ•ˆμ— React μ»΄ν¬λ„ŒνŠΈλ₯Ό ν‘œμ‹œν•˜λŠ” 루트λ₯Ό 생성할 수 μžˆμŠ΅λ‹ˆλ‹€.

const root = createRoot(domNode, options?)

레퍼런슀 {/reference/}

createRoot(domNode, options?) {/createroot/}

createRootλ₯Ό ν˜ΈμΆœν•˜λ©΄ λΈŒλΌμš°μ € DOM μ—˜λ¦¬λ¨ΌνŠΈ μ•ˆμ— μ½˜ν…μΈ λ₯Ό ν‘œμ‹œν•  수 μžˆλŠ” React 루트λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.

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

const domNode = document.getElementById('root');
const root = createRoot(domNode);

ReactλŠ” domNode에 λŒ€ν•œ 루트λ₯Ό μƒμ„±ν•˜κ³  κ·Έ μ•ˆμ— μžˆλŠ” DOM을 κ΄€λ¦¬ν•©λ‹ˆλ‹€. 루트λ₯Ό μƒμ„±ν•œ ν›„μ—λŠ” root.renderλ₯Ό ν˜ΈμΆœν•΄ κ·Έ μ•ˆμ— React μ»΄ν¬λ„ŒνŠΈλ₯Ό ν‘œμ‹œν•΄μ•Ό ν•©λ‹ˆλ‹€.

root.render(<App />);

μ˜¨μ „νžˆ React만으둜 μž‘μ„±λœ μ•±μ—μ„œλŠ” 일반적으둜 루트 μ»΄ν¬λ„ŒνŠΈμ— λŒ€ν•œ createRoot 호좜이 ν•˜λ‚˜λ§Œ μžˆμŠ΅λ‹ˆλ‹€. νŽ˜μ΄μ§€μ˜ 일뢀에 Reactλ₯Ό "λΏŒλ €μ„œ" μ‚¬μš©ν•˜λŠ” νŽ˜μ΄μ§€μ˜ κ²½μš°μ—λŠ” 루트λ₯Ό ν•„μš”λ‘œ ν•˜λŠ” 만큼 μž‘μ„±ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μ•„λž˜ μ˜ˆμ‹œλ₯Ό μ°Έκ³ ν•˜μ„Έμš”.

λ§€κ°œλ³€μˆ˜ {/parameters/}

  • domNode: DOM μ—˜λ¦¬λ¨ΌνŠΈ. ReactλŠ” DOM μ—˜λ¦¬λ¨ΌνŠΈμ— λŒ€ν•œ 루트λ₯Ό μƒμ„±ν•˜κ³  λ Œλ”λ§λœ React μ½˜ν…μΈ λ₯Ό ν‘œμ‹œν•˜λŠ” render와 같은 ν•¨μˆ˜λ₯Ό λ£¨νŠΈμ—μ„œ ν˜ΈμΆœν•  수 μžˆλ„λ‘ ν•©λ‹ˆλ‹€.

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

createRootλŠ” render와 unmount 두 κ°€μ§€ λ©”μ„œλ“œλ₯Ό ν¬ν•¨ν•œ 객체λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.

주의 사항 {/caveats/}

  • 앱이 μ„œλ²„μ—μ„œ λ Œλ”λ§ λ˜λŠ” 경우 createRoot()λŠ” μ‚¬μš©ν•  수 μ—†μŠ΅λ‹ˆλ‹€. λŒ€μ‹  hydrateRoot()λ₯Ό μ‚¬μš©ν•˜μ„Έμš”.
  • μ•±μ—μ„œ createRoot 호좜이 단 ν•œλ²ˆλ§Œ μžˆμ„ κ°€λŠ₯성이 λ†’μŠ΅λ‹ˆλ‹€. ν”„λ ˆμž„μ›Œν¬λ₯Ό μ‚¬μš©ν•˜λŠ” 경우 ν”„λ ˆμž„μ›Œν¬κ°€ 이 ν˜ΈμΆœμ„ λŒ€μ‹  μˆ˜ν–‰ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€.
  • μ»΄ν¬λ„ŒνŠΈμ˜ μžμ‹μ΄ μ•„λ‹Œ DOM 트리의 λ‹€λ₯Έ λΆ€λΆ„(예: λͺ¨λ‹¬ λ˜λŠ” 툴팁)에 JSX 쑰각을 λ Œλ”λ§ν•˜λ €λŠ” 경우, createRoot λŒ€μ‹  createPortal을 μ‚¬μš©ν•˜μ„Έμš”.

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

root.renderλ₯Ό ν˜ΈμΆœν•˜μ—¬ JSX 쑰각("React λ…Έλ“œ")을 React 루트의 λΈŒλΌμš°μ € DOM λ…Έλ“œμ— ν‘œμ‹œν•©λ‹ˆλ‹€.

root.render(<App />);

ReactλŠ” root에 <App />을 ν‘œμ‹œν•˜κ³  κ·Έ μ•ˆμ— μžˆλŠ” DOM을 κ΄€λ¦¬ν•©λ‹ˆλ‹€.

μ•„λž˜ μ˜ˆμ‹œλ₯Ό μ°Έκ³ ν•˜μ„Έμš”.

λ§€κ°œλ³€μˆ˜ {/root-render-parameters/}

  • reactNode: ν‘œμ‹œν•˜λ €λŠ” React λ…Έλ“œ. 일반적으둜 <App />κ³Ό 같은 JSX 쑰각이 λ˜μ§€λ§Œ, createElement()둜 μž‘μ„±ν•œ React μ—˜λ¦¬λ¨ΌνŠΈ, λ¬Έμžμ—΄, 숫자, null, undefined 등을 전달할 μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€.

λ°˜ν™˜κ°’ {/root-render-returns/}

root.renderλŠ” undefinedλ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.

주의 사항 {/root-render-caveats/}

  • root.renderλ₯Ό 처음 ν˜ΈμΆœν•˜λ©΄ ReactλŠ” React μ»΄ν¬λ„ŒνŠΈλ₯Ό λ Œλ”λ§ν•˜κΈ° 전에 React 루트 λ‚΄λΆ€μ˜ λͺ¨λ“  κΈ°μ‘΄ HTML μ½˜ν…μΈ λ₯Ό μ§€μ›λ‹ˆλ‹€.

  • μ„œλ²„μ—μ„œ λ˜λŠ” λΉŒλ“œ 쀑에 React에 μ˜ν•΄ μƒμ„±λœ HTML이 루트의 DOM λ…Έλ“œμ— ν¬ν•¨λœ 경우, λŒ€μ‹  이벀트 ν•Έλ“€λŸ¬λ₯Ό κΈ°μ‘΄ HTML에 μ²¨λΆ€ν•˜λŠ” hydrateRoot()λ₯Ό μ‚¬μš©ν•˜μ„Έμš”.

  • λ™μΌν•œ λ£¨νŠΈμ—μ„œ renderλ₯Ό 두 번 이상 ν˜ΈμΆœν•˜λ©΄, ReactλŠ” ν•„μš”μ— 따라 DOM을 μ—…λ°μ΄νŠΈν•˜μ—¬ μ‚¬μš©μžκ°€ μ „λ‹¬ν•œ μ΅œμ‹  JSXλ₯Ό λ°˜μ˜ν•©λ‹ˆλ‹€. ReactλŠ” 이전에 λ Œλ”λ§ 된 νŠΈλ¦¬μ™€ "비ꡐ"ν•΄μ„œ μž¬μ‚¬μš©ν•  수 μžˆλŠ” λΆ€λΆ„κ³Ό λ‹€μ‹œ λ§Œλ“€μ–΄μ•Ό ν•˜λŠ” 뢀뢄을 κ²°μ •ν•©λ‹ˆλ‹€. λ™μΌν•œ λ£¨νŠΈμ—μ„œ renderλ₯Ό λ‹€μ‹œ ν˜ΈμΆœν•˜λŠ” 것은 루트 μ»΄ν¬λ„ŒνŠΈμ—μ„œ set ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•˜λŠ” 것과 λΉ„μŠ·ν•©λ‹ˆλ‹€. ReactλŠ” λΆˆν•„μš”ν•œ DOM μ—…λ°μ΄νŠΈλ₯Ό ν”Όν•©λ‹ˆλ‹€.


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κ°€ "뢄리"λ©λ‹ˆλ‹€.

λ§€κ°œλ³€μˆ˜ {/root-unmount-parameters/}

root.unmountλŠ” λ§€κ°œλ³€μˆ˜λ₯Ό λ°›μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

λ°˜ν™˜κ°’ {/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" 였λ₯˜κ°€ λ°œμƒν•©λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ ν•΄λ‹Ή λ…Έλ“œμ˜ 이전 λ£¨νŠΈκ°€ 마운트 ν•΄μ œλœ ν›„ λ™μΌν•œ DOM λ…Έλ“œμ— μƒˆλ‘œμš΄ 루트λ₯Ό λ§Œλ“€ μˆ˜λŠ” μžˆμŠ΅λ‹ˆλ‹€.


μ‚¬μš©λ²• {/usage/}

μ˜¨μ „νžˆ React만으둜 μž‘μ„±λœ μ•± λ Œλ”λ§ν•˜κΈ° {/rendering-an-app-fully-built-with-react/}

앱이 μ˜¨μ „νžˆ React만으둜 μž‘μ„±λœ 경우, 전체 앱에 λŒ€ν•΄ 단일 루트λ₯Ό μƒμ„±ν•˜μ„Έμš”.

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

const root = createRoot(document.getElementById('root'));
root.render(<App />);

일반적으둜 이 μ½”λ“œλŠ” μ‹œμž‘ν•  λ•Œ ν•œ 번만 μ‹€ν–‰ν•˜λ©΄ λ©λ‹ˆλ‹€.

  1. HTML에 μ •μ˜λœ λΈŒλΌμš°μ € DOM λ…Έλ“œλ₯Ό μ°ΎμœΌμ„Έμš”.
  2. μ•± 내뢀에 React μ»΄ν¬λ„ŒνŠΈλ₯Ό ν‘œμ‹œν•˜μ„Έμš”.
<!DOCTYPE html>
<html>
  <head><title>My app</title></head>
  <body>
    <!-- This is the DOM node -->
    <div id="root"></div>
  </body>
</html>
import { createRoot } from 'react-dom/client';
import App from './App.js';
import './styles.css';

const root = createRoot(document.getElementById('root'));
root.render(<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>
  );
}

앱이 μ˜¨μ „νžˆ React만으둜 μž‘μ„±λœ 경우, μΆ”κ°€μ μœΌλ‘œ 루트λ₯Ό 더 λ§Œλ“€κ±°λ‚˜ root.renderλ₯Ό λ‹€μ‹œ ν˜ΈμΆœν•  ν•„μš”κ°€ μ—†μŠ΅λ‹ˆλ‹€.

이 μ‹œμ λΆ€ν„° ReactλŠ” 전체 μ•±μ˜ DOM을 κ΄€λ¦¬ν•©λ‹ˆλ‹€. μ»΄ν¬λ„ŒνŠΈλ₯Ό 더 μΆ”κ°€ν•˜λ €λ©΄ App μ»΄ν¬λ„ŒνŠΈ μ•ˆμ— μ€‘μ²©μ‹œν‚€μ„Έμš”. UI μ—…λ°μ΄νŠΈλŠ” 각 μ»΄ν¬λ„ŒνŠΈμ˜ Stateλ₯Ό 톡해 μˆ˜ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€. λͺ¨λ‹¬μ΄λ‚˜ 툴팁과 같은 μΆ”κ°€ μ½˜ν…μΈ λ₯Ό DOM λ…Έλ“œ 외뢀에 ν‘œμ‹œν•΄μ•Ό ν•˜λŠ” 경우 Portal둜 λ Œλ”λ§ν•˜μ„Έμš”.

HTML이 λΉ„μ–΄μžˆμœΌλ©΄, μ•±μ˜ μžλ°”μŠ€ν¬λ¦½νŠΈ μ½”λ“œκ°€ λ‘œλ“œλ˜κ³  싀행될 λ•ŒκΉŒμ§€ μ‚¬μš©μžμ—κ²Œ 빈 νŽ˜μ΄μ§€κ°€ ν‘œμ‹œλ©λ‹ˆλ‹€.

<div id="root"></div>

이것은 맀우 느리게 느껴질 수 μžˆμŠ΅λ‹ˆλ‹€! 이 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ μ„œλ²„μ—μ„œ λ˜λŠ” λΉŒλ“œ 쀑에 μ»΄ν¬λ„ŒνŠΈλ‘œλΆ€ν„° 초기 HTML을 생성할 수 μžˆμŠ΅λ‹ˆλ‹€. 그러면 λ°©λ¬ΈμžλŠ” μžλ°”μŠ€ν¬λ¦½νŠΈ μ½”λ“œκ°€ λ‘œλ“œλ˜κΈ° 전에 ν…μŠ€νŠΈλ₯Ό 읽고, 이미지λ₯Ό 보고, 링크λ₯Ό 클릭할 수 μžˆμŠ΅λ‹ˆλ‹€. 이 μ΅œμ ν™”λ₯Ό 기본적으둜 μˆ˜ν–‰ν•˜λŠ” ν”„λ ˆμž„μ›Œν¬λ₯Ό μ‚¬μš©ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€. μ‹€ν–‰ μ‹œμ μ— 따라 이λ₯Ό μ„œλ²„ μΈ‘ λ Œλ”λ§SSR λ˜λŠ” 정적 μ‚¬μ΄νŠΈ 생성SSG 이라고 ν•©λ‹ˆλ‹€.

μ„œλ²„ μΈ‘ λ Œλ”λ§μ΄λ‚˜ 정적 μ‚¬μ΄νŠΈ 생성을 μ‚¬μš©ν•˜λŠ” 앱은 createRoot λŒ€μ‹  hydrateRootλ₯Ό ν˜ΈμΆœν•΄μ•Ό ν•©λ‹ˆλ‹€. 그러면 ReactλŠ” DOM λ…Έλ“œλ₯Ό νŒŒκ΄΄ν•˜κ³  λ‹€μ‹œ μƒμ„±ν•˜λŠ” λŒ€μ‹  HTMLμœΌλ‘œλΆ€ν„° Hydrate(μž¬μ‚¬μš©)ν•©λ‹ˆλ‹€.


React둜 λΆ€λΆ„μ μœΌλ‘œ μž‘μ„±λœ νŽ˜μ΄μ§€ λ Œλ”λ§ν•˜κΈ° {/rendering-a-page-partially-built-with-react/}

νŽ˜μ΄μ§€κ°€ React만으둜 μž‘μ„±λ˜μ§€ μ•Šμ€ 경우, Reactκ°€ κ΄€λ¦¬ν•˜λŠ” 각 μ΅œμƒμœ„ UI에 λŒ€ν•œ 루트λ₯Ό μƒμ„±ν•˜κΈ° μœ„ν•΄ createRootλ₯Ό μ—¬λŸ¬ 번 ν˜ΈμΆœν•  수 μžˆμŠ΅λ‹ˆλ‹€. λ£¨νŠΈλ§ˆλ‹€ root.renderλ₯Ό ν˜ΈμΆœν•¨μœΌλ‘œμ¨ 각각 λ‹€λ₯Έ μ½˜ν…μΈ λ₯Ό ν‘œμ‹œν•  수 μžˆμŠ΅λ‹ˆλ‹€.

λ‹€μŒ μ˜ˆμ‹œμ—μ„œλŠ” μ„œλ‘œ λ‹€λ₯Έ 두 개의 React μ»΄ν¬λ„ŒνŠΈλ₯Ό index.html νŒŒμΌμ— μ •μ˜λœ 두 개의 DOM λ…Έλ“œμ— λ Œλ”λ§ν•©λ‹ˆλ‹€.

<!DOCTYPE html>
<html>
  <head><title>My app</title></head>
  <body>
    <nav id="navigation"></nav>
    <main>
      <p>This paragraph is not rendered by React (open index.html to verify).</p>
      <section id="comments"></section>
    </main>
  </body>
</html>
import './styles.css';
import { createRoot } from 'react-dom/client';
import { Comments, Navigation } from './Components.js';

const navDomNode = document.getElementById('navigation');
const navRoot = createRoot(navDomNode);
navRoot.render(<Navigation />);

const commentDomNode = document.getElementById('comments');
const commentRoot = createRoot(commentDomNode);
commentRoot.render(<Comments />);
export function Navigation() {
  return (
    <ul>
      <NavLink href="/">Home</NavLink>
      <NavLink href="/about">About</NavLink>
    </ul>
  );
}

function NavLink({ href, children }) {
  return (
    <li>
      <a href={href}>{children}</a>
    </li>
  );
}

export function Comments() {
  return (
    <>
      <h2>Comments</h2>
      <Comment text="Hello!" author="Sophie" />
      <Comment text="How are you?" author="Sunil" />
    </>
  );
}

function Comment({ text, author }) {
  return (
    <p>{text} β€” <i>{author}</i></p>
  );
}
nav ul { padding: 0; margin: 0; }
nav ul li { display: inline-block; margin-right: 20px; }

document.createElement()λ₯Ό μ‚¬μš©ν•˜μ—¬ μƒˆ DOM λ…Έλ“œλ₯Ό μƒμ„±ν•˜κ³  λ¬Έμ„œμ— μˆ˜λ™μœΌλ‘œ μΆ”κ°€ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€.

const domNode = document.createElement('div');
const root = createRoot(domNode);
root.render(<Comment />);
document.body.appendChild(domNode); // You can add it anywhere in the document

DOM λ…Έλ“œμ—μ„œ React 트리λ₯Ό μ œκ±°ν•˜κ³  이 νŠΈλ¦¬κ°€ μ‚¬μš©ν•˜λŠ” λͺ¨λ“  λ¦¬μ†ŒμŠ€λ₯Ό μ •λ¦¬ν•˜λ €λ©΄ root.unmountλ₯Ό ν˜ΈμΆœν•˜μ„Έμš”.

root.unmount();

이 κΈ°λŠ₯은 React μ»΄ν¬λ„ŒνŠΈκ°€ λ‹€λ₯Έ ν”„λ ˆμž„μ›Œν¬λ‘œ μž‘μ„±λœ μ•± 내뢀에 μžˆλŠ” κ²½μš°μ— 주둜 μœ μš©ν•©λ‹ˆλ‹€.


Updating a root component {/updating-a-root-component/}

같은 λ£¨νŠΈμ—μ„œ renderλ₯Ό 두 번 이상 ν˜ΈμΆœν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€. μ»΄ν¬λ„ŒνŠΈ 트리 ꡬ쑰가 이전 λ Œλ”λ§κ³Ό μΌμΉ˜ν•˜λŠ” ν•œ, ReactλŠ” κΈ°μ‘΄ Stateλ₯Ό μœ μ§€ν•©λ‹ˆλ‹€. λ‹€μŒ μ˜ˆμ‹œμ—μ„œ μž…λ ₯ 창에 μ–΄λ–»κ²Œ νƒ€μ΄ν•‘ν•˜λ“  관계없이, λ§€ 초 λ°˜λ³΅λ˜λŠ” render 호좜둜 μΈν•œ μ—…λ°μ΄νŠΈκ°€ μ•„λ¬΄λŸ° 문제λ₯Ό μΌμœΌν‚€μ§€ μ•ŠμŒμ„ μ£Όλͺ©ν•˜μ„Έμš”.

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

const root = createRoot(document.getElementById('root'));

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

renderλ₯Ό μ—¬λŸ¬ 번 ν˜ΈμΆœν•˜λŠ” κ²½μš°λŠ” ν”ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. μΌλ°˜μ μœΌλ‘œλŠ”, μ»΄ν¬λ„ŒνŠΈκ°€ Stateλ₯Ό μ—…λ°μ΄νŠΈν•©λ‹ˆλ‹€.

Show a dialog for uncaught errors {/show-a-dialog-for-uncaught-errors/}

By default, React will log all uncaught errors to the console. To implement your own error reporting, you can provide the optional onUncaughtError root option:

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

const root = createRoot(
  document.getElementById('root'),
  {
    onUncaughtError: (error, errorInfo) => {
      console.error(
        'Uncaught error',
        error,
        errorInfo.componentStack
      );
    }
  }
);
root.render(<App />);

The onUncaughtError option is a function called with two arguments:

  1. The error that was thrown.
  2. An errorInfo object that contains the componentStack of the error.

You can use the onUncaughtError root option to display error dialogs:

<!DOCTYPE html>
<html>
<head>
  <title>My app</title>
</head>
<body>
<!--
  Error dialog in raw HTML
  since an error in the React app may crash.
-->
<div id="error-dialog" class="hidden">
  <h1 id="error-title" class="text-red"></h1>
  <h3>
    <pre id="error-message"></pre>
  </h3>
  <p>
    <pre id="error-body"></pre>
  </p>
  <h4 class="-mb-20">This error occurred at:</h4>
  <pre id="error-component-stack" class="nowrap"></pre>
  <h4 class="mb-0">Call stack:</h4>
  <pre id="error-stack" class="nowrap"></pre>
  <div id="error-cause">
    <h4 class="mb-0">Caused by:</h4>
    <pre id="error-cause-message"></pre>
    <pre id="error-cause-stack" class="nowrap"></pre>
  </div>
  <button
    id="error-close"
    class="mb-10"
    onclick="document.getElementById('error-dialog').classList.add('hidden')"
  >
    Close
  </button>
  <h3 id="error-not-dismissible">This error is not dismissible.</h3>
</div>
<!-- This is the DOM node -->
<div id="root"></div>
</body>
</html>
label, button { display: block; margin-bottom: 20px; }
html, body { min-height: 300px; }

#error-dialog {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background-color: white;
  padding: 15px;
  opacity: 0.9;
  text-wrap: wrap;
  overflow: scroll;
}

.text-red {
  color: red;
}

.-mb-20 {
  margin-bottom: -20px;
}

.mb-0 {
  margin-bottom: 0;
}

.mb-10 {
  margin-bottom: 10px;
}

pre {
  text-wrap: wrap;
}

pre.nowrap {
  text-wrap: nowrap;
}

.hidden {
 display: none;
}
function reportError({ title, error, componentStack, dismissable }) {
  const errorDialog = document.getElementById("error-dialog");
  const errorTitle = document.getElementById("error-title");
  const errorMessage = document.getElementById("error-message");
  const errorBody = document.getElementById("error-body");
  const errorComponentStack = document.getElementById("error-component-stack");
  const errorStack = document.getElementById("error-stack");
  const errorClose = document.getElementById("error-close");
  const errorCause = document.getElementById("error-cause");
  const errorCauseMessage = document.getElementById("error-cause-message");
  const errorCauseStack = document.getElementById("error-cause-stack");
  const errorNotDismissible = document.getElementById("error-not-dismissible");

  // Set the title
  errorTitle.innerText = title;

  // Display error message and body
  const [heading, body] = error.message.split(/\n(.*)/s);
  errorMessage.innerText = heading;
  if (body) {
    errorBody.innerText = body;
  } else {
    errorBody.innerText = '';
  }

  // Display component stack
  errorComponentStack.innerText = componentStack;

  // Display the call stack
  // Since we already displayed the message, strip it, and the first Error: line.
  errorStack.innerText = error.stack.replace(error.message, '').split(/\n(.*)/s)[1];

  // Display the cause, if available
  if (error.cause) {
    errorCauseMessage.innerText = error.cause.message;
    errorCauseStack.innerText = error.cause.stack;
    errorCause.classList.remove('hidden');
  } else {
    errorCause.classList.add('hidden');
  }
  // Display the close button, if dismissible
  if (dismissable) {
    errorNotDismissible.classList.add('hidden');
    errorClose.classList.remove("hidden");
  } else {
    errorNotDismissible.classList.remove('hidden');
    errorClose.classList.add("hidden");
  }

  // Show the dialog
  errorDialog.classList.remove("hidden");
}

export function reportCaughtError({error, cause, componentStack}) {
  reportError({ title: "Caught Error", error, componentStack,  dismissable: true});
}

export function reportUncaughtError({error, cause, componentStack}) {
  reportError({ title: "Uncaught Error", error, componentStack, dismissable: false });
}

export function reportRecoverableError({error, cause, componentStack}) {
  reportError({ title: "Recoverable Error", error, componentStack,  dismissable: true });
}
import { createRoot } from "react-dom/client";
import App from "./App.js";
import {reportUncaughtError} from "./reportError";
import "./styles.css";

const container = document.getElementById("root");
const root = createRoot(container, {
  onUncaughtError: (error, errorInfo) => {
    if (error.message !== 'Known error') {
      reportUncaughtError({
        error,
        componentStack: errorInfo.componentStack
      });
    }
  }
});
root.render(<App />);
import { useState } from 'react';

export default function App() {
  const [throwError, setThrowError] = useState(false);

  if (throwError) {
    foo.bar = 'baz';
  }

  return (
    <div>
      <span>This error shows the error dialog:</span>
      <button onClick={() => setThrowError(true)}>
        Throw error
      </button>
    </div>
  );
}

Displaying Error Boundary errors {/displaying-error-boundary-errors/}

By default, React will log all errors caught by an Error Boundary to console.error. To override this behavior, you can provide the optional onCaughtError root option to handle errors caught by an Error Boundary:

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

const root = createRoot(
  document.getElementById('root'),
  {
    onCaughtError: (error, errorInfo) => {
      console.error(
        'Caught error',
        error,
        errorInfo.componentStack
      );
    }
  }
);
root.render(<App />);

The onCaughtError option is a function called with two arguments:

  1. The error that was caught by the boundary.
  2. An errorInfo object that contains the componentStack of the error.

You can use the onCaughtError root option to display error dialogs or filter known errors from logging:

<!DOCTYPE html>
<html>
<head>
  <title>My app</title>
</head>
<body>
<!--
  Error dialog in raw HTML
  since an error in the React app may crash.
-->
<div id="error-dialog" class="hidden">
  <h1 id="error-title" class="text-red"></h1>
  <h3>
    <pre id="error-message"></pre>
  </h3>
  <p>
    <pre id="error-body"></pre>
  </p>
  <h4 class="-mb-20">This error occurred at:</h4>
  <pre id="error-component-stack" class="nowrap"></pre>
  <h4 class="mb-0">Call stack:</h4>
  <pre id="error-stack" class="nowrap"></pre>
  <div id="error-cause">
    <h4 class="mb-0">Caused by:</h4>
    <pre id="error-cause-message"></pre>
    <pre id="error-cause-stack" class="nowrap"></pre>
  </div>
  <button
    id="error-close"
    class="mb-10"
    onclick="document.getElementById('error-dialog').classList.add('hidden')"
  >
    Close
  </button>
  <h3 id="error-not-dismissible">This error is not dismissible.</h3>
</div>
<!-- This is the DOM node -->
<div id="root"></div>
</body>
</html>
label, button { display: block; margin-bottom: 20px; }
html, body { min-height: 300px; }

#error-dialog {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background-color: white;
  padding: 15px;
  opacity: 0.9;
  text-wrap: wrap;
  overflow: scroll;
}

.text-red {
  color: red;
}

.-mb-20 {
  margin-bottom: -20px;
}

.mb-0 {
  margin-bottom: 0;
}

.mb-10 {
  margin-bottom: 10px;
}

pre {
  text-wrap: wrap;
}

pre.nowrap {
  text-wrap: nowrap;
}

.hidden {
 display: none;
}
function reportError({ title, error, componentStack, dismissable }) {
  const errorDialog = document.getElementById("error-dialog");
  const errorTitle = document.getElementById("error-title");
  const errorMessage = document.getElementById("error-message");
  const errorBody = document.getElementById("error-body");
  const errorComponentStack = document.getElementById("error-component-stack");
  const errorStack = document.getElementById("error-stack");
  const errorClose = document.getElementById("error-close");
  const errorCause = document.getElementById("error-cause");
  const errorCauseMessage = document.getElementById("error-cause-message");
  const errorCauseStack = document.getElementById("error-cause-stack");
  const errorNotDismissible = document.getElementById("error-not-dismissible");

  // Set the title
  errorTitle.innerText = title;

  // Display error message and body
  const [heading, body] = error.message.split(/\n(.*)/s);
  errorMessage.innerText = heading;
  if (body) {
    errorBody.innerText = body;
  } else {
    errorBody.innerText = '';
  }

  // Display component stack
  errorComponentStack.innerText = componentStack;

  // Display the call stack
  // Since we already displayed the message, strip it, and the first Error: line.
  errorStack.innerText = error.stack.replace(error.message, '').split(/\n(.*)/s)[1];

  // Display the cause, if available
  if (error.cause) {
    errorCauseMessage.innerText = error.cause.message;
    errorCauseStack.innerText = error.cause.stack;
    errorCause.classList.remove('hidden');
  } else {
    errorCause.classList.add('hidden');
  }
  // Display the close button, if dismissible
  if (dismissable) {
    errorNotDismissible.classList.add('hidden');
    errorClose.classList.remove("hidden");
  } else {
    errorNotDismissible.classList.remove('hidden');
    errorClose.classList.add("hidden");
  }

  // Show the dialog
  errorDialog.classList.remove("hidden");
}

export function reportCaughtError({error, cause, componentStack}) {
  reportError({ title: "Caught Error", error, componentStack,  dismissable: true});
}

export function reportUncaughtError({error, cause, componentStack}) {
  reportError({ title: "Uncaught Error", error, componentStack, dismissable: false });
}

export function reportRecoverableError({error, cause, componentStack}) {
  reportError({ title: "Recoverable Error", error, componentStack,  dismissable: true });
}
import { createRoot } from "react-dom/client";
import App from "./App.js";
import {reportCaughtError} from "./reportError";
import "./styles.css";

const container = document.getElementById("root");
const root = createRoot(container, {
  onCaughtError: (error, errorInfo) => {
    if (error.message !== 'Known error') {
      reportCaughtError({
        error,
        componentStack: errorInfo.componentStack,
      });
    }
  }
});
root.render(<App />);
import { useState } from 'react';
import { ErrorBoundary } from "react-error-boundary";

export default function App() {
  const [error, setError] = useState(null);

  function handleUnknown() {
    setError("unknown");
  }

  function handleKnown() {
    setError("known");
  }

  return (
    <>
      <ErrorBoundary
        fallbackRender={fallbackRender}
        onReset={(details) => {
          setError(null);
        }}
      >
        {error != null && <Throw error={error} />}
        <span>This error will not show the error dialog:</span>
        <button onClick={handleKnown}>
          Throw known error
        </button>
        <span>This error will show the error dialog:</span>
        <button onClick={handleUnknown}>
          Throw unknown error
        </button>
      </ErrorBoundary>

    </>
  );
}

function fallbackRender({ resetErrorBoundary }) {
  return (
    <div role="alert">
      <h3>Error Boundary</h3>
      <p>Something went wrong.</p>
      <button onClick={resetErrorBoundary}>Reset</button>
    </div>
  );
}

function Throw({error}) {
  if (error === "known") {
    throw new Error('Known error')
  } else {
    foo.bar = 'baz';
  }
}
{
  "dependencies": {
    "react": "19.0.0-rc-3edc000d-20240926",
    "react-dom": "19.0.0-rc-3edc000d-20240926",
    "react-scripts": "^5.0.0",
    "react-error-boundary": "4.0.3"
  },
  "main": "/index.js"
}

Displaying a dialog for recoverable errors {/displaying-a-dialog-for-recoverable-errors/}

React may automatically render a component a second time to attempt to recover from an error thrown in render. If successful, React will log a recoverable error to the console to notify the developer. To override this behavior, you can provide the optional onRecoverableError root option:

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

const root = createRoot(
  document.getElementById('root'),
  {
    onRecoverableError: (error, errorInfo) => {
      console.error(
        'Recoverable error',
        error,
        error.cause,
        errorInfo.componentStack,
      );
    }
  }
);
root.render(<App />);

The onRecoverableError option is a function called with two arguments:

  1. The error that React throws. Some errors may include the original cause as error.cause.
  2. An errorInfo object that contains the componentStack of the error.

You can use the onRecoverableError root option to display error dialogs:

<!DOCTYPE html>
<html>
<head>
  <title>My app</title>
</head>
<body>
<!--
  Error dialog in raw HTML
  since an error in the React app may crash.
-->
<div id="error-dialog" class="hidden">
  <h1 id="error-title" class="text-red"></h1>
  <h3>
    <pre id="error-message"></pre>
  </h3>
  <p>
    <pre id="error-body"></pre>
  </p>
  <h4 class="-mb-20">This error occurred at:</h4>
  <pre id="error-component-stack" class="nowrap"></pre>
  <h4 class="mb-0">Call stack:</h4>
  <pre id="error-stack" class="nowrap"></pre>
  <div id="error-cause">
    <h4 class="mb-0">Caused by:</h4>
    <pre id="error-cause-message"></pre>
    <pre id="error-cause-stack" class="nowrap"></pre>
  </div>
  <button
    id="error-close"
    class="mb-10"
    onclick="document.getElementById('error-dialog').classList.add('hidden')"
  >
    Close
  </button>
  <h3 id="error-not-dismissible">This error is not dismissible.</h3>
</div>
<!-- This is the DOM node -->
<div id="root"></div>
</body>
</html>
label, button { display: block; margin-bottom: 20px; }
html, body { min-height: 300px; }

#error-dialog {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background-color: white;
  padding: 15px;
  opacity: 0.9;
  text-wrap: wrap;
  overflow: scroll;
}

.text-red {
  color: red;
}

.-mb-20 {
  margin-bottom: -20px;
}

.mb-0 {
  margin-bottom: 0;
}

.mb-10 {
  margin-bottom: 10px;
}

pre {
  text-wrap: wrap;
}

pre.nowrap {
  text-wrap: nowrap;
}

.hidden {
 display: none;
}
function reportError({ title, error, componentStack, dismissable }) {
  const errorDialog = document.getElementById("error-dialog");
  const errorTitle = document.getElementById("error-title");
  const errorMessage = document.getElementById("error-message");
  const errorBody = document.getElementById("error-body");
  const errorComponentStack = document.getElementById("error-component-stack");
  const errorStack = document.getElementById("error-stack");
  const errorClose = document.getElementById("error-close");
  const errorCause = document.getElementById("error-cause");
  const errorCauseMessage = document.getElementById("error-cause-message");
  const errorCauseStack = document.getElementById("error-cause-stack");
  const errorNotDismissible = document.getElementById("error-not-dismissible");

  // Set the title
  errorTitle.innerText = title;

  // Display error message and body
  const [heading, body] = error.message.split(/\n(.*)/s);
  errorMessage.innerText = heading;
  if (body) {
    errorBody.innerText = body;
  } else {
    errorBody.innerText = '';
  }

  // Display component stack
  errorComponentStack.innerText = componentStack;

  // Display the call stack
  // Since we already displayed the message, strip it, and the first Error: line.
  errorStack.innerText = error.stack.replace(error.message, '').split(/\n(.*)/s)[1];

  // Display the cause, if available
  if (error.cause) {
    errorCauseMessage.innerText = error.cause.message;
    errorCauseStack.innerText = error.cause.stack;
    errorCause.classList.remove('hidden');
  } else {
    errorCause.classList.add('hidden');
  }
  // Display the close button, if dismissible
  if (dismissable) {
    errorNotDismissible.classList.add('hidden');
    errorClose.classList.remove("hidden");
  } else {
    errorNotDismissible.classList.remove('hidden');
    errorClose.classList.add("hidden");
  }

  // Show the dialog
  errorDialog.classList.remove("hidden");
}

export function reportCaughtError({error, cause, componentStack}) {
  reportError({ title: "Caught Error", error, componentStack,  dismissable: true});
}

export function reportUncaughtError({error, cause, componentStack}) {
  reportError({ title: "Uncaught Error", error, componentStack, dismissable: false });
}

export function reportRecoverableError({error, cause, componentStack}) {
  reportError({ title: "Recoverable Error", error, componentStack,  dismissable: true });
}
import { createRoot } from "react-dom/client";
import App from "./App.js";
import {reportRecoverableError} from "./reportError";
import "./styles.css";

const container = document.getElementById("root");
const root = createRoot(container, {
  onRecoverableError: (error, errorInfo) => {
    reportRecoverableError({
      error,
      cause: error.cause,
      componentStack: errorInfo.componentStack,
    });
  }
});
root.render(<App />);
import { useState } from 'react';
import { ErrorBoundary } from "react-error-boundary";

// 🚩 Bug: Never do this. This will force an error.
let errorThrown = false;
export default function App() {
  return (
    <>
      <ErrorBoundary
        fallbackRender={fallbackRender}
      >
        {!errorThrown && <Throw />}
        <p>This component threw an error, but recovered during a second render.</p>
        <p>Since it recovered, no Error Boundary was shown, but <code>onRecoverableError</code> was used to show an error dialog.</p>
      </ErrorBoundary>

    </>
  );
}

function fallbackRender() {
  return (
    <div role="alert">
      <h3>Error Boundary</h3>
      <p>Something went wrong.</p>
    </div>
  );
}

function Throw({error}) {
  // Simulate an external value changing during concurrent render.
  errorThrown = true;
  foo.bar = 'baz';
}
{
  "dependencies": {
    "react": "19.0.0-rc-3edc000d-20240926",
    "react-dom": "19.0.0-rc-3edc000d-20240926",
    "react-scripts": "^5.0.0",
    "react-error-boundary": "4.0.3"
  },
  "main": "/index.js"
}

문제 ν•΄κ²° {/troubleshooting/}

루트λ₯Ό μƒμ„±ν–ˆλŠ”λ° 아무것도 ν‘œμ‹œλ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€ {/ive-created-a-root-but-nothing-is-displayed/}

μ‹€μ œλ‘œ 앱을 λ£¨νŠΈμ— λ Œλ”λ§ν•˜λŠ” 것을 μžŠμ§€ μ•Šμ•˜λŠ”μ§€ ν™•μΈν•˜μ„Έμš”.

import { createRoot } from 'react-dom/client';
import App from './App.js';

const root = createRoot(document.getElementById('root'));
root.render(<App />);

root.render(...) λͺ…λ Ή μ—†μ΄λŠ” 아무것도 ν‘œμ‹œλ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.


I'm getting an error: "You passed a second argument to root.render" {/im-getting-an-error-you-passed-a-second-argument-to-root-render/}

A common mistake is to pass the options for createRoot to root.render(...):

Warning: You passed a second argument to root.render(...) but it only accepts one argument.

To fix, pass the root options to createRoot(...), not root.render(...):

// 🚩 Wrong: root.render only takes one argument.
root.render(App, {onUncaughtError});

// βœ… Correct: pass options to createRoot.
const root = createRoot(container, {onUncaughtError});
root.render(<App />);

"λŒ€μƒ μ»¨ν…Œμ΄λ„ˆκ°€ DOM μ—˜λ¦¬λ¨ΌνŠΈκ°€ μ•„λ‹™λ‹ˆλ‹€" λΌλŠ” 였λ₯˜κ°€ λ°œμƒν•©λ‹ˆλ‹€. {/im-getting-an-error-target-container-is-not-a-dom-element/}

This error means that whatever you're passing to createRoot is not a DOM node.

If you're not sure what's happening, try logging it:

const domNode = document.getElementById('root');
console.log(domNode); // ???
const root = createRoot(domNode);
root.render(<App />);

예λ₯Ό λ“€μ–΄ domNodeκ°€ null이면 getElementById κ°€ null을 λ°˜ν™˜ν–ˆμŒμ„ μ˜λ―Έν•©λ‹ˆλ‹€. μ΄λŠ” 호좜 μ‹œμ μ— λ¬Έμ„œμ— μ§€μ •λœ IDλ₯Ό κ°€μ§„ λ…Έλ“œκ°€ μ—†λŠ” κ²½μš°μ— λ°œμƒν•©λ‹ˆλ‹€. μ—¬κΈ°μ—λŠ” λͺ‡ κ°€μ§€ μ΄μœ κ°€ μžˆμ„ 수 μžˆμŠ΅λ‹ˆλ‹€.

  1. 찾고자 ν•˜λŠ” IDκ°€ HTML νŒŒμΌμ—μ„œ μ‚¬μš©ν•œ ID와 λ‹€λ₯Ό 수 μžˆμŠ΅λ‹ˆλ‹€. μ˜€νƒ€κ°€ μžˆλŠ”μ§€ ν™•μΈν•˜μ„Έμš”!
  2. λ²ˆλ“€μ˜ <script> νƒœκ·ΈλŠ” HTMLμ—μ„œ 그보닀 뒀에 μžˆλŠ” DOM λ…Έλ“œλ₯Ό "인식할" 수 μ—†μŠ΅λ‹ˆλ‹€.

λ˜λ‹€λ₯Έ 일반적인 μ‚¬λ‘€λŠ” createRoot(domNode) λŒ€μ‹  createRoot(<App />)으둜 μž‘μ„±ν–ˆμ„ κ²½μš°μž…λ‹ˆλ‹€.


"ν•¨μˆ˜κ°€ React μžμ‹μœΌλ‘œ μœ νš¨ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€" 였λ₯˜κ°€ λ°œμƒν•©λ‹ˆλ‹€. {/im-getting-an-error-functions-are-not-valid-as-a-react-child/}

이 였λ₯˜λŠ” root.render에 μ „λ‹¬ν•˜λŠ” 것이 React μ»΄ν¬λ„ŒνŠΈκ°€ μ•„λ‹˜μ„ μ˜λ―Έν•©λ‹ˆλ‹€.

이 였λ₯˜λŠ” <Component /> λŒ€μ‹  Component둜 root.renderλ₯Ό ν˜ΈμΆœν•  λ•Œ λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€.

// 🚩 Wrong: App is a function, not a Component.
root.render(App);

// βœ… Correct: <App /> is a component.
root.render(<App />);

λ˜λŠ” ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•œ κ²°κ³Ό λŒ€μ‹  root.render에 ν•¨μˆ˜ 자체λ₯Ό μ „λ‹¬ν–ˆμ„ λ•Œλ„ λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€.

// 🚩 Wrong: createApp is a function, not a component.
root.render(createApp);

// βœ… Correct: call createApp to return a component.
root.render(createApp());

μ„œλ²„μ—μ„œ λ Œλ”λ§λœ HTML이 μ²˜μŒλΆ€ν„° λ‹€μ‹œ μƒμ„±λ©λ‹ˆλ‹€ {/my-server-rendered-html-gets-re-created-from-scratch/}

앱이 μ„œλ²„μ—μ„œ λ Œλ”λ§λ˜κ³  React의 초기 HTML을 ν¬ν•¨ν•˜λŠ” κ²½μš°μ—, 루트λ₯Ό μƒμ„±ν•΄μ„œ root.renderλ₯Ό ν˜ΈμΆœν•˜λ©΄, λͺ¨λ“  HTML이 μ‚­μ œλ˜κ³  λͺ¨λ“  DOM λ…Έλ“œκ°€ μ²˜μŒλΆ€ν„° λ‹€μ‹œ μƒμ„±λ˜λŠ” 것을 λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€. μ΄λ ‡κ²Œ ν•˜λ©΄ 속도가 λŠλ €μ§€κ³ , ν¬μ»€μŠ€μ™€ 슀크둀 μœ„μΉ˜κ°€ μž¬μ„€μ •λ˜λ©°, κ·Έ λ°–μ˜ λ‹€λ₯Έ μ‚¬μš©μž μž…λ ₯듀이 손싀될 수 μžˆμŠ΅λ‹ˆλ‹€.

μ„œλ²„μ—μ„œ λ Œλ”λ§λœ 앱은 createRoot λŒ€μ‹  hydrateRootλ₯Ό μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€.

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

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

APIκ°€ λ‹€λ₯΄λ‹€λŠ” 점에 μœ μ˜ν•˜μ„Έμš”. 특히, μΌλ°˜μ μœΌλ‘œλŠ” root.renderλ₯Ό μ•„μ˜ˆ ν˜ΈμΆœν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.