[리액트 공식 문서 번역] createRoot

버건디·2023년 2월 16일
1

리액트 공식 문서

목록 보기
2/4

원 글 링크

createRoot

createRoot 은 당신이 브라우저 DOM 노드 안에 리액트 컴포넌트들을 보여주도록 하기 위해서 root를 만들수 있도록 합니다.

const root = createRoot(domNode, options?)

Reference

createRoot(domNode, options?)

브라우저 DOM 요소 안에 보여질 컨텐츠를 담는 React root를 만들기 위해서 createRoot 을 호출하세요.

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

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

리액트는 domNode를 위해서 root를 만들것이고, 그 안에 있는 DOM을 관리합니다.

당신이 root를 만든 후에는, root.render 메서드를 호출해서 그 안에 리액트 컴포넌트를 보여지도록 하면 됩니다.

root.render(<App />);

리액트로 빌드된 어플리케이션은 대개로 root 컴포넌트를 위한 하나의 createRoot을 가지고 있습니다.

페이지의 여러 부분들을 위해서 리액트의 "sprinkles"를 사용하는 페이지는 필요하다면 여러개의 root를 가질수도 있습니다. (아래쪽에 설명 있음)

매개변수들

  • domNode : DOM 요소입니다. 리액트는 이 DOM 요소를 위한 root를 만들것이고 너가 렌더된 리액트 컨텐츠를 보여지도록 하기 위해서 render 메서드 같은 함수를 사용할수 있도록 합니다.

  • options : 리액트 root를 위한 옵션 객체입니다.

  1. optional onRecoverableError : 리액트가 에러로부터 자동으로 복구할때 호출되는 콜백함수입니다.

  2. optional identifierPrefix : 리액트가 useId에 의해 생성된 ID에 사용하는 접두사입니다. 같은 페이지에 여러개의 roots들이 존재할때 충돌을 피하기 위해서 유용합니다.

반환값

createRoot은 render 메서드와 unmount메서드를 가지고 있는 객체를 반환합니다.

주의 사항

  • 만약에 당신의 앱이 서버에서 렌더링 된다면, createRoot은 지원되지 않습니다. 대신에 hydrateRoot 을 사용하세요.

  • 당신의 앱 안에서는 하나의 createRoot만 가질 가능성이 있습니다. 당신이 만약 프레임 워크를 사용한다면 이것을 호출하도록 하세요.

  • 당신이 만약 당신의 컴포넌트의 자식이 아닌 DOM tree의 다른 부분이 있는 JSX를 렌더하고 싶다면, (예를 들어서 모달창이나 tooltip 같은) createRoot 보다는 createPortal을 사용하세요.


root.render(reactNode)

리액트 root 의 브라우저 DOM 노드 안에 JSX를 보여지도록 하기 위해서 root.render를 호출하세요.

root.render(<App />);

리액트는 root 안에서 App 컴포넌트를 보여지도록 할 것이고, 안에서 DOM을 조작할 것입니다.

매개변수들

  • reactNode : 당신이 보여지도록 하고픈 리액트 Node 입니다. 보통 안에 들어가는 리액트 Node는 App같은 JSX 조각이 됩니다.
    그러나 문자열, 숫자, null 이나 undefined 같은 createElement 메서드로 구성되어진 리액트 요소도 전달할 수 있습니다.

반환값

root.render는 undefined를 반환합니다. => ???

주의 사항

  • 당신이 만약 root.render 메서드를 처음으로 호출했을때, 리액트는 컴포넌트를 렌더링 하기 전에 root 요소 안에 있는 모든 HTML 컨텐츠들을 지웁니다.

  • 만약 당신의 root의 DOM 노드가 서버사이드 렌더링으로 인해 서버쪽에서 생성된 HTML을 포함 하고 있다면, hydrateRoot 메서드를 사용하세요. 이것은 기존에 존재하던 HTML에 이벤트 핸들러들을 부착해줄 것입니다.

  • 당신이 만약에 같은 root 에서 한번 이상 render 메서드를 호출한다면, 리액트는 당신이 전달한 최신 JSX를 반영하기 위해서 필요한 만큼 DOM을 렌더링할 것입니다.
    리액트는 전에 렌더링 된 tree와 비교해서 다시 재사용 될수 있는 부분이 어떤 부분인지, 다시 만들어야하는 부분은 또 어떤 부분인지 결정합니다.
    같은 root 안에서 render 메서드를 다시 호출하는 것은 root 컴포넌트에서 set 함수를 호출하는 것과 비슷합니다.
    리액트는 불필요한 DOM 업데이트를 피합니다.


root.unmount()

root.unmount는 리액트 root 안에서 렌더된 트리를 제거하기 위해서 호출합니다.

