[React] hydration이란? (+ root란?)

devAnderson·2022년 4월 13일
9

TIL

목록 보기
87/103

🚍 0. 조금 부끄러운 이유긴 한데...

최근 react 18 관련 자료들과 블로깅을 보면서, 나는 hydration이라는 개념이 아예 리엑트에 내장된... 무언가 랜더링 프로세스중에 한가지가 새로 추가된 것이라고 착각하고 있었다만, 그런게 전혀 아니었고 리엑트가 제공하는 기능중에 하나였었는데 혼자 오해해서 신나게 블로깅했던 기억을 무마하며... 조금 더 보완설명 및 최근에 react18에서 공식 업데이트된 root가 무엇인지 더하는 의미로 써본다

🚍 1. 그래서, hydration이 뭔데?

공식 문서에는 항상 답이 있다.
hydration에 대해서 이야기하려면 우선 너무나도 친숙한, "index" 파일에 존재하던 React.render에 대해서 말해봐야 할 것이다.

위에서 말해주는 것처럼, render 메서드는 DOM에 있는 타겟 (id="root"로 설정되던 그것) 에다가 React 엘리먼트를 랜더링하는 역할을 한다. (그리고 나서 이 삽입된 엘리먼트를 추적하기 위한 참조인 ref가 반환된다고 한다. 공식문서상으론 해당 ref를 직접적으로 컨트롤하는 것을 지양하라고 말한다)

그 이후, 혹은 업데이트가 되어 기존에 업데이트된 노드 콘텐츠가 존재한다면 diffing 알고리즘을 이용해서 비교한 후 업데이트를 한다고 한다.

그런데 여기서 경고문구가 하나 있다.

서버에서 랜더링한 컨테이너에 이벤트를 보충할 때에는 render가 아닌 hydrate을 사용하라고?

그렇다. 여기서부터 나는 뭔가 잘못 이해하고 있음을 알게 된 것이다.
hydrate은 일반적인 render을 통해서 CSR을 할때 실행되는 내용이 아니다.
직접적으로 "hydrate"이라는 메서드를 사용해서 SSR으로 받아온 문서에 이벤트를 보충할 때 사용하는 것이었던 것이다!

🚍 2. hydration의 구조

hydrate이란 간단하게 설명하면 "이벤트 핸들러 함수들" 을 정적인 DOM 노드들에 붙여서 동적으로 상호작용이 가능하도록 바꾸어주는 기능이다.

이제까지 CSR의 가장 큰 문제점이라고 한다면 초기에 커다란 js 파일을 전달받은 후 DOM을 조작하여 컴포넌트들을 랜더링하는 과정으로 인한 초기 페이지의 보이는 속도가 느리다는 단점이 있었다.

SSR의 경우라면 완전히 랜더링이 완료된 html 문서를 전달하여 해당 내용을 그대로 브라우저가 랜더링하는 방식을 택했지만 새로운 화면으로 이동할 때마다 서버에 요청을 한 후에 새 페이지를 랜더링했기 때문에 깜빡거림이 존재할 수밖에 없었다. (물론 이 한계점을 막기 위해서 요즘에는 새로운 페이지를 받기 전까지 기존 페이지를 보여주는 형태로 발전했다고 한다)

여튼, CSR과 SSR의 장점을 모두 가져오기 위해서, 서버로부터는 미리 랜더링이 된 html을 전달하고, 동시에 동적인 이벤트와 관련한 JS 파일도 번들링한 후 둘 다 전달하는 방식으로 진보하게 되었다.

이 때에 전달받은 html은 정적이기 떄문에 전달받은 js 파일과 연동시키는 과정이 필요하게 되었는데 이 과정을 hydration이라고 하는 것이다.

이 hydrate 기능을 react16부터 제공하는 "React.hydrate"을 통해 할 수 있다.

🚍 3. root?

react18로 넘어오면서, 이제는 createRoot를 이용한 랜더링이 완전히 고착되었다.
이로 인해 hydration 도 조금 바뀌게 되어 추가하는 내용이다.

여튼 그렇다면 이렇게 바꾼 이유를 알아야 할 것이다.

리엑트에서 "root"란, 리엑트 앱이 엘레멘트를 추가하고 추적하는 최상위 레벨의 데이터 구조이다.

