React DOM의 render와 hydrate

next를 사용하던 도중 SSR(Server Side Rendering)에서 사용되는 hydrate라는 키워드에 대해 공부하고 싶어져서 공부해보았다.

react-dom은 앱의 최상위 레벨에서 사용하는 DOM과 관련한 메서드와 React 모델 외부로 나갈 수 있는 해결책을 제공한다.

cra로 만든 프로젝트 기준 최상위 레벨인 index.js에서 사용된다.
render 메소드를 제공해주는 그 패키지이다.

1
2
3
4
5
6
7
8
9
import React from "react";
import ReactDOM from "react-dom";
import App from "./APP";

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

render

1
ReactDOM.render(element:React$Element<any>,container:Container,callback:?Function,)

renderelementcontainer DOM에 렌더링하고 컴포넌트에 대한 참조(Ref)를 반환한다.

callback이 제공되면 렌더링되거나 업데이트 된 후에 실행된다.

element가 이전에 container내부에 렌더링 되었다면 해당 엘리먼트는 업데이트하고 최신의 React 엘리먼트를 반영하는 데 필요한 DOM만 변경한다.

일전에 공부하였던 diff 알고리즘React fiber를 통하여 DOM에 변경점만 업데이트 하거나 렌더링하는 함수이다.

코드

ReactDOM에서 render 함수를 볼 수 있다.

동작방식은 다음과 같다.

  1. container가 DOM 엘리먼트인지 확인하고 아닐경우 에러를 발생시킨다.

  2. root 엘리먼트는 _reactRootContainer 속성을 가지고 있다.
    또한 container는 __reactContainere+$랜덤 문자열 속성도 가지고 있다.
    다음 경우 에러를 발생시킨다.

    1. container__reactRootContainer 속성이 undefined일 경우
    2. container 내부에 __reactContainerer+$랜덤 문자열 속성이 존재하지 않을 경우
  3. 2까지 에러가 발생하지 않았을 경우 DOM 엘리먼트인 containersub tree로 React 엘리먼트인 element를 render한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
export function render(
element: React$Element<any>,
container: Container,
callback: ?Function
) {
if (__DEV__) {
console.error(
"ReactDOM.render is no longer supported in React 18. Use createRoot " +
"instead. Until you switch to the new API, your app will behave as " +
"if it's running React 17. Learn " +
"more: https://reactjs.org/link/switch-to-createroot"
);
}

if (!isValidContainerLegacy(container)) {
throw new Error("Target container is not a DOM element.");
}

if (__DEV__) {
const isModernRoot =
isContainerMarkedAsRoot(container) &&
container._reactRootContainer === undefined;
if (isModernRoot) {
console.error(
"You are calling ReactDOM.render() on a container that was previously " +
"passed to ReactDOM.createRoot(). This is not supported. " +
"Did you mean to call root.render(element)?"
);
}
}
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback
);
}

주의사항

공식문서에 나와있는 주의사항은 다음과 같다.

  1. ReactDOM.render()는 처음 호출할 때 기존의 DOM 엘리먼트를 교체하며 이후의 호출은 React의 DOM diffing 알고리즘을 사용하여 더욱 효율적으로 업데이트한다.

  2. ReactDOM.render()는 컨테이너 노드를 수정하지 않고 컨테이너의 하위 노드만 수정하여 자식 노드를 덮어쓸 필요 없이 기존의 DOM 노드에 컴포넌트를 추가할 수 있다.

  3. ReactDOM.render()는 현재 ReactComponent 루트(root) 인스턴스에 대한 참조를 반환한다. 그러나 이 반환 값을 사용하는 것은 피해야한다.
    ReactComponent 인스턴스의 참조가 필요하다면 권장하는 해결책은 루트 엘리먼트에 콜백 ref를 붙여야한다.

  4. ReactDOM.render()를 사용해 서버에서 렌더링한 컨테이너에 이벤트를 보충할 때는 hydrate()를 사용해야 한다.

hydrate

1
ReactDOM.hydrate(element:React$Element<any>,container:Container,callback:?Function,)

위의 render와 동일하지만 HTML이 ReactDOMServer로 렌더링 된 후 컨테이너에 이벤트를 보충하기 위해 사용된다.
React는 기존 마크업에 이벤트 리스너를 연결한다.

서버에서 온 정적인 HTML코드에 React 코드들을 렌더링하여 리액트가 관리할 수 있도록 컴포넌트화 하는 함수이다.

SSR을 하는 경우에는 hydrate로 콜백만 붙여야 한다.(고한다.)

코드

마찬가지로 ReactDOM에서 hydrate 코드를 볼 수 있다.

render와 동작 방식이 똑같다.

다만 render와 달리legacyRenderSubtreeIntoContainer 함수에서 forceHydrate가 true이다.

1
2
3
4
5
6
7
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
true,
callback
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
export function hydrate(
element: React$Node,
container: Container,
callback: ?Function
) {
if (__DEV__) {
console.error(
"ReactDOM.hydrate is no longer supported in React 18. Use hydrateRoot " +
"instead. Until you switch to the new API, your app will behave as " +
"if it's running React 17. Learn " +
"more: https://reactjs.org/link/switch-to-createroot"
);
}

if (!isValidContainerLegacy(container)) {
throw new Error("Target container is not a DOM element.");
}

if (__DEV__) {
const isModernRoot =
isContainerMarkedAsRoot(container) &&
container._reactRootContainer === undefined;
if (isModernRoot) {
console.error(
"You are calling ReactDOM.hydrate() on a container that was previously " +
"passed to ReactDOM.createRoot(). This is not supported. " +
"Did you mean to call hydrateRoot(container, element)?"
);
}
}
// TODO: throw or warn if we couldn't hydrate?
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
true,
callback
);
}

ref

리액트 공식 문서
리액트의 hydration이란?

댓글