root.unmount()

리액트로 빌드된 앱은 대개 root.unmount를 호출할 필요가 없습니다.

하지만 이 함수는 혹시나라도 당신의 root DOM 노드가 다른 코드에 의해서 DOM에서 제거될수 있을 가능성이 있을때, 매우 유용합니다.

예를 들어서, DOM으로부터 활성화 되지 않은 tab들을 제거하는 제이쿼리 tab 패널을 상상해보세요.

만약에 tab이 제거되어진다면, DOM 으로부터 그 안에 있는, 리액트 root를 포함해서 모든것들이 삭제 될 것입니다.

이런 경우에, root.unmount를 호출함으로써 리액트에게 삭제된 root 컨텐츠 관리를 그만하라고 말할 필요가 있습니다.

그렇지 않으면, 제거된 root 안에 있는 컴포넌트들은 subscription 같은 전역적인 리소스들을 청소하고, 해제하는 것을 알지 못합니다.

root.unmount는 root DOM 노드로부터 리액트를 분리시키고, root 안에 있는 모든 컴포넌트들을 unmount 시킵니다. 심지어 트리 안에 state나 이벤트 핸들러들을 삭제하는 것을 포함해서요.

매개변수

root.unmount는 매개변수를 따로 받지 않습니다.

리턴값

root.unmount는 undefined를 리턴합니다.

주의 사항

  • root.unmount는 root DOM 노드로부터 리액트를 분리 시키고, tree 안에 있는 모든 컴포넌트들을 unmount 시킵니다.

  • 당신이 만약 한 root 안에서 root.unmount를 호출했다면, root.render 를 호출할 수 없습니다.
    unmount 된 root 에서 root.render를 호출하려 한다면, "Cannot update an unmounted root” 에러가 띄어질 것입니다.
    그러나, 전의 root에 대한 node 가 unmount 된 후에 같은 DOM에 관한 새로운 root를 만들 수 있습니다.


    - 사용법

- 리액트로 만들어진 앱 렌더링 하기

당신이 만약 리액트로 앱을 만든다면, 전체 어플리케이션에 관한 단일 root를 만들어야 합니다.

import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);

대개로 당신은 시작할때 한번 딱 이 코드를 실행시킵니다. 이 코드는

  1. 당신의 HTML 안에서 정의된 브라우저 DOM 노드를 찾습니다.
  2. 당신의 app 안에서 리액트 컴포넌트를 보여지도록 합니다.

- index.html

<!DOCTYPE html>
<html>
  <head><title>My app</title></head>
  <body>
    <!-- This is the DOM node -->
    <div id="root"></div>
  </body>
</html>

- index.js

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

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

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

당신이 만약 리액트로 개발을 하고 있다면, 더 많은 root 를 만들 필요도 없고 root.render를 다시 호출할 필요도 없습니다.

이러한 부분들로 비추어보아서, 리액트는 당신의 전체 어플리케이션의 DOM을 관리합니다.

더 많은 컴포넌트들을 추가하기 위해서, App 컴포넌트 안에 그들을 중첩 시키세요.

당신이 만약 UI를 업데이트 해야 한다면, 당신의 각각 컴포넌트들은 state를 사용함으로써 업데이트 할 수 있습니다.

당신이 DOM 노드 밖에서 modal 이나 tooltip같은 외부의 컨텐츠를 렌더링 하고싶다면, portal을 사용하세요.

알아두어야 할 점

당신의 HTML이 비어있을때, 사용자는 자바스크립트 코드들이 로드되고 실행되기 전까지 빈 페이지를 보아야 합니다.

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

사용자 입장에서 이건 매우 느리게 느껴질수 있습니다.

이러한 문제를 해결하기 위해서 당신은 빌드되는 동안 컴포넌트들이 서버쪽에서 렌더링 되어서 이미 완성된 초기 HTML을 생성할 수 있습니다.

그러면 당신의 페이지 방문자는 자바스크립트 코드가 실행되기전에 텍스트나, 이미지들, 링크들을 볼 수 있습니다.

우리는 이러한 최적화를 위해서 프레임워크를 사용하는것을 추천합니다.

실행 시점에 따라서 이것을 서버사이드렌더링이나 SSG 라고 부릅니다.

주의할점

SSR 이나 SSG를 사용하는 어플리케이션들은 createRoot 대신에 hydrateRoot을 호출해야합니다.

리액트는 당신의 HTML로부터 DOM 노드들을 제거하는 것이 아닌 hydrate하고 다시 그들을 재생성 합니다.


리액트로 만들어진 페이지를 부분적으로 렌더링 하기

당신의 페이지가 만약에 리액트로 완전하게 만들어지지 않았다면,

