FE/리뷰

[모던 리액트 딥다이브] 4.2장 SSR을 위한 리액트 API

따봉치치 2024. 8. 3. 16:39
728x90

리액트 API
  • 리액트는 애플리케이션을 서버에서 렌더링할 수 있는 API도 제공함
  • react-dom/server.js에서 확인 할 수 있음

 

renderToString
  • 인수로 넘겨받은 리액트 컴포넌트를 렌더링해 HTML 문자열로 반환하는 함수
  • 서버 사이드 렌더링을 구현하는 데 가장 기초적인 API
  • 먼저 완성된 HTML을 서버에서 제공할 수 있으므로 초기 렌더링에서 뛰어난 성능일 보임
import React, { useEffect } from "react"
import ReactDOMServer from 'react-dom/server'
function ChilrenComponent({fruits} : {fruits: Array<string>}) {
    useEffect(() => {
        console.log(fruits);
    },[fruits]);

    function handleClick() {
        console.log('hello');
    }

    return (
        <ul>
            {fruits.map((fruit) => (
                <li key={fruit} onClick={handleClick}>
                    {fruit}
                </li>
            ))}
        </ul>
    )
}

function SampleComponent() {
    return (
        <>
            <div>hello</div>
            <ChilrenComponent fruits={['apple','banana','peach']}/>
        </>
    )
}

const result = ReactDOMServer.renderToString(
    React.createElement('div',{id:'root'}, <SampleComponent />),
)
// 위 result가 반환한 문자열
<div id="root" data-reactroot="">
    <div>hello</div>
    <ul>
        <li>apple</li>
        <li>banana</li>
        <li>peach</li>
    </ul>
</div>

 

  • 렌더링은 루트 컴포넌트인 <div id="root" />에서 수행됨
  • ChildrenComponent에 있는 useEffect와 같은 훅과 handleClick과 같은 이벤트 핸들러는 결과물에 포함되지 않음
  • renderToString은 인수로 주어진 리액트 컴포넌트를 기준으로 빠르게 브라우저가 렌더링할 수 있는 HTML을 제공하는 데 목적이 있는 함수일 뿐
  • 클라이언트에서 실행되는 자바스크립트 코드를 포함시키거나 렌더링하는 역할까지 해주지는 않음
  • data-reactroot
    • 리액트 컴포넌트의 루트 엘리먼트가 무엇인지 식별하는 역할
    • 이후에 자바스크립트를 실행하기 위한 hydrate 함수에서 루트를 식별하는 기준점

 

 

 

renderToStaticMarkup
  • renerToString과 유사한 함수
  • 리액트 컴포넌트를 기준으로 HTML 문자열을 만듦
  • 루트 요소에 추가한 data-reactroot와 같은 리액트에서만 사용하는 추가적인 DOM 속성을 만들지 않음
  • 결과물인 HTML의 크기를 아주 약간이라도 줄일 수 있음
  • 클라이언트에서는 리액트에서 제공하는 useEffect와 같은 브라우저 API를 절대로 실행할 수 없음
  • 리액트의 이벤트 리스너가 필요 없는 완전히 순수한 HTML을 만들 때만 사용됨
...

const result = ReactDOMServer.renderToStaticMarkup(
    React.createElement('div',{id:'root'}, <SampleComponent />),
)

 

// 위 result가 반환한 문자열
<div id="root">
    <div>hello</div>
    <ul>
        <li>apple</li>
        <li>banana</li>
        <li>peach</li>
    </ul>
</div>
  • 순수한 HTML 문자열이 반환

 

 

renderToNodeStream
  • renderToString과 결과물이 완전히 같지만 차이점이 있음
  • 브라우저에서 사용하는 것이 완전히 불가능함 => 완전히 Node.js 환경에 의존하고 있음
  • 결과물 타입이 string이 아닌 Node.js의 ReadableStream임 => 브라우저가 원하는 결과물인 string을 얻기 위해서는 추가적인 처리가 필요함
  • renderToString의 결과물이 매우 크면 큰 문자열을 한 번에 메모리에 올려두고 응답을 수행해야 해서 Node.js가 실행되는 서버에 큰 부담이 될 수 있음
  • 대신 스트림을 활용하면 데이터를 청크 단위로 분리해 순차적으로 처리할 수 있음
  • 대부분의 널리 알려진 리액트 서버 사이드 렌더링 프레임워크는 모두 renderToString 대신 renderToNodeStream을 채택하고 있음ㅇ
스트림 : 동영상과 같이 큰 데이터를 다룰 때 데이터를 청크(chunck, 작은 단위)로 분할해 조금씩 가져오는 방식

 

 

renderToStaticNodeStream
  • renderToNodeStream과 제공하는 결과물은 동일하나 리액트 자바스크립트에 필요한 리액트 속성이 제공되지 않음
  • hydrate를 할 필요가 없는 순수 HTML 결과물이 필요할 때 사용하는 메서드

 

hydrate
  • hydrate 함수는 두 개의 함수 renderToString, renderToNodeStream으로 생성된 HTML 콘텐츠에 자바스크립트 핸들러나 이벤트를 붙이는 역할
  • 즉, 정적으로 생성된 HTML에 이벤트와 핸들러를 붙여 완전한 웹페이지 결과물을 만듦
  • render와의 차이점
    •  hydrate는 기본적으로 이미 렌더링된 HTML이 있다는 가정하에 작업이 수행되고, 이 렌더링된 HTML을 기준으로 이벤트를 붙이는 작업만 실행됨
    • 즉, hydrate로 넘겨준 두 번째 인수에는 이미 renderToString 등으로 렌더링된 정적인 HTML 정보가 반드시 담겨 있어야 함
// containerId를 가리키는 element는 서버에서 렌더링된 HTML의 특정 위치를 의미함
const element = document.getElementById(containerId)
// 해당 element를 기준으로 리액트 이벤트 핸들러를 붙임
ReactDOM.hydrate(<App/>, element);

 

 

 

render
  • 주로 create-react-app으로 생성된 프로젝트의 index.jsx에서 찾아 볼 수 있음
  • render 함수는 컴포넌트와 HTML의 요소를 인수로 받음
  • HTML의 요소에 해당 컴포넌트를 렌더링하며, 여기에 이벤트 핸들러를 붙이는 작업까지 모두 한 번에 수행함
  • 클라이언트에서만 실행되는, 렌더링과 이벤트 핸들러 추가 등 리액트를 기반으로 한 온전한 웹페이지를 만드는 데 필요한 모든 작업을 수행함
const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);

 

728x90