본 포스팅 시리즈는 📚'모던 리액트 Deep Dive'를 주간 별로 1장씩 공부하며
* 새롭게 알게 된 것들
* 평소 알고 있다고 생각했지만 이번에 제대로 알게 된 것들
* 궁금한 부분에 대해 딥다이브한 것들
등을 기재하기 위해 시작되었다.
📄 Single Page Application; SPA
: 렌더링과 라우팅에 필요한 대부분의 기능을 서버가 아닌 브라우저의 자바스크립트에 의존하는 방식.
- 최초 렌더링에는 첫 페이지에서 필요한 데이터를 모두 불러온다. (HTML 포함)
- 이후에는 페이지 전환을 위한 모든 작업이 자바스크립트와 history.pushState와 history.replaceState를 통해 이뤄진다. (HTML 포함 ❌)
📡 Server Side Rendering, SSR
: 최초에 사용자에게 보여줄 페이지를 서버에서 렌더링해 빠르게 사용자에게 화면을 제공하는 방식.
리액트는 리액트 애플리케이션을 브라우저 자바스크립트 환경에서 렌더링할 수 있는 방법 뿐만 아니라 서버에서 렌더링할 수 있는 API도 제공하고 있는데, 대표적인 것들을 한 번 살펴보자.
renderToStringrenderToString 은 인수로 넘겨받은 리액트 컴포넌트를 렌더링해 HTML 문자열로 반환하는 함수이다. SSR을 구현하는 데 가장 기초적인 API이며, 최초의 페이지를 HTML로 먼저 렌더링하는 역할을 한다.
import ReactDOMServer from 'react-dom/server'
const result = ReactDOMServer.rednerToString(
React.createElement('div', { id: 'root' }, <SampleComponent />)
)
위 코드에서 result는 다음과 같은 HTML 문자열을 반환한다.
<div id="root" data-reactroot="">
<!-- ... -->
</div>
반환된 문자열은 다음과 같은 특징을 가진다.
renderToString 이 인수로 주어진 리액트 컴포넌트를 기준으로 브라우저가 렌더링할 수 있는 HTML을 빠르게 제공하는 데 목적이 있는 함수이기 때문이다. renderToStaticMarkuprenderToStaticMarkup 은 리액트 컴포넌트를 기준으로 HTML 문자열을 만든다는 점에서 renderToString 함수와 동일하다.
한 가지 차이점은 리액트에서만 사용하는 추가적인 DOM 속성을 만들지 않는다는 점이다. 이것은 결과물인 HTML의 크기를 아주 약간이라도 줄일 수 있다는 장점이 있다.
renderToString 대신 renderToStaticMarkup 함수를 사용해서 렌더링하면, 클라이언트에서는 리액트에서 제공하는 브라우저 API를 절대로 실행할 수 없다. renderToStaticMarkup 의 결과물은 hydrate 함수를 수행하지 않는다는 가정 하에 순수한 HTML만 반환하기 때문이다.
renderToNodeStreamrenderToNodeStream 은 renderToString 함수의 결과물과 완전히 동일한 결과물을 만들지만 두 가지 차이점이 있다.
renderToNodeStream 은 브라우저에서 사용하는 것이 완전히 불가능하다.renderToNodeStream 의 결과물은 Node.js의 ReadableStream이다. ReadableStream은 utf-8로 인코딩된 바이트 스트림으로, Node.js, Deno, Bun 같은 서버 환경에서만 사용할 수 있다.🤔 그럼
renderToNodeStream은 언제 사용하는 걸까?renderToString으로 생성해야 하는 HTML의 크기가 매우 크다면❓ 크기가 큰 문자열을 한 번에 메모리에 올려두고 응답을 수행해야 하기 때문에 Node.js가 실행되는 서버에 큰 부담이 될 수 있다.
따라서 데이터를청크(chunk)로 분할해 조금씩 가져오는 방식인 스트림 방식을 활용해, 큰 크기의 데이터를 순차적으로 처리하는renderToNodeStream을 사용한다.
renderToStaticNodeStreamrenderToStaticNodeStream 또한 renderToNodeStream과 결과물은 동일하나, 리액트 자바스크립트에 필요한 리액트 속성이 제공되지 않는다. 마찬가지로 hydrate를 할 필요가 없는 순수 HTML 결과물이 필요할 때 사용하는 메서드이다.
hydrate 함수와 render 함수의 차이 (p. 276-278)hydrate 함수는 renderToString과 renderToNodeStream으로 생성된 HTML 컨텐츠에 자바스크립트 핸들러나 이벤트를 붙이는 역할을 한다.
주로 CRA(Create React App)으로 생성된 프로젝트에서 사용되는 render 함수는 hydrate 함수와 비슷하게 브라우저에서만 사용되는 메서드이다.
render 는 다음과 같이 사용된다.
import * as ReactDOM from 'react-dom'
import App from './App'
const rootElement = document.getElementById('root')
ReactDOM.render(<App />, rootElement)
render 함수는 렌더링할 컴포넌트와 렌더링 위치인 HTML 요소를 인수로 받는다. HTML 요소에 해당 컴포넌트를 렌더링하며, 여기에 이벤트 핸들러를 붙이는 작업까지 수행한다. 따라서 render 함수를 통해서는 리액트를 기반으로 한 온전한 웹페이지를 만드는 데 필요한 모든 작업 수행이 가능하다.
import * as ReactDOM from 'react-dom'
import App from './App'
// containerID를 가리키는 element는 서버에서 렌더링된 HTML의 특정 위치를 의미.
const element = document.getElementById(containerId)
// 해당 element를 기준으로 리액트 이벤트 핸들러를 붙인다.
ReactDOM.hydrate(<App />, element)
그렇다면 hydrate를 사용한 예제를 살펴보자.
render와의 가장 큰 차이점은 hydrate는 기본적으로 이미 렌더링된 HTML이 있다는 가정 하에 작업이 수행되고, 이벤트를 붙이는 작업만 실행한다는 것이다.
따라서 이 예제 코드에서는 element 내부에는 App 컴포넌트를 렌더링한 정보가 사전에 포함되어있었어야 hydrate 함수를 실행할 수 있다. 이에 비해 render 함수는 아무것도 없는 빈 HTML에 정보를 렌더링한다.