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

버건디·2023년 6월 26일
1

리액트 공식 문서

목록 보기
4/4
post-thumbnail

원글 링크


hydrateRoot

hydrateRoot은 이전에 react-dom/server에 의해 생성된 HTML 컨텐츠를 가지고 있는 브라우저 DOM node 내부에 리액트 컴포넌트들을 보여질 수 있도록 합니다.

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

- 참조

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 관리를 인계합니다.

리액트로 만들어진 앱은 root 컴포넌트와 함께 보통 하나의 hydrateRoot을 가지고 있습니다.

파라미터들

  • domNode : 서버에서 루트 요소로서 렌더링 된 DOM 요소.
  • reactNode : 기존에 존재하는 HTML을 렌더링 하기 위해 사용되는 "리액트 노드". 이것은 일반적으로 renderToPipeableStream(<App />) 같은 ReactDOM Server메서드로 렌더링 된 App 컴포넌트 같은 JSX 조각들 입니다.
  • 선택적 options : 이 리액트 root을 위한 옵션들을 가진 객체입니다.
  1. 선택적 onRecoverableError : 에러들을 리액트가 자동으로 해결하려 할때 호출 되는 콜백 함수.
  2. 선택적 identifierPrefix : 리액트가 useId를 사용해서 만들기 위한 ID에 쓰이는 문자열 접두사. 다수의 같은 페이지의 root들을 사용할때 충돌을 피하기 위해 사용됩니다. 서버에서 사용되는 접두사와 같아야합니다.

반환 값

hydrateRootrenderunmount라는 두가지의 메서드 객체를 반환합니다.

주의 사항

  • hydrateRoot() 은 렌더링 된 컨텐츠가 서버에서 렌더링 된 컨텐츠들과 동일할 것이라고 예상합니다. 만약 불일치 하다면 버그로 취급하고 수정해야합니다.
  • 개발모드에서, 리액트는 하이드레이션 동안에 이 불일치에 대해서 경고를 띄웁니다. 이러한 불일치의 경우에서 차이점들을 자동으로 일치화 해준다는 보장이 없습니다. 이것은 성능의 이유에서 정말 중요합니다. 왜냐하면 대부분의 앱에서 불일치는 거의 없고 이를 검증하는데는 상당한 비용이 소모됩니다.
  • 보통 한개의 hydrateRoot을 앱 안에서 호출할 것입니다. 만약에 프레임워크를 사용한다면, 자동으로 불려집니다.
  • 이미 렌더링 된 HTML가 없는 클라이언트 렌더링된 앱이라면, hydrateRoot 은 지원되지 않습니다. 대신 createRoot()을 사용하세요.

- root.render(reactNode)

브라우저 돔요소를 위한 hydrate 된 리액트 루트 안에 있는 리액트 컴포넌트를 업데이트 하기 위해 root.render를 호출합니다

root.render(<App />);

리액트는 hydrate 된 root 안에 있는 App 컴포넌트를 업데이트 할 것입니다.

파라미터들

  • reatNode : "React node"는 당신이 업데이트 하고 싶은 노드입니다. 이는 보통 App 컴포넌트 같은 JSX 조각이지만, 당신은 createElement()로 만들어진 문자열이나 숫자, null이나 undefined 같은 리액트 요소들도 전달할 수 있습니다.

반환값

root.renderundefined를 반환합니다.

주의사항

  • root 가 hydration이 끝나기 전에 root.render를 호출한다면, 리액트는 이미 존재하고 있는 서버 사이드 렌더링된 HTML을 삭제하고 전체 요소들을 클라이언트 렌더링으로 전환합니다.

- root.unmount()

리액트 루트 안에 렌더링 된 트리를 제거하기 위해서 root.unmount를 호출하세요.

root.unmount();

리액트로 만들어진 앱은 보통 root.unmount를 호출하지 않습니다.

이것은 당신의 리액트 root DOM 노드가 만약ㅇ 다른 코드에 의해서 DOM으로부터 제거 된다 하면 유용합니다.

