본문으로 건너뛰기

React Server Components

무료2021-05-05#Front-End#JS#React服务端组件#RSC#React Server Components vs SSR#SSR with React Server Components#React Server Components under the hood

React Server Components 란 무엇인가? 어떤 용도가 있는가? 어떤 모습인가?

서문에

2020 년 크리스마스 전에, React 팀은 Server Components 에 관한 메시지를 공개했습니다. 그 이전, 저는 마침 SSR(Server-Side Rendering, 서버 사이드 렌더링) 을 연구하고 있었고, [Next.js 의 하이브리드 렌더링](/articles/next-js 하이브리드 렌더링/) 에 감탄하고 있었습니다

실제, Server Components 도 확실히 Server-Side Rendering 과는 천사만루의 연결이 있습니다. 결국 둘 다 Server 라는 단어가 포함되어 있으니까요 (진지한 얼굴로, 이것은 매우 중요합니다).これまで SSR 에 관한 일련의 연구 덕분에, 어떤 의미에서는, Server Components 의 배후에 있는 사고와 설계 고려를 더욱 깊이 이해할 수 있다고 말할 수 있습니다

一.Server Components 란 무엇인가?

Server Components are a new type of?component that we're introducing to?React and this tells React that we only want?this component to ever render on the server.

(Data Fetching with React Server Components 강연 비디오에서 인용)

공식 정의에 따르면, Server Components 는 본질적으로 새로운 React 컴포넌트 (Portal, Fragment 등의 컴포넌트 타입과 같음) 이며, 그 특별한 점은 이러한 종류의 컴포넌트는 서버에서만 렌더링된다는 것입니다

그러나, React 컴포넌트는 클라이언트에서 잘 동작하고 있는데, 어떻게 갑자기 서버에서 실행해야 한다고 말하는 것일까요? Hooks 도 아직 완전히 이해하지 못했는데,一大波의 서버 사이드 컴포넌트 업데이트가 또 오려고 합니다. React 팀의 이러한 사람들은 도대체 무엇을 하고 있는 것일까요?

二.무엇을 해결하기 위한 것인가?

RFC 에 쓰여 있는 대로, React 가 Server Components 개념을 도입함으로써一大把의 문제를 해결할 수 있습니다:

  • Zero-Bundle-Size Components: 번들 결과물의 크기 문제

  • Full Access to the Backend: 데이터 액세스, 전달은 먼저 컴포넌트 트리 구조를 고려해야 하는 문제

  • Automatic Code Splitting: 코드 분할의 퍼포먼스 최적화는 수동 개조가 필요하다는 문제

  • No Client-Server Waterfalls: 리퀘스트 순서와 컴포넌트 트리 구조가 강관련이라는 문제

  • Avoiding the Abstraction Tax: 다층 추상이 가져오는 퍼포먼스 문제

이들 등이지만, 이들은 해결책에서 역산한 수익처럼 보이며, 제가 더욱 주목하고 있는 것은 그 초심입니다. 최초로 가장 해결하고 싶었던 문제는 무엇일까요?

First, we wanted to make it easier for developers to fall into the "pit of success" and achieve good performance by default. Second, we wanted to make it easier to fetch data in React apps.

(Motivation 에서 인용)

초심은 2 대류의 문제를 해결하고 싶은 것입니다:

  • 제 1 류: 퍼포먼스 최적화는 비교적 복잡하여, 디폴트로 고성능을 실현할 수 있는가?

  • 제 2 류:React 애플리케이션에서 데이터를 취득하는 것은実は많은 우려가 있지만, 더욱 심플하고, 더욱 우아한 방법은 없는가?

퍼포먼스 최적화는 필요에 따라 라이브러리를 인용하는, 루트별로 코드를 분할하는, 데이터 리퀘스트를 사전에 수행하는, 과도한 추상을 줄이는 등입니다. 이러한 최적화 조치는 모두 수동 개조가 필요하여, 애플리케이션 개발에 일정의 복잡성을 가져옵니다. 한편, 고성능을 위해, 통상 데이터 리퀘스트를顶层에 끌어올리기 때문에, 데이터 표시 컴포넌트가 데이터 소스에 명확하게 대응할 수 없다는 문제가 생깁니다

예를 들어 우리가 자주 목격하는:

// 数据展示组件的数据源依赖不清晰
function ArtistPage({ artistID }) {
  const artistData = fetchAllTheStuffJustInCase();

  return (
    <ArtistDetails
      details={artistData.details}
      artistId={artistId}>
      <TopTracks
        topTracks={artistData.topTracks}
        artistId={artistId} />
      <Discography
        discography={artistData.discography}
        artistId={artistId} />
    </ArtistDetails>
  );
}

