클라이언트 컴포넌트의 data fetching
function Note(props) {
const [note, setNote] = useState(null);
useEffect(() => {
fetch(`https://api.example.com/notes/${props.id}`)
.then(res => res.json())
.then(
(result) => {
setNote(result);
}
)
}, [props.id])
if (note === null) {
return "Loading";
} else {
}
}
장점
- 각 컴포넌트에서 데이터를 요청하는 경우, 실제 컴포넌트가 렌더링 될 때 필요한 데이터만 가져와 보여줄 수 있음
단점
- 클라이언트와 서버 사이의 API 요청 증가
- 부모 컴포넌트는 컴포넌트 렌더링 후 필요 데이터를 받아오기 시작하고, 이 과정이 끝나기 전까지 자식 컴포넌트의 렌더링과 API 호출이 지연되며 불필요한 렌더링 발생
리액트 서버 컴포넌트의 data fetching
React Server Component
서버에서 동작하는 리액트 컴포넌트.
컴포넌트 렌더링을 클라이언트가 아닌 서버에서 수행할 수 있음
서버에서 render를 수행하기 때문에 API를 통한 데이터 요청의 latency를 줄일 수 있고, 클라이언트에서의 연속된 API 호출을 제거해 client-server waterfall을 막을 수 있음
import { fetch } from 'react-fetch';
function Note(props) {
const note = fetch(`https://api.example.com/notes/${props.id}`).json();
if (note === null) {
return <div>노트가 존재하지 않습니다.</div>;
} else {
}
}
장점
- 클라이언트 컴포넌트에서 발생하던 client-server waterfall을 제거하여 컴포넌트에서 필요한 데이터만 fetching하는 방식을 유지하며, 퍼포먼스 향상 가능
서버 컴포넌트 장점
1. 자유로운 서버 리소스 접근
- 서버에서 동작하기 때문에 데이터베이스, 파일 시스템 등 서버 사이드 데이터 소스에 직접 접근 가능
- 서버에서 fetching한 데이터는 클라이언트 컴포넌트에 props로 전달 가능
(json으로 인코딩 가능한 serializable props만 전달 가능, function은 전달 불가능)
import fs from 'react-fs';
import db from 'db.server';
function Note(props) {
const note = db.notes.get(props.id);
const noteFromFile = JSON.parse(fs.readFile(`${id}.json`));
if(note === null) {
}
return {
}
}
제로 번들 사이즈 컴포넌트
- 라이브러리를 많이 사용하게 되면 번들 사이즈가 늘게 되고, 퍼포먼스에 악영향을 끼침
서버 컴포넌트 코드는 브라우저에 다운로드 되지 않고 서버에서 미리 렌더링 된 static content를 전달하기 때문에 패키지를 추가해도 번들 사이즈에 영향을 끼치지 않음
자동 코드 분할
[client component]
- React.lazy와 dynamic import를 사용해 렌더링에 필요한 컴포넌트를 동적으로 불러옴
- lazy loading이 필요한 컴포넌트마다 일일이 React.lazy와 dynamic import를 적용해야 함
- 부모 컴포넌트가 렌더링 된 이후 로딩을 시작하기 때문에 화면에 보이기 전 딜레이 존재
import React from 'react';
const OldPhotoRenderer = React.lazy(() => import('./OldPhotoRender.js'));
cosnt NewPhotoRenderer = React.lazy(() => import('./NewPhotoRenderer.js'));
function Photo(props) {
if(FeatureFlags.useNewPhotoRenderer) {
return <NewPhotoRenderer {...props} />;
} else {
return <OldPhotoRenderer {...props} />;
}
}
[server component]
- import 되는 모든 클라이언트 컴포넌트를 code splitting 포인트로 간주하기 때문에 더 이상 React.lazy로 메뉴얼 하게 명시하지 않아도 됨
= 서버에서 미리 필요한 컴포넌트를 선택하기 때문에 클라이언트는 렌더링 프로세스 초기에 번들 다운로드 가능
waterfall
- 렌더링과 로딩이 한 번에 진행되는 것이 아니라, 렌더링 이후 데이터가 로딩되는 방식으로 인해 불필요하게 시간이 소요되는 것
서버/클라이언트/공유 컴포넌트
서버 컴포넌트
- 서버에서만 렌더링되는 컴포넌트
- 유저 인터렉티비티 제공 불가
[주의사항]
- useState(), useReducer(), useEffect()와 같은 state/effects 사용 불가
- DOM과 같은 브라우저 api 사용 불가
- state/effects/브라우저 api 사용하는 커스텀 훅 사용 불가
- 데이터베이스 / 내부 서비스 / 파일 시스템과 같은 server-only 데이터 사용 가능
- 서버 컴포넌트 / 클라이언트 컴포넌트 / native elements(ex. div, span..) import 및 렌더링 가능
- 클라이언트 컴포넌트 props로 serializable한 데이터 전달 가능
[파일 네임 컨벤션]
클라이언트 컴포넌트
- 클라이언트에서 렌더링 되거나 SSR을 통해 서버에서 렌더링 되는 컴포넌트
- 유저 인터렉션 사용 가능
- 서버 컴포넌트 도입 전 리액트 컴포넌트
[주의사항]
- 서버 컴포넌트 import 불가
(서버 컴포넌트 -> 클라이언트 컴포넌트로 또 다른 서버 컴포넌트를 자식으로 넘겨주는 건 가능)
- server-only 데이터 사용 불가
- state / effects / 브라우저 api 사용 가능
[파일 네임 컨벤션]
공유 컴포넌트
[주의사항]
- state / effects / 브라우저 api 사용 불가
- 서버 컴포넌트 import 불가, server-only 데이터 사용 불가
- 서버와 클라이언트 컴포넌트에서 import 되어 사용 가능
[파일 네임 컨벤션]
RSC / SSR
*CSR
- 페이지 진입 -> HTML, js, data 로드 후 컴포넌트 렌더링이 끝나기 전까지 사용자는 빈 화면만 보게 됨
SSR
- js 파일을 서버에서 먼저 HTML로 렌더링
- 페이지 동작을 위해서는 자바스크립트 번들이 모두 다운로드 되고 hydration이 되어야 하지만, 빈 화면 대신 데이터가 존재하는 HTML을 제공함으로써 무거운 js 파일이 다운로드되는 동안 사용자에게 의미있는 콘텐츠 제공
- non-interactive한 버전의 클라이언트 컴포넌트를 최대한 빠르게 브라우저에 전달해 초기 페이지의 first contentful paint 속도를 향상시키는 것이 목적
RSC와 SSR의 차이
- RSC의 코드는 클라이언트로 전달되지 않음
- SSR의 모든 컴포넌트의 코드는 js 번들에 포함되어 클라이언트로 전송
- RSC는 페이지 레벨에 상관없이 모든 컴포넌트에서 서버에 접근 가능
- next.js는 가장 top level의 페이지에서만 getServerProps(), getInitialProps()로 서버에 접근 가능
- RSC는 클라이언트 상태를 유지하며 refetch 될 수 있음, 서버 컴포넌트는 HTML이 아닌 특별한 형태로 컴포넌트를 전달하기 떄문에 필요한 경우 포커스, 인풋 입력값 같은 클라이언트 상태를 유지하며 여러 번 데이터를 가져오고 리렌더링해 전달 가능
- SSR은 HTML로 전달되기 때문에 새로운 refetch가 필요한 경우 HTML 전체를 리렌더링 해야 하고, 이로 인해 클라이언트 상태를 유지할 수 없음
즉, RSC는 SSR의 대체가 아닌 보완의 수단으로 사용할 수 있음
SSR으로 초기 HTML 페이지를 빠르게 보여주고, 서버 컴포넌트로는 클라이언트로 전송되는 js 번들 사이즈를 감소시킨다면 사용자에게 기존보다 훨씬 빠르게 인터렉팅한 페이지를 제공할 수 있음
출처: https://tech.kakaopay.com/post/react-server-components/