예를 들어서, DOM으로부터 비활성화 탭들을 제거하는 제이쿼리 탭 패널을 생각해봅시다.

만약에 탭이 제거 된다면, 그 안에 있는 (안에 있는 리액트 root도 포함하여) 내용물들이 DOM으로 부터 제거 될 것입니다.

당신은 root.unmount를 호출함으로써 제거된 루트의 컨텐츠들을 관리하는 것을 그만하도록 리액트에게 말할 수 있습니다.

그렇지 않으면, 제거된 루트 안에 있는 컴포넌트들은 구독과 같은 리소스들을 정리하지 않고 계속 둘 것입니다.

root.unmount를 호출하는 것은 트리 안에 있는 상태나 이벤트 핸들러들을 제거하는 것을 포함하여 root DOM 요소로부터 리액트를 분리 시키고 모든 컴포넌트들을 해제합니다.

파라미터들

root.unmount 는 파라미터를 받지 않습니다.

반환 값

rendernull 을 반환합니다.

주의사항들

  • root.unmount를 호출하는 것은 트리 안에 있는 모든 컴포넌트들을 언마운트 시키고, root DOM 노드로부터 리액트를 분리 시킵니다.

  • 만약 당신이 root.unmount를 호출한다면, 다시 root.render를 재호출 할수는 없습니다. 언마운트 된 루트에서 root.render를 호출하려고 시도하면 “Cannot update an unmounted root” 에러를 띄울 것 입니다.


- 사용법

서버 렌더링 된 HTML을 hydrating

만약에 당신의 앱의 HTML 이 서버측에서 생성됐다면, 당신은 이것을 클라이언트측에서 hydrate 해야합니다.

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

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

이것은 당신의 앱 안에 있는 리액트 컴포넌트와 browser DOM node 안에 있는 서버 HTML을 hydrate 할 것입니다.

대개로, 시작할때 한번 수행할것입니다. 만약에 프레임워크를 사용한다면, 이것은 백그라운드 환경에서 실행됩니다.

당신의 앱을 hydrate 하기 위해서, 리액트는 서버로부터 생성된 초기 HTML에 당신의 컴포넌트 로직을 "부착" 합니다.

Hydration 은 서버로부터 온 초기 HTML 스냅샷을 브라우저 안에서 작동되는 상호작용적인 완벽한 앱으로 변화 시킵니다.

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

- index.js

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

hydrateRoot(
  document.getElementById('root'),
  <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>
  );
}

당신은 더 많은 공간들에서 hydrateRoot을 호출할 필요가 없습니다. 이 시점에서, 리액트는 당신의 어플리케이션의 DOM을 관리합니다. UI를 업데이트 하기 위해서는 당신의 컴포넌트는 state를 사용해야합니다.

- 함정

hydrateRoot에 전달하는 리액트 트리는 서버에서 만들어내는 결과물과 같아야합니다.

이것은 사용자 경험에서 상당히 중요합니다.

사용자는 당신의 자바스크립트 코드가 로딩 되기 전에 서버에서 생성된 HTML을 바라보는데에 시간을 사용할 것입니다.

서버 사이드 렌더링은 결과물의 HTML 스냅샷을 보여줌으로써 앱이 실제 로딩되는 속도보다 더 빨리 로딩 되는 것처럼 보이게 합니다.

갑자기 다른 결과물을 보여줌으로써 더 빨리 로딩 되는 것처럼 보이게 하는것을 깨뜨립니다.

이것이 바로 왜 서버사이드 렌더링 된 결과물이 클라이언트 측에서 초기에 렌더링 되는 결과물과 일치해야하는 이유입니다.

이러한 hydration 오류를 일으키는 흔한 원인들은 다음과 같습니다.

  • 루트 노드 안에 리액트로 생성된 HTML 주변에 추가 공백이 존재합니다.
  • 당신의 렌더링 로직 안에 typeof window !== "undefined" 같은 식으로 체크하는 경우
  • window.matchMedia 같은 브라우저에서만 사용되는 API를 렌더링 로직 안에서 사용했을 경우
  • 서버와 클라이언트가 서로 다른 데이터를 렌더링 하는 경우