(코드 유지 보수성을 고려하여) 더욱 바라는 것은:

// 类似这样的清晰依赖,每个组件明确知道其数据从哪来
function ArtistDetails({ artistId, children }) {
  const artistData = fetchDetails(artistId);
  // ...
}
function TopTracks({ artistId }) {
  const topTracks = fetchTopTracks(artistId);
  // ...
}
function Discography({ artistId }) {
  const discography = fetchDiscography(artistId);
  // ...
}

끊임없이 극한의 사용자 경험과 개발 경험을 추구하는 React 팀에게 있어, 더욱 심플하고, 더욱 우아한 데이터 취득 방식은 항상 탐구 방향이었습니다 (Suspense for Data Fetching (Experimental) 등). 따라서, 실제 상황은 React 팀이 데이터 취득 문제를 해결하는際に, Server Components 의 사고를 제안하고, 번뜩여서 이 대담한 생각이 많은 퍼포먼스 문제도 덤으로 해결할 수 있다는 것을 깨닫고,そこで 클라이언트에서 서버로의 도약적인 신특성 예고가 태어났을지도 모릅니다……

三.Server Components 는 어떻게 해결하는가?

컴포넌트의 데이터 관련 문제를 해결하려면, 컴포넌트에 각각 명확한 데이터 소스를 가지게 할 필요가 있지만, 폭포식 리퀘스트는 퍼포먼스 문제를 가져옵니다……좋은 사용자 경험, 저유지 보수 비용, 고성능은 3 つ를 겸비하는 것이 어려운 것처럼 보이지만, 불가능하지는 않습니다. 적어도 React 팀은 이미 2 つ의 해법을 탐색했습니다:

  • Relay + GraphQL:Relay 프레임워크 와 GraphQL 특성을 배합하여, 산재한 데이터 리퀘스트를 머지하여, 퍼포먼스 문제를 해결

  • Move our components to the server: 컴포넌트를 서버에 가지고 가서 실행하여, 데이터 리퀘스트의 코스트를 내려, 이로 인해 (很大程度上에서) 퍼포먼스 문제를 해결

GraphQL 은 리퀘스트로 지정된 데이터 모델 (schema) 에 기반하여 데이터 프래그먼트를 쉽게 조립할 수 있으며, Relay 프레임워크와 배합하여 복수 회의 리퀘스트를 1 회에 머지합니다. 이로 인해 컴포넌트 소스 코드의 유지 보수성 (명확한 데이터 소스 의존) 을 유지하면서,由此产生的 퍼포먼스 문제도 회피할 수 있습니다. 그러나 안타깝게도 GraphQL 에 강��게 의존하고 있어, 진정한 의미에서의 통용 해결책이라고는 할 수 없습니다

한편, Server Components 의 길은 비교적 야심차서, 복수 회의 클라이언트 리퀘스트의 시간 오버헤드를 내리기 위해, 차라리 컴포넌트를 서버상에서 실행하기로 했습니다. 그리고 (동일 유닛) 서버간의 데이터 통신은 매우 빠르고, 이때 복수 회의 데이터 리퀘스트의 퍼포먼스 오버헤드는 두려워할 가치가 없으며, 더욱이 최종적으로 (프레임워크 층에서) 데이터 캐시 메커니즘을 도입한 후에 완전히 해결됩니다

잠깐, 컴포넌트를 서버에 가지고 가서 실행하는 것은, SSR 그 자체 아닙니까? 이는莫非朝花夕拾?

四.Server Components 와 SSR 의 관계?

연락

일반적으로, 전통적인 SSR 은 2 つ의 프로세스를 면치 못합니다.서버 사이드 렌더링 + 클라이언트 hydrate 2 차 렌더링:

  • 서버 사이드 렌더링: 서버 사이드에서 퍼스트 스크린의 콘텐츠 (HTML 문자열) 를 렌더링

  • 클라이언트 hydrate 2 차 렌더링: 클라이언트에서 퍼스트 스크린의 콘텐츠 (이때 페이지는 인터랙션 불가) 및 프런트엔드 애플리케이션의 완전한 코드를 로드한 후, render 에 유사한 hydrate 2 차 렌더링 프로세스를 수행하여, 인터랙션 이벤트를 바인드한다 (이때 페이지는 인터랙션 가능)

Server Components 의 렌더링 프로세스는 이에 유사합니다:

  • 서버 사이드 렌더링: 서버 사이드에서 퍼스트 스크린의 콘텐츠 (일종의 중간 형식,同样로 UI 를 기술하는 데 사용) 를 렌더링

  • 클라이언트 렌더링: 서버 사이드 출력의 중간 형식을 받아, 처음부터 render 하여, 스트리밍 렌더링을 시작

