Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[우창완] 5장: 리액트와 상태 관리 라이브러리 #43

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 196 additions & 0 deletions 4장/우창완.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
# 서버사이드 렌더링

## SPA란?

최초 첫 페이지에서 모든 데이터를 모두 불러온 이후에, 페이지 전환을 위한 모든 작업이 자바스크립트와 브라우저에서 동작하는 방식

- 왜 body에 아무 내용이 없을까?

`index.html` 를 불러오고, JavaScript를 동적으로 렌더링 하기 때문이다.



## SPA(클라이언트 사이드 렌더링)의 한계와 서버사이드 렌더링

웹의 기능이 강력해지면서, JavaScript 번들링이 빠르게 늘어났다. 이전보다 강력한 기능을 제공하지만,

사용자들이 기다려야 하는 시간이 늘어나는 한계가 존재했다.



### 서버 사이드 렌더링의 장점

* 최초 페이지 진입(FCP)가 상대적으로 빠르다.(데이터 호출이 있을 경우, 항상 그렇지는 않다)
* SEO와 동적 메타데이터 생성에 유리하다.
* CLS가 적다.
* 사용자 기기별 편차가 비교적 적다.



### 서버 사이드 렌더링의 단점

* `window is not defined` 와 같은 에러를 신경써야 한다. 이는, 서버에서 사용할 수 없는 기능을 사용하였을 때 발생한다.

* 서버 관리의 부담이 증가한다.

가장 큰 단점이라고 생각한다. 클라이언트 렌더링 기반 서비스 배포 시에는 배포 환경을 단순하게 구성할 수 있지만, 서버 렌더링 기반 서비스 배포시에는, 배포 시간이 더욱 길어질 뿐만 아니라, Vercel이 아닌 AWS 배포 시에는 구성해야할 복잡성이 정말 많이 늘어난다. 또한 Server Component의 경우에는 외부 배포시에 아직도 비정상적으로 동작하지 않는 이슈가 존재한다.

* 서비스 지연 문제

이 문제는 최초 페이지 렌더링이 항상 빠르지 않다는 문제와 관련이 있다. 렌더링이 데이터에 의존적일 경우, 사용자는 어떠한 UI도 보지 못한다. Streaming을 통해 이 문제를 해결할 수 있다.



#### 눈 여겨봐야 할 키워드 - client, server rendering 최적화 기법

* 페인트 홀딩(Paint Holding): 페이지 이동 시, 흰 화면이 아닌, 이전 페이지의 모습을 보여주는 기법
* back forward cache(bfcache): 브라우저 뒤로 가기, 앞으ㅇ로 가기 실행 시 캐시된 페이지를 보여주는 기법
* Shared Element Transitions: 페이지 라우팅 시에, 두 페이지에 동일 요소가 있다면 해당 콘텍스트를 유지하여 부드럽게 전환하는 기법



## 서버 사이드 렌더링을 위한 리액트 API 살펴보기

1. `renderToString`: Component를 HTML 문자열로 렌더링한다.

``` tsx
const result = ReactDOMServer.renderToString(
React.createElement('div', {id: 'root'}, <Sample/> )
)

//결과값
<div id="root" data=reactroot=''>
<div>hello</div>
</div>
```



2. `renderToStaticMarkup`: 동일하게 HTML 문자열을 생성하지만, `data-reactroot` 와 같은 react에서 사용하는 DOM 속성을 사용하지 않는다. 이는 hydrate과정이 필요없을 때 사용되며, hydrate과정이 일어나지 않아 event 등록이 불가능하다.
3. `renderToNodeStream`: Node의 ReadableStream을 반환한다. renderToString과 동일한 결과물을 반환, Streaming chunk로 반환하여 한 번에 반환하는 크기를 줄여, Node.js 메모리를(서버 부하 감소) 효율적으로 이용할 수 있다.

4. `renderToStaticMarkup`: hydration이 필요없는 ReadableStream 반환



## 서버 사이드 렌더링 예제 프로젝트

효율성으로 인해 `renderToNodeStream` 이 쓰이지만 브라우저가 받는 결과물은 `renderToString`가 동일(완성해서 browser로 전달)



## Next.js란?

### app.tsx와 document.tsx

Next.js는 최초 렌더링 시 서버 렌더링, 페이지 이동 시, 클라이언트 렌더링으로 동작(Link의 동작 방식 hover, intersection observer등으로 동작)

document.tsx는 HTML 를 오버라이딩 할 수 있는 곳, 주로 head 메타 데이터를 추가한다.



Link를 이용하여 페이지 이동 시에는, 클라이언트 사이드에서 필요한 코드만 요청하지만, <a/> 태그 이용시에는 서버와 클라이언트에서 SPA hydration으로 동작한다 (해당 페이지 뿐 만 아니고 모든 코드 요청)



### 빌드 결과물

getServerSideProps가 있을 때 빌드 시, 눈 여겨 볼 부분은 다음과 같다.

