본문 바로가기

Frontend

React Server Components

Introducing Zero-Bundle-Size React Server Components (2020년 12월 21일에 작성된 글 입니다.)

이 글은 위 글및 동영상을 참고하여 번역 및 개인 생각을 적은 글 입니다. 

 

소프트웨어 개발에 있어서 위 삼각형의 세가지 부분을 한번에 가져갈 수 없다고 합니다. 예를 들면 Good, Cheap을 선택하면 Fast하지 못하게되고 Fast, Good을 선택한다면 Cheap하게 개발 할 수 없음을 의미합니다. 

 

Dan은 React에서 위 세가지를 모두 가져가고 싶다고 합니다. 

  • Good user experience
  • Cheap maintenance
  • Fast performance

갑자기 Dan은 Spotify가 좋다고 합니다. 그리고 Spotify의 앱의 컴포넌트를 구성한다면 다음과 같이 하고 싶다고 합니다. ㅋㅋㅋ 

다음은 ArtistPage에 대한 컴포넌트입니다.  

 

function ArtistPage({ artisId }) {
	return (
    	<ArtistDetails artistId={artisId}>
            <TopTracks artistId={artisId} />
            <Discography artisId={artistId} />
        </ArtistDetails>
    );
}

 

Discogrpahy는 검색해보니까 음악리스트 컴포넌트인것 같습니다. Dan은 User Experience를 좋게 만들기 위해서는 Data Fetching로직이 매우 중요하다고 합니다. 하지만 또한 이 코드가 Cheap to maintain, 즉 유지보수 또한 쉬웠으면 좋겠다고 합니다.

 

결국 코드를 작성했을 때 다음과 같이 작성할 수 있습니다.

 

function ArticlePage({ artistId }) {
    const stuff = fetchAllTheStuffJustInCase();
    
    return (
    	<ArtistDetails details={stuff.details} artistId={artistId}>
            <TopTracks topTracks={stuff.topTracks} artistId={artistId} />
            <Discography discography={stuff.discography} artistId={artistId} />
        </ArtistDetails>
    );
}

 

fetching 코드는 추상화된 것 입니다. useEffect를 쓰던 redux를 쓰던 상관없습니다. 어째튼 리액트는 위와 같이 동작합니다. 데이터를 가져와서 아래 컴포넌트에 내려줍니다.

 

여기서 문제는 뭘까요? 데이터 fetching 로직이 컴포넌트와 커플링된다는 것입니다. Dan은 아래와 같이 코드를 작성하고 싶다고 합니다.

 

function ArtistPage({ artistId }) {
	return (
    	<ArtistDetails artistId={artistId}>
            <TopTracks artistId={artistId} />
            <Discography artistId={artistId} />
        </ArtistDetails>
    );
}

그리고 각각의 컴포넌트는 필요한 데이터를 가져오는 방식으로 코딩을 하면 아래와 같죠.

 

function ArtistDetails({ artistId, children }) {
	const details = fetchDetails(artistId);
   	// ...
}

function TopTracks({ artistId }) {
	const topTracks = fetchTopTracks(artistId);
    // ...
}

function Discography({ artistId }) {
	const discography = fetchDiscography(artistId);
    // ...
}

 

또한 Dan은 코드의 실행이 빨랐으면 좋겠다고 합니다. 근데 위와 같은 방법은 빠르지 않다고 합니다. 그 이유는 코드의 실행 방식에 있습니다. ArtistDetails 컴포넌트에서 fetching이 일어나고 시간이 좀 걸립니다. 그 다음 TopTracks 컴포넌트도 데이터 페칭을 합니다. 느린게 감이 오죠? TopTracks는 ArtistDetails에서 TopTracks를 렌더링하기 전까지 fetching을 시작조차 하지 않습니다. 이 것을 Network waterfall이라고 합니다. 왜냐면 폭포같거든요.

 

 

위 그림을 볼께요. 위의 리액트 로고를 상위 컴포넌트라고 하면 위에서 fetching이 시작되고 끝날때까지 기다리고 그다음 하위 컴포넌트에서 data fetching이 시작됩니다. 동시에 요청을 보낼 수 있지만, 우리가 코드를 짠 방식 때문에 동시에 요청을 보내는거 자체가 불가능하죠.

 

이 접근 방법으로는 Fast Performance를 추구하지 못합니다. 다시 돌아와서 아까처럼  상위 페이지 컴포넌트에서 fetching을 해서 props로 내려준다면 Network waterfall이 발생하지는 않겠죠.

 

Facebook에서는 이 문제를 Relay와 Graphql을 사용하여 해결했다고 합니다.