따라서, Server Components 와 SSR 의 연락에는 적어도以下の점이 있습니다:

  • 둘 다 서버 사이드에서 컴포넌트 렌더링 로직을 실행 (따라서 둘 다 인터랙션을 서포트하지 않음)
  • 둘 다 동일 컴포넌트가 클라이언트, 서버 사이드를 넘어 실행 가능 (Shared Components)
  • 둘 다 클라이언트에서 서버 사이드로 연장하여, 더욱 많은 퍼포먼스 돌파를 추구

양자의 관계에 대해, React 공식은 매우 적확한 표현을 하고 있습니다. **Server Components 와 SSR 은 보완적 (complementary)**이며, 쌍검합벽으로, SSR 은 퍼스트 스크린을 HTML 에 렌더링하여 콘텐츠 표시를 가속할 수 있고, Server Components 는 hydrate 2 차 렌더링에 필요한 로드 실행 코드량을削减할 수 있습니다 (Server Components 는 서버 사이드에서만 렌더링되며, 관련 코드는 클라이언트에서 로드 실행할 필요가 없음). 이로 인해 페이지의 인터랙티브 시간을 가속할 수 있습니다:

You can combine Server Components and SSR, where Server Components render first, with Client Components rendering into HTML for fast non-interactive display while they are hydrated. When combined in this way you still get fast startup, but you also dramatically reduce the amount of JS that needs to be downloaded on the client.

구별

중요한 구별은 3 점 있습니다:

  • Server Components 관련 코드는 근본적으로 클라이언트에 제공되지 않으며, 전통적 SSR 은 모든 컴포넌트 코드를 클라이언트 bundle 에 때려넣을 필요가 있음

  • Server Components 는 컴포넌트 트리의 임의의 위치에서 직접 백엔드에 액세스 가능하며, 전통적 SSR 은顶层 (페이지 레벨) 에서만 데이터 취득 가능

  • Server Components 는 업데이트 시 클라이언트 인터랙션 상태 (입력된 검색어, 스크롤 위치, 포커스, 선택 내용 등을 포함) 를 유지 가능.Server Components 의 렌더링 결과는 HTML 보다도 정보가 풍부한 중간 형식 (HTML 은 HTML 만 표현할 수 있지만, 커스텀 형식에는 이 제한이 없음. 예를 들어 props 를 가질 수 있음)

Server Components 는 서버 사이드에서만 실행되며, 클라이언트는 이러한 코드를 로드하지 않습니다. 서버 사이드가 클라이언트에 제공하는始终是 Server Components 의 렌더링 결과로, 2 차 업데이트도 포함합니다. 중간 형식으로 클라이언트에 제공된 후, 클라이언트는 서비스로부터의 렌더링 결과를 현재의 이미 렌더링済みの 클라이언트 컴포넌트에 merge 할 뿐이므로, 인터랙션 상태를 유지할 수 있습니다:

Specifically, React merges new props passed from the server into existing Client Components, maintaining the state (and DOM) of these components to preserve focus, state, and any ongoing animations.

P.S.양자의 구별에 관한 상세 정보는, danabramov on Zero-Bundle-Size React Server Components 참조

五.Server Components 의 우세

1.bundle size 를削减하는 데 유리

Server Components 는 서버 사이드에서만 실행되므로, 컴포넌트 자체 및 그 의존 라이브러리는 클라이언트 bundle 에 때려넣지 않습니다. 따라서 很大程度上에서 패키지体积를削减할 수 있습니다 (Facebook 의 파일럿 케이스는 30% 정도削减)

한편, 중간의 다층 추상 캡슐화는 모두 서버 사이드에서 소화되며, 클라이언트의 부담을 경감합니다

2.컴포넌트 트리의 임의의 위치에서 백엔드 리소스에 액세스 가능

컴포넌트 트리의 임의의 위치에서 백엔드 리소스에 액세스할 수 있는 것은, 전통적 SSR 에서도 할 수 없습니다. 전통적 SSR 에는 클라이언트 프레임워크의 배합이 부족하여, 데이터를 일괄로 취득하고, 동기 의 컴포넌트 렌더링을 수행하고, 마지막으로 결과를 클라이언트에 제공할 수밖에 없기 때문입니다

실제, 초심은 컴포넌트와 그 데이터 소스의 관계를 더욱 명확히 하고, 코드 유지 보수성을 더욱 좋게 하는 것입니다:

