server component 그리고 serialization

이준희·2023년 8월 28일
1

최근 서버컴포넌트를 활용한 작업을 하다가 이해할 수 없는 서버컴포넌트 에러를 마주하였고 이를 이해하기 위해 서버컴포넌트 메커니즘에 대해 서치한 내용을 정리해보려고 한다.

서버 컴포넌트?

React의 서버 컴포넌트 (RSC)를 통해 서버와 클라이언트 사이에서 React 애플리케이션을 상호 보완적으로 렌더링 할 수 있다. RSC의 도입으로 애플리케이션의 일부는 서버에서, 다른 일부는 클라이언트에서 렌더링하는 유연성을 갖게 되었다.

RSC의 동작방식

서버 컴포넌트의 작동 방식을 깊이 파악하기 위해서는, RSC가 실제로 어떤 방식으로 렌더링되는지 알아야한다. 아래의 예시처럼 서버 컴포넌트와 클라이언트 컴포넌트가 혼합된 화면 구조를 생각해보자.

사용자가 특정 페이지를 열기 위해 서버에 요청을 보내면, 서버는 그 시점부터 루트 컴포넌트부터 시작하여 컴포넌트 트리를 직렬화된 JSON 형식으로 재구성하게 된다.

직렬화?

직렬화는 데이터나 객체의 상태를 다른 환경 (예: 파일, 메모리, 네트워크)에 저장하거나 전송하기 위해 특정 형식으로 변환하는 절차를 의미한다.

주의할점은 모든 객체를 직렬화할 수는 없다는 것이다. 대표적으로 function은 직렬화가 불가능한 객체다. function이 실행코드와 실행 컨텍스트를 모두 포함하는 개념이기 때문인데, 함수는 자신이 선언된 스코프에 대한 참조를 유지하고, 그 시점의 외부 변수에 대한 참조를 기억하고 있다. js의 클로저가 바로 이런 현상을 가리키는 용어이기도 하다.

const tmp = 1;
    
const tmpLog = () => {
    console.log(tmp)
}

tmpLog() //1

이처럼 함수의 실행 컨텍스트, 스코프, 클로저까지 직렬화할 수는 없기 때문에 function은 직렬화가 불가능한 객체로 분류되는 것이다.

직렬화는 서버 컴포넌트를 모두 실행하여 JSON 객체 형태의 구조로 변환하는 과정이다. 예를 들면 아래와 같다.

React.createElement("div", {style: {backgroundColor: 'blue'}}, "Server Component")

{
"$$typeof": "react.element",
"type": "div",
"props": { "style": {backgroundColor: "blue"}, "children": "Server Component" },
...
} 

그러나 이런 절차를 모든 컴포넌트에 적용하는 것은 아니다. RCC의 경우, 직접 해석하는 것을 생략한다. 그렇다고 RCC를 공백으로 남겨두면 실제 컴포넌트 구조와의 일관성이 떨어진다. 이를 해결하기 위해 RCC가 렌더링되는 위치를 명시하는 특별한 placeholder를 배치한다.

예를 들어

{
  "$$typeof": "react.element",
  "type": {
    "$$typeof": "react.module.ref",
    "name": "main", 
    "filepath": "./path/ClientComp.js"
  },
  "props": { "content": "child content" },
}

앞서 언급한 바와 같이, RCC는 함수의 형태를 가지므로 직렬화가 불가능하다. 그래서 React는 "module reference"라는 새로운 유형을 도입하여 직렬화 문제를 해결한다. 이 유형을 사용하여 함수를 직접 참조하는 대신 해당 컴포넌트의 위치나 경로를 지정함으로써 직렬화 문제를 회피하고 있다.

0개의 댓글