1편에서 RSC에 대해 대략적으로 살펴봤다면, 다음으로 Server Component에 대해 알아보자.
이제, RSC가 서버에서 실행되므로, 각 컴포넌트 내부에서 직접 데이터 요청이 가능하다.
가령, 목록 api에서 조회하던 데이터를 직접 컴포넌트에서도 조회할 수 있게 되는 것인데 GET /rsc/:shipId?
를 살펴보면
여기서 getShip
이나 searchShip
은 DB 조회를 모방한 함수로써, shipData
에 접근하여 데이터를 조회한다. (네트워크 지연 같은 상황도 모방)
이제 getShip
를 서버 컴포넌트에서 호출할 수 있는 것이다.
위에서 언급한대로 GET api에서 (흉내낸)DB 조회 함수를 지운다.
그렇다면 이제 App.js
도 데이터 조회의 결과를 props로 받을 필요 없으니 ship, shipResults
를 props에서 제거한다.
SearchResults
에서 직접 DB를 조회하자.
SearchResults
컴포넌트는 검색 결과에 따라서 리스트가 나오는 좌측 부분이다.
컴포넌트에 async
키워드를 붙이고 DB 접근 함수를 호출하면 된다.
마찬가지로 Detail 컴포넌트에서도 직접 조회가 가능하다. Detail은 우측에 이미지가 나오는 부분이다.
동일하게 DB 직접 조회로 코드를 변경했다.
이제 우리는 데이터가 필요한 컴포넌트에서 각각 DB 조회를 하여 데이터를 패칭할 수 있도록 변경했다.
하지만, 문제가 생겼는데 이 중 하나라도 데이터 페칭이 지연되면 전체 앱이 느리게 렌더링 된다는 것이다.
searchShip
에 딜레이를 추가하고 다시 앱을 실행시켜보면
searchShip
가 완료되기 전까지 전체 앱이 렌더링되지 않는 것을 확인할 수 있다.
서버 컴포넌트가 서버에서 독립적으로 async로 데이터 요청을 해서 병렬인가? 싶지만 사실 병렬이 아니다.
RSC는 기본적으로 전체 리액트 트리 완료 후 RSC payload 스트리밍을 하려고 하기 때문에 모든 컴포넌트가 준비될 때까지 기다리는 것이다.
이 문제를 해결하기 위해 Suspense를 사용해야 한다. Suspense를 통해 먼저 준비된 부분부터 스트리밍 가능하다.
SearchResults
와 ShipDetails
각각 Suspense를 해주자.
번들러가 없기 때문에 createElement
를 직접 사용해서 조금 난해해보이지만 createElement(Component, props, child1, child2, child3...)
라는 걸 인지하면
<Suspense fallback={<SearchResultsFallback />}>
<SearchResults shipId={sihpId} search={search} />
<Suspense/>
와 동일하다.
이제 다시 앱을 확인하기 전에 RCS Payload가 어떻게 스트리밍 되는지 api를 먼저 확인해보자.
renderToPipeableStream
가 반환하는 pipe
에서 RSC Payload를 어떻게 스트리밍하는지 보자.
해당 api인 http://localhost:4000/rsc/3ba8aa65ffe6c
를 브라우저에서 보면 Payload 청크가 3번에 나눠서 스트리밍되는 것을 알 수 있다.
호출하자마자 처음부터 있는 가장 큰 청크인 이니셜 청크는 Root
에 대한 청크다.
그 다음 스트리밍되는 청크가 딜레이가 없는 Detail 컴포넌트고
마지막으로 4초 뒤에 만들어지는 청크가 delay 4초를 추가한 SearchResults 컴포넌트다
그럼 앱을 실행시켜서 화면 동작을 확인해보자.
RCS Payload 스트리밍에서 본 것처럼 데이터가 준비되는 순서로 스트리밍 하고 지연된 컴포넌트만 Suspense에 걸리는 것을 확인할 수 있다. 서버 컴포넌트와 Suspense 조합으로 Cascading waterfalls 해결할 수 있다.
1편에서 말했듯 RSC에선 상태 관리를 하지 못 한다. stateless니, 메모리니 이런 이유들을 제쳐두고서라도 느낌적으로 서버에선 hook 같은 기능을 사용하지 못할 것 같다.
하지만 컨텍스트로 상태를 관리하고 싶은 충동이 들 때도 있다.
가령, shipId
, search
같은 파라미터을 props가 아니라 전역 컨텍스트로 관리한다면 프롭스 드릴링을 줄일 수 있을 텐데 말이다!
그래서 사용할 수 있는 Node.js의 기능으로 AsyncLocalStorage
가 있다.
AsyncLocalStorage는 마치 클라이언트 훅에 useContext처럼 컨텍스트를 공유할 수 있는 기능이다.
AsyncLocalStorage
인스턴스를 하나 만들고 이제 api의 파라미터를 컨텍스트 안에 추가하면 된다.
run
의 콜백 안에서는 모든 컴포넌트가 동일한 컨텍스트에 접근 할 수 있다.
이렇게 첫 번째 인자에 공유할 데이터를 명시하고, 콜백 안에서 pipe
를 실행시키면 된다.
이제 App
에서 받던 props가 없으니 클라이언트 코드를 수정하면 된다.
getStore
를 통해 서버에서 받은 파라미터를 공유할 수 있게 된다. 동일하게 shipID
, search
를 소비했던 컴포넌트들도 props가 아니라 AsyncLocalStorage
로 컴포넌트에서 직접 조회하도록 수정하면 된다.(생략)
이로써 RSC가 해결하고자 했던 Cascading waterfalls와 Prop drilling 가 서버 컴포넌트로 해결되었다. 서버 컴포넌트는 이만 마무리하고 클라이언트 컴포넌트를 살펴보자. (AsyncLocalStorage 신기하다)
참고 : epic-react