// 类似这样的清晰依赖,每个组件明确知道其数据从哪来
function ArtistDetails({ artistId, children }) {
  const artistData = fetchDetails(artistId);
  // ...
}
function TopTracks({ artistId }) {
  const topTracks = fetchTopTracks(artistId);
  // ...
}
function Discography({ artistId }) {
  const discography = fetchDiscography(artistId);
  // ...
}

컴포넌트를 수정할 때 대응하는 데이터도 함께 수정하고, 컴포넌트를下线 할 때 데이터 리퀘스트도 함께 내린다

3.필요에 따라 코드를 다운로드 가능

서버 사이드에는 데이터가 있으므로, 어느 컴포넌트를 내릴 필요가 있는지를 정확히 알고 있습니다:

Server Components let?you only download the code that you actually need, they enable?automatic code splitting for client code?with support of a bundler plugin, you can use as?much Server Components or as little as you like.

동시에 자동 코드 분할을 가능하게 하여, 모든 Client Components import 는 자동으로 필요에 따라 로드되며, dynamic imports 를逐一採用할 필요가 없어졌습니다. 개발자는 명시적으로気に할 필요가 없고, Server Components 가 디폴트로 서포트합니다

4.SSR 과 보완

SSR 의 각도에서만 보더라도, Server Components 는 컴포넌트화 프레임워크가 컴포넌트 시스템 레벨에서 SSR 애플리케이션 프레임워크가 해결할 수 없는 문제를 해결했습니다. 예를 들어:

  • hydrate 2 차 렌더링을 가속 (클라이언트에서 필요한 로드 실행 코드량을削减하여, 우회하여 구국)

  • 스트리밍 렌더링 서포트 (SSR 스트리밍 출력과는 달라, 스트리밍 렌더링은 반드시 컴포넌트화 프레임워크 자체가 서버 사이드와 배합할 필요가 있음)

  • 부분 업데이트를 허가하고, 인터랙션 상태를 유지 (전통적 SSR 은 퍼스트 스크린에서만 사용 가능)

이러한 퍼포먼스 포인트는 SSR 프레임워크만으로는 극한까지 수행할 수 없지만, Server Components 는 이 프로세스를 대폭 가속했습니다

한편, 서두에서 Next.js 가 하이브리드 렌더링 방면에서 깊게 탐구하고 있어, SSG, SSR, CSR 을 복수 방식으로 혼용 가능하여, 모든 기회를 잡아 프리렌더링을 수행하며, 그 목적은 퍼스트 스크린 퍼포먼스를 향상시키는 것 (SPA 루트 점프 등의 인터랙션 시나리오에서의 퍼스트 스크린 퍼포먼스를 포함) 이라고 기술했습니다. 따라서, 어떤 의미에서는, Server Components 와 이러한 프리렌더링 탐구는 이곡동공이며,そのため 충돌하지 않고 배합 사용할 수 있습니다

六.발전 현황

작년부터 Facebook 내부에서 파일럿을 수행했지만, 초보적인 검증에 불과하며, 생산화까지는 일정의 거리가 있습니다

게다가 컴포넌트를 서버 사이드에 가지고 가서 실행하기 때문에, 구축, 서버 사이드 렌더링, 루트 제어 등 많은環節에 관여하며, 컴포넌트화 프레임워크의 범주를 넘어서므로, React 팀은 Next.js 팀과 협력하여共建할 계획을 세우고, 먼저 Next.js 와의 통합을 시도합니다 (물론, Server Components 는 특정 SSR 프레임워크에만 한정되지 않으며, 먼저 하나를 통합하여 시도할 뿐입니다)

현재 공식 Demo 를 통해试玩 가능합니다. 주의가 필요한 것은, Demo 는 postgre 데이터베이스에 의존하고 있으므로, docker 를 통해 기동하는 것을 권장합니다:

docker-compose up -d
docker-compose exec notes-app npm run seed

试玩을 통해 몇 가지 상세를 이해할 수 있습니다. 예를 들어 Server Components 가 렌더링해 온 중간 형식은 대략 이러한 모습입니다:

[caption id="attachment_2344" align="alignnone" width="625"]server-components server-components[/caption]

그 중에서, Client Components 는 bundle 인덱스의 형식으로 반환되며, 네이티브 컴포넌트 (div, span 등) 는 JSON 형식으로 반환됩니다. 예를 들어:

[
  "$",
  "header",
  null,
  {
    "className": "sidebar-note-header",
    "children": [
      ["$", "strong", null, { "children": "todo" }],
      ["$", "small", null, { "children": "5/4/21" }]
    ]
  }
]

P.S.React Server Components 의 더욱 많은 기술 상세에 대해서는, RFC: React Server Components 참조

참고 자료

댓글

아직 댓글이 없습니다

댓글 작성