* exports.id = 628; 페이지 별 고유 식별자를 부여한다. 번들링, 페이지 라우팅, 캐싱에서 사용하는 걸로 예상..?
* getServerSideProps 이 포함되어있으면 빌드 결과물이 JavaScript (없을 시에, html로 빌드 => 빌드 size가 더 큰 이유.)

```tsx
"use strict";
(() => {
var exports = {};
exports.id = 628;
exports.ids = [628];
exports.modules = {

/***/ 4971:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ Hello),
/* harmony export */ "getServerSideProps": () => (/* binding */ getServerSideProps)
/* harmony export */ });
/* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(997);
/* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__);

function Hello() {
console.log( true ? "서버" : 0) // eslint-disable-line no-console
;
return /*#__PURE__*/ react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx(react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.Fragment, {
children: "hello"
});
}
const getServerSideProps = ()=>{
return {
props: {}
};
};
/***/ }),

/***/ 997:
/***/ ((module) => {

module.exports = require("react/jsx-runtime");
/***/ })
};
;

// load runtime
var __webpack_require__ = require("../webpack-runtime.js");
__webpack_require__.C(exports);
var __webpack_exec__ = (moduleId) => (__webpack_require__(__webpack_require__.s = moduleId))
var __webpack_exports__ = (__webpack_exec__(4971));
module.exports = __webpack_exports__;
})();
```



### getServerSideProps, getStaticSideProps, getInitialSideProps

서버사이드에서 데이터 페칭을 하는 메서드들

### getServerSideProps

리액트 서버 사이드 렌더링 순서



1. 서버에서 데이터를 fetch한다
2. 1번에서 가져온 정보를 기반으로 HTML를 완성한다.
3. `pre-render`한 HTML파일을 서버에서 클라이언트로 제공한다.
4. JavaScript를 기반으로 리액트 컴포넌트 트리를 생성하고, prerender HTML을 바탕으로 `hydration`을 진행한다. (useEffect, event handler 등)
5. hydrate로 만든 리액트 컴포넌트 트리와 pretender html이 다르다면 hydration 에러를 발생시킨다.



#### getInitialProps

getServerSideProps, getStaticSideProps 이전에 서버에서 데이터 페칭을 지원했던 방식

현재는 제한적인 경우에서만 사용한다.





### 스타일 적용하기

#### CSS-in-JS

Next.js prerender과정에서 스타일을 입히기 위해서, render되는 페이지의 스타일을 collect하여 React.Context로 제공한다.

이 때문에, context를 사용하지 못하는 서버 컴포넌트에서는 context에 의존하는 emotion, styled-components를 사용하지 못한다.



production모드에서는 HTML에 <style>을 통하여 스타일을 적용하는 대신에, 자바스크립트를 활용해 CSSOM 트리에 직접 스타일을 넣는다.



73 changes: 73 additions & 0 deletions 5장/우창완.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# 리액트와 상태 관리 라이브러리







## 5.1 상태 관리 라이브러리들 역사

### Flux 패턴의 등장

단방향 상태 관리 패턴으로 복잡성 완화

* 대표 라이브러리: Flux

### 리덕스의 등장

리덕스는 flux와 Elm 아키텍쳐의 조합

Elm이란?: 데이터를 모델, 뷰, 업데이트로 구분하는 방식

### tanstack-query, zustand

최근에는 서버 상태 관리를 react-query, 클라이언트 상태 관리를 zustand, redux 등으로 나누어 관리하는 것이 일반적





## 5.2 직접 상태 관리 모듈 만들어보기

* 358page subscribe 잘 이해를 못함(subscribe와 useEffect unsubscribe가 어떻게 동작하고 있는지?)
* 362p => selector로 특정 상태만 useState에 저장해서 원하는 값만 구독 가능



* redux와 zustand 상태 관리 라이브러리를 바닐라로 작성되어 있고, 리액트와 상태 주기를 맞추기 위해서 useSyncExternalStore를 의존하고 있음(367p - useSubscription)
* ![image-20240109221141909](/Users/uchang-wan/Library/Application Support/typora-user-images/image-20240109221141909.png)

* useSyncExternalStore

![image-20240109221010373](/Users/uchang-wan/Library/Application Support/typora-user-images/image-20240109221010373.png)



* 370p => context를 사용할 경우, 매번 스토어를 안 만들고 context로 상태 공유를 한다.





### 5.2.4 상태 관리 라이브러리 Recoil, Jotai, Zustand

#### Recoil

개발 거의 중단 상태

이전에 컨퍼런스 recoil의 동시성과 suspense지원 때문에 recoil이 유일한 선택지였다고 본 것 같은데.. 어떤 부분에서 suspense와 결합하여 사용하는지 이해가 잘 안된다...

https://www.youtube.com/watch?v=0-UaleJZOw8

Recoil 써본 분 헬프..



#### Jotai - 패스

#### Zustand

* zustand는 구독, 발행 패턴으로 상태 관리 - (구독, 발행 패턴 ... 다음에 코드 작성해보기......)
* useSyncExternalStore로 리액트로 상태 주기 공유