당신은 createRoot을 여러번 호출해서 각각에 최상단 UI를 만들어 관리 할 수 있습니다.

당신은 root.render 를 호출해서 각각의 root에서 다른 컨텐츠들이 보여지도록 할 수 있습니다.

여기 index.html 파일에서 2개의 DOM 노드들이 렌더링 된 다른 리액트 컴포넌트를 볼수 있습니다.

- index.html

<nav id="navigation"></nav>
<main>
  <p>This paragraph is not rendered by React (open index.html to verify).</p>
  <section id="comments"></section>
</main>

- index.js

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

- Components.js

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

당신은 또한 document.createElement() 를 사용함으로써 새로운 DOM 노드를 만들고 그것을 document에 추가할 수 있습니다.

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 노드로부터 리액트 tree를 제거하고 모든 리소스들을 정리하기 위해서, root.unmount를 사용하십시요.

root.unmount()

이것은 컴포넌트가 다른 프레임워크 안에서 만들어진 앱 내부에 있는 경우 매우 유용합니다.


root 컴포넌트 업데이트

당신은 같은 root 에서 여러번 render 메서드를 호출할 수 있습니다.

컴포넌트 tree 구조가 전에 렌더링된 구조와 일치하는 한, 리액트는 state를 보존합니다.

밑의 예에서 매초마다 render 메서드가 반복되어서 불려지지만 이것은 당신이 인풋창 안에 값을 입력하는 것에 영향을 끼치지 않습니다.

- index.js

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

- App.js

export default function App({counter}) {
  return (
    <>
      <h1>Hello, world! {counter}</h1>
      <input placeholder="Type something here" />
    </>
  );
}

render 메서드를 여러번 호출하는것은 일반적이진 않습니다. 대개로 컴포넌트 안에 있는 state를 업데이트 합니다.


Troubleshooting

root 를 만들었는데, 아무것도 보이지 않습니다.

root 안에 실제적으로 당신의 app을 렌더링 하는 것을 잊은건지 확인해볼 필요가 있습니다.

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

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

이러한 경우에는 아무것도 보이지 않습니다.

“Target container is not a DOM element” 에러

이 에러는 createRoot 에 전달하는 값이 DOM node 가 아닐때 발생합니다.

왜 에러가 발생하는지 모르겠다면, 콘솔창 로그를 남겨보세요.

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

예를 들어서, domNode 가 null 이라면, getElementById가 null 값을 리턴하는 것을 의미합니다.

만약에 당신이 호출할 당시에 id 값이 주어진 document안에 아무런 node도 존재하지 않는다면 null 값을 리턴하는데, 여기 몇가지의 이유가 있습니다.

  1. HTML 파일에서 작성한 ID값과 호출한 ID값이 달라서 일 수 있습니다. 확인해보세요
  2. script 태그는 HTML뒤에 나타나는 DOM 노드들을 볼 수 없습니다 .

만약 작동하지 않는다면, Adding React to a Website 이 페이지를 확인해보세요.

이 에러를 잡기 위한 다른 방법으로는 createRoot(domNode) 가 아니라 createRoot(<App컴포넌트/>)로 작성하는 방법이 있습니다.


“Functions are not valid as a React child.” 에러

이 에러는 root.render에 전달하는 것이 리액트 컴포넌트가 아닐때 발생합니다.

이것은 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());

만약 작동하지 않는다면, Adding React to a Website 이 페이지를 확인해보세요.


서버에서 렌더링 된 HTML이 다시 재생성 됩니다.

만약 당신의 어플리케이션이 서버사이드 렌더링이 되어서 초기 생성된 HTML이 존재한다면,

root.render를 호출하는 것은 모든 HTML을 지우고 모든 DOM node들을 재생성 하는 방식이란 것을 알아야합니다.

이러한 방식은 느리고, 모든 설정값들을 초기화하고 다른 사용자의 인풋값을 삭제시킬수도 있습니다 .

서버에서 렌더링 된 app들은 createRoot 이 아닌 hydrateRoot을 사용해야 합니다 .

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

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

API가 다르다는 점에 유의해야 합니다. 특히 이러한 경우에는 root.render 호출을 하면 안됩니다.


- 알아두어야 할 점

root.render는 undefined를 반환합니다. => ???

root.render는 실질적으로 컴포넌트를 렌더링하는 부분인데, 반환값이 undefined라는 것이 이해가 가지 않았었다.

root.render 가 사실 REACTDOM.render 안에 있는 내부 메서드이고, 실제로 DOM 노드를 렌더링 하는것은 REACTDOM.render가 처리하는 것이기 때문에 root.render의 반환값은 undefined가 되는것이었다.

profile
https://brgndy.me/ 로 옮기는 중입니다 :)

0개의 댓글