23.06.05
오늘 한 일
- 알고리즘 문제 풀이
- 디지털콘텐츠기획 기말 과제 1/2 완료
- 카공실록 리뷰 상세 페이지 구현
- 카공실록 공유 바텀시트 구현
- 프리온보딩 6월
Next.js 13
궁금증
카공실록 프로젝트에서 Next.js 13 버전의 App directory를 사용하고 있다.
App directory 안에 있는 모든 컴포넌트들은 별다른 설정을 하지 않으면 기본적으로 server component다.
만약 client component를 사용하고 싶으면 파일의 최상단에 use client
를 추가해주면 된다.
서버 컴포넌트 왜씀?
- 서버 인프라를 더 잘 활용 가능
- 클라이언트 측 js 번들 크기 감소(js 번들 크기에 영향을 large dependencies가 서버에 남아 성능 향상)
- 초기 페이지 로드 빨라짐
경로가 Next.js로 로드되면 initial HTML이 서버에 렌더링 됨 -> 이 HTML은 브라우저에서 점진적으로 향상(progressively enhanced)되어 클라이언트가 Next.js 및 React 클라이언트 측 런타임을 비동기적으로 로드함으로써 어플리케이션을 take over하고 interactivity를 추가할 수 있다.
client component
는 서버에서 prerendering되고 client에서 hydrate된다.
server component -> client component (O) / client component -> server component (X)
server component 내부에서 client component를 import할 수 있지만, client component 내부에는 server component를 import할 수 없다.
// server component
import { ClientComponent } from './client-component'
export default function ServerComponent() {
return (
<div>
<ClientComponent />
</div>
)
}
// client component
import { ServerComponent } from './server-component' // error
export default function ClientComponent() {
return (
<div>
<ServerComponent />
</div>
)
}
RFC에서 더 자세한 내용을 확인할 수 있다.
RSC -> RCC -> RSC 가능?
이 그림같은 구조가 가능할까?
RCC에서 RSC를 import할 수 없지만, children
으로 ReactNode를 받아서 렌더링하는 방식으로는 가능하다.
// server component
export default function InnerServerComponent() {
return (
<div>
<p>InnerServerComponent</p>
</div>
)
}
// client component
export default function ClientComponent({children}) {
return (
<InnerServerComponent>{children}</InnerServerComponent>
)
}
// server component
export default function OuterServerComponent() { // RSC
return (
<ClientComponent> // RCC
<InnerServerComponent /> // RSC
</ClientComponent>
)
}
RSC 렌더링 라이프사이클
1. 서버가 렌더링 요청 받음
root
컴포넌트는 항상 RSC이고, root
컴포넌트가 렌더링되면서 서버가 렌더링을 시작한다.
2. 서버가 root 컴포넌트 엘리먼트를 JSON으로 직렬화
최초 root 서버 컴포넌트를 기본 html 태그와 클라이언트 컴포넌트 표시자(placeholder) 트리로 렌더링하는 게 최종 목표다.
트리를 직렬화하여 JSON으로 브라우저로 보내면, 브라우저가 다시 역직렬화하여 placeholder에 실제 클라이언트 컴포넌트를 채우고 렌더링하는 작업을 수행할 수 있다.
JSON.stringify(<OuterServerComponent />)
하면 직렬화된 렌더링 트리 얻을 수 있을까?
리액트 앨리먼트의 구조를 생각해보자.
// React element for <div>oh my</div>
> React.createElement("div", { title: "oh my" })
// 객체 형태로 표현
{
$$typeof: Symbol(react.element),
type: "div",
props: { title: "oh my" },
...
}
// React element for <MyComponent>oh my</MyComponent>
> function MyComponent({children}) {
return <div>{children}</div>;
}
> React.createElement(MyComponent, { children: "oh my" });
// 객체 형태로 표현
{
$$typeof: Symbol(react.element),
type: MyComponent // reference to the MyComponent function
props: { children: "oh my" },
...
}
기본 html 태그 엘리먼트가 아닌 컴포넌트 엘리먼트의 경우, 컴포넌트를 참조하려고 하기 때문에 직렬화 할 수 없다.
- 기본
HTML 태그
의 경우엔 가능하다. 서버 컴포넌트
라면, 서버 컴포넌트 함수를 props와 함께 호출하고, 그 결과를 JSON으로 만들어서 내려보낸다.클라이언트 컴포넌트
라면, 사실 JSON으로 직렬화가 가능하다. 이미 필드가 컴포넌트 함수가 아닌모듈 참조 객체
(module reference object)를 가리키고 있다.
모든 props는 직렬화되야 한다
서버 컴포넌트에서는 이벤트 핸들러를 props로 전달할 수 없다.
RSC 렌더링 중에 클라이언트 컴포넌트 마주침 -> 이 함수 호출하지 않고 넘어감
function SomeServerComponent() {
return <ClientComponent1>Hello world!</ClientComponent1>;
}
function ClientComponent1({children}) {
// 클라이언트에서는 가능
return <ClientComponent2 onChange={...}>{children}</ClientComponent2>;
}
이렇게 하면 ClientComponent2
는 RSC 트리에서 나타나지 않고 module reference가 있는 엘리먼트와 ClientComponent1
의 props만 볼 수 있다.
3. 브라우저가 리액트 트리를 재구조화
브라우저는 서버로부터 받은 JSON을 역직렬화하여 리액트 엘리먼트 트리를 재구성한다.
type이 module reference
인 엘리먼트를 만날 때마다 실제 클라이언트 컴포넌트 함수에 대한 참조로 바꾸려고 시도한다.
=> 트리 렌더링하고 DOM에 커밋
module reference
객체?
RSC는 module reference 라고 불리우는, 리액트 엘리먼트의 type 필드에 새로운 값을 넣을 수 있도록 제공한다. 이 값으로 컴포넌트 함수대신, 이 참조를 직렬화 한다.
{
$$typeof: Symbol(react.element),
// 실제 컴포넌트 함수 대신에, 참조 객체를 갖게됨
type: {
$$typeof: Symbol(react.module.reference),
// ClientComponent is the default export...
name: "default",
// from this file!
filename: "./src/ClientComponent.client.js"
},
props: { children: "oh my" },
}
이 변환 작업은 번들러
에서 이루어진다.
RSC Wire format
정확히 서버가 어떤 형태의 데이터를 보내는 것일까? 어떤 데이터가 서버에서 브라우저로 스트리밍 되는 걸까?
M1:{"id":"./src/ClientComponent.client.js","chunks":["client1"],"name":""}
J0:["$","@1",null,{"children":["$","span",null,{"children":"Hello from server land"}]}]
M
Line: 클라이언트 번들에서 컴포넌트 함수를 조회하는데 필요한 정보와 클라이언트 컴포넌트 module reference를 정의J
Line:M
라인에서 정의된 클라이언트 컴포넌트를 참조. 실제 리액트 컴포넌트 엘리먼트를 트리를 정의
이 형식의 포맷은 스트리밍으로 전송 가능하다.
서버 컴포넌트 함수를 호출할 때, 이 함수들은 각자 필요한 데이터를 가져올 때 promises
를 던진다. 이걸 마주하면 placeholder
에 위치 시킨다. resolve되면 다시 서버 컴포넌트 함수를 호출하고 chunk를 추가로 스트리밍한다.
내일 할 일
- 알고리즘 문제 풀이
- 카공실록 react hook form 적용
- 디지털콘텐츠기획 ppt 완성 및 발표 준비
- IT특강 프로젝트 레이아웃 구상
- 카공실록 오후 11시 회의