리액트는 몇몇 hydration 에러로부터 해결을 하지만, 하지만 위와 같은 다른 버그들은 꼭 해결해야합니다.

속도가 저하되거나, 최악의 경우에서는 이벤트 핸들러가 다른 요소에 연결 될 수 있습니다.


- 전체 문서를 Hydrating 하기

리액트로 만들어진 앱은 html 태그를 포함해서 JSX 같은 전체 문서를 렌더링 할 수 있습니다.

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

전체 문서를 hydrate를 하기 위해서는, hydrateRoot 의 첫번째 인자로서 document를 전달합니다.

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

hydrateRoot(document, <App />);

- 불가피한 피할수 없는 hydration 불일치 에러들

만약 단일 요소 속성이나 문구 컨텐츠가 불가피하게 서버와 클라이언트 사이에서 불일치가 일어난다면,

이 불일치 경고 문구를 끌 수 있습니다.

hydration 경고 문구를 끄고 싶다면, suppressHydrationWarning={true}로 설정하세요 .

- index.html

<!--
  HTML content inside <div id="root">...</div>
  was generated from App by react-dom/server.
-->
<div id="root"><h1>Current Date: <!-- -->01/01/2020</h1></div>

- index.js

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

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

- App.js

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

이것은 한단계 더 깊은 단계에서 작동하며, 임시 방편으로 사용됩니다. 남용하지마세요.

텍스트 컨텐츠가 아니라면, 리액트는 패치를 시도하지 않으므로 후에 업데이트까지 일관성이 유지되지 않을 수 있습니다.


- 서버 컨텐츠와 클라이언트 컨텐츠의 다름 조작

만약 당신이 의도적으로 서버와 클라이언트 사이에서 다르게 렌더링 해야할 필요가 있다면, 당신은 두개의 렌더링을 수행할 수 있습니다.

클라이언트 측에서 무언가 다르게 렌더링하는 컴포넌트들은 isClient 같은 상태 변수들을 읽을 수 있으며, Effect 내에서 true로 변화시킬 수 있다.

index.html

<!--
  HTML content inside <div id="root">...</div>
  was generated from App by react-dom/server.
-->
<div id="root"><h1>Is Server</h1></div>

index.js

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

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

App.js

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 렌더링보다 늦게 로드 될수 있으므로 hydration 후에 다른 UI를 렌더링 하는것은 사용자에게 불편함을 느끼게 할 수 있ㅅㅂ니다.


hydrate 된 root 컴포넌트 업데이트하기

root 가 hydrating 이 끝나면, 당신은 리액트 루트 컴포넌트를 업데이트 하기 위해 root.render 메서드를 호출할 수 있습니다. createRoot와 다르게, 이미 초기에 HTML로써 컨텐츠가 생성되었기 때문에 이것을 할필요가 없습니다.

만약 당신이 hydration 후에 어떤 시점에 root.render를 호출하고, 컴포넌트 트리 구조가 이전에 렌더링 된 요소들과 일치한다면, 리액트는 이 state 를 보존합니다.

어떻게 당신이 인풋안에 값을 입력하는지 알아둬야합니다. 이것은 매초마다 예세어 보여지는 것처럼 render 메서드를 호출해서 업데이트 하는것은 파괴적이지 않습니다.

index.html

<!--
  All HTML content inside <div id="root">...</div> was
  generated by rendering <App /> with react-dom/server.
-->
<div id="root"><h1>Hello, world! <!-- -->0</h1><input placeholder="Type something here"/></div>

index.js

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

App.js

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

hydrate 된 root 에서 root.render를 호출하는 것은 흔하지 않습니다. 대개로, 당신은 대신에 컴포넌트들 안에서 상태를 업데이트 합니다.

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

0개의 댓글