import * as ReactDOM from 'react-dom';
import App from 'App';

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

// Initial render.
ReactDOM.render(<App tab="home" />, container);

// During an update, React would access
// the root of the DOM element.
ReactDOM.render(<App tab="profile" />, container);

그런데 리엑트 팀이 생각하기를,

어차피 루트는 이미 정해져 있고 변동하지 않으므로 계속 그것을 쓰게 만드면 되는데 매번 업데이트때마다 render 함수에게 이거를 전달해줘야 할 필요가 있을까?

라고 판단했다고 한다.

그래서 새로운 버전으로

import Reac from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

const root = ReactDOM.createRoot(document.getElementById("root"));

root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

이렇게 바꾸었다고 한다.
root를 미리 변수로 설정해서 createRoot 메서드의 인자로 전달하면서 렉시컬 환경에 정의하고, root에 존재하는 render 메서드를 이용해 이미 등록된 root를 계속해서 사용하겠다는 의지로 보인다.

해당 내용으로 변동함으로써 또 다른 이점을 얻었다고 하는데 그것이 바로 기존 ReactDom의 메서드로 존재했던 "hydrate" 함수를 제거하고 createRoot가 리턴하는 객체의 메서드로 포함시켰고,

partial hydration과 어울리지 않는 쓸데없는 3번째 인자인 callback 함수를 제거했다고 한다.

import * as ReactDOMClient from 'react-dom/client';

import App from 'App';

const container = document.getElementById('app');

// Create *and* render a root with hydration.
const root = ReactDOMClient.hydrateRoot(container, <App tab="home" />);
// Unlike with createRoot, you don't need a separate root.render() call here

해당 메서드를 제거한 이유는, 개발자가 기대하는 컴포넌트가 랜더링 된 후의 callback의 실행 시점이 부분적인 hydration을 진행하는 SSR의 개념에서 정확하게 실행 시점이 이러하다고 보장할 수 없기 때문이라고 한다.

4. hydration의 가능성

이제 이미 랜더링이 된 html 파일을 받고, 이벤트 헨들러와 관련된 js 파일을 hydration을 통해서 연동시킴으로서 더 빠른 "First paint"를 실현시켰다고 할 수 있다만 이것에는 또 다른 고려점이 존재한다.

그것은 바로 고질적인 SSR의 문제점이었던,

화면에 내용은 보이는데 클릭해도 아무것도 안일어나요

라는 문제점이다.

즉, Time to Interactive에 심각한 문제점을 일으키기 때문에 기존의 폭포수 방식 랜더링에 비교해서 더 나쁜 유저경험을 선사해줄 가능성이 존재한다.

이것을 해결하기 위해, react18에서 추가로 업데이트한 내용이 바로 선택적인 hydration 기술이다.

전에 언급되었던 Suspense를 통해 랜더링이 오래걸리는 컴포넌트를 감싸서 독립적으로 hydration을 시킬수는 있었지만, 이 컴포넌트의 hydration 과정으로 인해 이미 랜더링은 완료되고 hydration만 기다리고 있는 다른 컴포넌트들의 interaction이 영향받는 문제가 생겼지만,

react18의 선택적인 hydration을 통해 사용자가 클릭을 할 경우, hydration의 우선순위가 Suspense로 감싸져 오래걸리는 컴포넌트가 아닌 클릭 대상 컴포넌트로 옮겨지면서 먼저 hydration되게 되므로 interaction이 손쉽게 이루어질 수 있게 되었다.

🚍 결론

진짜 적어놓다 보니 내가 필요할 것 같아서 요약을 하자면

  1. react의 hydration은 자동기능이 아니라 SSR을 할 때에 굳이 render트리를 만들지 않고 기존에 형성된 DOM에 이벤트만 붙이면 되므로 해당 과정을 실행하는 메서드이다.

  2. react 의 Root 개념이 업데이트되면서, 이제는 createRoot 메서드를 호출하여 만들어지는 클로져 환경에 root를 등록하고 리턴되는 객체의 메서드에 존재하는 hydrate관련 함수를 이용해 SSR로 전달받은 html에 이벤트 핸들러를 붙여줄 수 있게 되었다.

profile
자라나라 프론트엔드 개발새싹!

0개의 댓글