각각의 컴포넌트는 자신이 필요한 데이터를 Graphql Fragment를 통해 구체화하고, Relay는 fragments들을 조합하여 새로운 페이지로 navigate할 때 서버와 통신할 때  한번의 통신으로 가능하게끔 해준다. 하지만 이경우 백엔드를 Graphql로 만들어야한다. 이는 상당히 많은 제약이 따르는데 기존 REST API 레거시 코드가 많아서 전환을 못한다거나, 모르거나등의 이유를 들 수 있다.  

 

Relay에 대한 자세한 내용은 아래 링크에서 공식문서를 읽어보자.

https://relay-ko.github.io/  

 

이 제약을 해결하는 방법에는 우리의 컴포넌트들을 서버로 옮기는 방법이 있다. RSC.

 

 

클라이언트가 요청을 보내면 서버의 Parent Component는 통신을하고 그 후 Child Component 또한 통신을 한 후 응답을 보낸다.

 

서버 컴포넌트는 서버에서만 실행된다. 또한 Cilent에게 전송되지 않는다. 예를들어서 하나의 서버 컴포넌트 파일에 날짜를 포맷하기 위해 date-fns와 같은 라이브러리를 import 했다고 해보자. 그냥 일반 컴포넌트라면 date-fns 파일들도 번들링되어서 클라이언트에게 전송된다. 하지만 서버 컴포넌트는 클라이언트에게 전송하지 않는다.

 

서버 컴포넌트가 SSR과 다른점은 클라이언트 상태를 보존한다는 점에 있다. 서버사이드 렌더링과 서버 컴포넌트는 보완적인 관계이고, 우리는 이 두기술을 같이 사용할 수 있다. 서버 컴포넌트는 서버컴포넌트 트리를 refetch하더라도  클라이언트 상태를 유지할 수 있다. 

 

추가로 한 컴포넌트를 목적에 따라 서버에서도, 클라이언트에서도 사용할 수 있는데 이를 Shared Component라고한다.

 

동영상에서 소개된 Server Component Demo App은 아래 링크를 통해 코드를 볼 수 있다.

https://github.com/reactjs/server-components-demo

 

React IO Libraries

  • react-fs: FileSystem
  • react-pg: PostgreSQL
  • react-fetch: Fetch

위 라이브러리들은 기본 라이브러리들의 가벼운 wrapper이다. wrapper 코드가 100줄이 안되는 것도 있다고 한다. wrapper 코드에서 추가된 점은 React가 결과를 캐시하는 방법을 알려준다고 한다.

 

정리

위 그림과 같이 클라이언트 컴포넌트와 서버 컴포넌트로 구성된 리액트앱이 있다. 서버컴포넌트는 데이터를 클라이언트 컴포넌트에게 props로 내려준다. 

 

다음은 서버 컴포넌트의 특징이다.

  • Zero effect on bundle size
  • Access the backend directly
  • Automatic client code splitting
  • Use as much or as little as you lilke
  • Server mental model, modern UX
  • Opt-in: client React isn't going away

결과적으로 서버 컴포넌트는 데이터를 fetching할 때 발생하는 waterfall를 없애준다. 

 

궁극적으로 모든 앱은 성격이 다르기 때문에 우리는 당신에게 어떤 앱을 만들라고 말하지 않는다. 우리는 당신이 어떤것을 사용하던 규정하지 않는다. 하지만 우리는 당신에게 trade-off를 조절할 수 있는 tool을 제공하고 그로 인해 당신이 유지보수성퍼포먼스 사이에서 하나를 택하게 선택할 필요가 없고 클라이언트와 서버 중 하나를 택할 필요가 없도록 균형을 조정할 수 있는 도구를 제공하자 한다. (DAN)

 

유지 보수성 -> 컴포넌트에 fetch 로직을 분리하는것

퍼포먼스 -> 상위 컴포넌트에서 fetching

 

Ultimately every app is different and we don't want to tell you what kind of apps to build. We don't want to be prescriptive about it but we want to give you the tools to adjust the trade-off so that you don't have to choose between maintainability and performance and so that you don't have to choose between the client and the server beacuse we believe that you can have both. thank you.

'Frontend' 카테고리의 다른 글

PWA (점진적 웹 앱)  (0) 2022.06.16
React Query와 상태관리  (0) 2022.06.13
em vs rem  (0) 2022.06.02
HTTP와 HTTPS  (0) 2022.03.06
[FEConf Korea] 프론트엔드에서 TDD가 가능하다는 것을 보여드립니다.  (0) 2022.02.28