Skip to content

Commit

Permalink
docs: 챕터 12 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
100Gyeon authored Nov 30, 2024
1 parent 0421521 commit 195e7f8
Showing 1 changed file with 290 additions and 0 deletions.
290 changes: 290 additions & 0 deletions 챕터_12/백지연.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
# CHAPTER 12 리액트 디자인 패턴

## 리액트에서 널리 사용되는 디자인 패턴

- [고차 컴포넌트 패턴](#고차-컴포넌트-패턴)
- [렌더링 Props 패턴](#렌더링-props-패턴)
- [Hooks 패턴](#hooks-패턴)
- [정적 가져오기](#정적-가져오기)
- [동적 가져오기](#동적-가져오기)
- [코드 스플리팅](#코드-스플리팅)
- [PRPL 패턴](#PRPL-패턴)
- [로딩 우선순위](#로딩-우선순위)

## 고차 컴포넌트 패턴

고차 컴포넌트는 **컴포넌트를 인자로 받아 새로운 컴포넌트를 반환**하는 컴포넌트
즉, 인자로 받은 컴포넌트에 추가 기능을 적용한 컴포넌트를 반환

```javascript
// 컴포넌트를 인자로 받음
function withStyles(Component) {
return (props) => {
const style = { padding: '0.2rem', margin: '1rem' };
return <Component style={style} {...props} />; // 새로운 컴포넌트 반환
};
}

const Button = () => <button>Click me!</button>;
const Text = () => <p>Hello World!</p>;

const StyledButton = withStyles(Button);
const StyledText = withStyles(Text);
```

### 목적

여러 컴포넌트에 동일한 로직을 사용하고 싶을 때 사용

### 활용 예시

특정 스타일 적용, 인증 요구, 전역 상태 추가

### 장점

- 로직을 한 곳에 집중시켜 중복 제거
- 효과적으로 관심사 분리

### 단점

고차 컴포넌트가 대상 컴포넌트에 전달하는 prop의 이름이 일으키는 충돌 → 디버깅과 애플리케이션 확장에 어려움 발생

## 렌더링 Props 패턴

자신의 렌더링 로직을 구현하는 대신, 렌더링 prop을 호출
렌더링 prop의 이름이 `render`일 필요는 없고, JSX를 렌더링하면 렌더링 prop으로 간주

```javascript
<Component render={() => <h1>I am a render prop!</h1>} />

<Component render={data => <ChildComponent data={data} />} />
```

### 장점

고차 컴포넌트 패턴에서 발생할 수 있는 **이름 충돌 문제를 해결** → 어떤 props가 어디에서 오는지 정확하게 파악 가능

### 단점

- 대부분의 경우 렌더링 Props 패턴을 Hooks 패턴으로 대체 가능
- 라이프사이클 관련 메서드를 추가할 수 없음 → 데이터를 변경하지 않는 렌더링에 치중한 컴포넌트에만 사용 가능

### 상태 끌어올리기

A 컴포넌트와 B 컴포넌트가 상태를 공유하기 위해 **가장 가까운 공통 조상 컴포넌트**로 상태를 끌어올리는 것
작은 규모의 애플리케이션에서는 전역 상태 관리 라이브러리 대신 이 패턴 사용하는 것만으로도 충분
하지만 큰 규모라면 상태 끌어올리기가 복잡해지며, 리렌더링으로 인해 성능에 악영향 줄 수 있음
이때 렌더링 Props 패턴을 사용하면 좋음

```javascript
function Input(props) {
const [value, setValue] = useState('');
return (
<>
<input type="text" value={value} onChange={(e) => setValue(e.target.value)} />
// 여기
{props.render(value)}
</>
);
}

export default function App() {
return (
<div className="App">
<h1>Temperature Converter</h1>
<Input
// 여기
render={(value) => (
<>
<Kelvin value={value} />
<Fahrenheit value={value} />
</>
)}
/>
</div>
);
}
```

### 컴포넌트의 자식으로 함수 전달하기

렌더링 Prop으로 전달하는 대신 **컴포넌트의 자식**으로 함수 전달 가능

```javascript
function Input(props) {
const [value, setValue] = useState('');
return (
<>
<input type="text" value={value} onChange={(e) => setValue(e.target.value)} />
// 여기
{props.children(value)}
</>
);
}

export default function App() {
return (
<div className="App">
<h1>Temperature Converter</h1>
<Input>
// 여기
{(value) => (
<>
<Kelvin value={value} />
<Fahrenheit value={value} />
</>
)}
</Input>
</div>
);
}
```

## Hooks 패턴

Hooks를 사용하면 **클래스 컴포넌트를 사용하지 않고도 상태와 라이프사이클 메서드 활용 가능**

### 장점

- 간결한 코드
- 복잡한 컴포넌트의 단순화
- 상태 관련 로직 재사용
- UI에서 분리된 로직 공유

## 정적 가져오기

`import 모듈 from '모듈'`을 사용해 가져오는 모듈은 모두 정적으로 가져온 것

컴포넌트를 정적으로 가져오면 웹팩은 모듈을 초기 번들에 포함시킴
애플리케이션을 빌드한 후에는 웹팩이 생성한 번들 확인 가능

## 동적 가져오기

모듈이 초기 번들에 불필요하게 포함되어 로딩 시간이 증가하는 문제를 해결하기 위해 컴포넌트를 동적으로 가져올 수 있음
컴포넌트를 정적으로 가져오는 대신 실제로 필요한 시점에 맞춰 불러오는 것
리액트의 `Suspense` 컴포넌트로 동적으로 로드할 컴포넌트를 감싸면 된다.
리액트 18부터 SSR 환경에서도 `Suspense` 사용 가능

### 상호작용 시 가져오기

ex. 채팅 애플리케이션에서 사용자가 이모지를 클릭하면 EmojiPicker 컴포넌트를 동적으로 가져오는 상황

### 화면에 보이는 순간 가져오기

IntersectionObserver API를 사용해 컴포넌트가 화면에 보이는지 확인
ex. 사용자가 스크롤해야 화면에 나타나는 컴포넌트

## 코드 스플리팅

복잡한 애플리케이션에서는 적절한 시기에 정적/동적 임포트가 가능하도록 코드를 최적으로 스플리팅하고 번들링해야 함

### 경로 기반 분할

때로는 특정 페이지(경로)에서만 필요한 리소스가 있음
이런 경우 경로별로 컴포넌트를 지연 로딩하면, 현재 경로에 필요한 코드가 포함된 번들만 요청 가능

### 번들 분할

거대한 번들 하나를 요청하는 대신, 여러 개의 작은 번들로 분할하는 방법

#### 장점

- 첫 번째 콘텐츠가 사용자 화면에 표시되는 시간(`FCP`) 단축
- 가장 큰 콘텐츠가 화면에 렌더링되는 시간(`LCP`) 개선
- 모든 콘텐츠가 화면에 표시되고 인터랙티브해지는 데 걸리는 시간(`TTI`) 개선

## PRPL 패턴

저사양 기기나 인터넷 연결이 불안정한 지역에서도 애플리케이션이 원활하게 작동해야 함
어떤 환경에서도 애플리케이션이 최대한 효율적으로 로드될 수 있도록 PRPL 패턴 사용

4가지 핵심 성능 고려사항에 중점

- `Push` : 중요한 리소스를 효율적으로 푸시해 서버 왕복 횟수를 최소화하고 로딩 시간 단축
- `Render` : 초기 경로를 최대한 빠르게 렌더링해 UX 개선
- `Pre-cache` : 자주 방문하는 경로의 에셋을 백그라운드에서 미리 캐싱해 서버 요청 횟수 줄이고 더 나은 오프라인 경험 제공
- `Lazy-load` : 자주 요청되지 않는 경로나 에셋은 지연 로딩

>
## 로딩 우선순위

`preload`는 브라우저의 최적화 기능으로, **중요한 리소스를 더 일찍 요청** 가능
로딩 순서를 지정하면 Core Web Vitals의 로딩 성능 및 지표에 긍정적인 영향 미침

`TTI` 또는 `FID`를 최적화할 때 유용
상호작용에 필요한 리소스를 먼저 로딩하다가
`FCP`, `LCP`에 필요한 리소스(ex. 폰트, 히어로 이미지)의 로딩이 지연되지 않도록 주의해야 함

자바스크립트 자체의 로딩을 최적화하려면, `<body>` 태그보다는 `<head>` 태그 안에서 `<script defer>`를 사용하는 게 좋음

### SPA의 Preload

#### `prefetching`

- 앞으로 사용될 가능성이 높은 리소스를 미리 가져와 캐시에 저장하는 방식
- 브라우저가 인터넷 연결 상태와 대역폭을 고려해 어떤 리소스 미리 가져올지 결정

#### `preload`

- **즉시** 사용해야 하는 리소스 (ex. 초기 렌더링에 사용되는 특정 폰트나 접속 시 바로 보이는 히어로 이미지 등)
- **어떤 상황에서든 무조건 미리 로드**
- 초기 렌더링 후 약 1초 이내에 표시되어야 하는 리소스만 선별해 미리 로드하는 것이 좋음

### Preload + async 기법

브라우저가 스크립트를 높은 우선순위로 다운로드하면서도, 스크립트를 기다리는 동안 파싱이 멈추지 않도록 하기 위한 기법

```html
<link rel="preload" href="emoji-picker.js" as="script" />
<script src="emoji-picker.js" async></script>
```

### 크롬 95+ 버전에서의 Preload

크롬 95+ 버전에서는 preload의 queue-jumping 동작이 개선되어 preload 기능이 더 안전해짐

> queue-jumping이란?
> 브라우저는 리소스를 요청할 때 queue에 쌓아서 순차적으로 처리합니다.
> 기본적으로 브라우저는 HTML 파일을 읽고, 필요한 스크립트나 스타일시트를 순서대로 로드합니다.
> 하지만 preload를 사용하면, 중요한 리소스를 우선적으로 로드하도록 **큐에서 뛰어넘을 수 있습니다**.
## 리스트 가상화

대규모 데이터 리스트의 렌더링 성능을 향상시키는 기술
현재 화면에 보이는 행만 동적으로 렌더링
`react-virtualized` 같은 라이브러리를 사용하여 구현
스크롤되는 뷰포트 내에서 현재 보이는 항목만 렌더링하므로, 한 번에 수천 개의 행 데이터를 렌더링하는 데 드는 리소스를 절약

### 윈도잉/가상화의 작동 방식

`react-virtualized`에서의 작동 방식

https://github.com/user-attachments/assets/c4afb058-8c99-4adb-8a04-1a99cc89949f

윈도잉이 동작하기 위해 필요한 요소

- 스크롤을 위한 큰 DOM 요소
- `position: relative`를 가지는 작은 컨테이너 DQM 요소 (영상에서 ul에 해당)
- 컨테이너 내부에 위치하고 `position: absolute`이며 `top`, `left`, `width`, `height` 등을 설정한 자식 요소들

[react-window](https://github.com/bvaughn/react-window)[react-virtualized](https://github.com/bvaughn/react-virtualized) 개발자 Brian Vaughn이 다시 만든 라이브러리
smaller, faster, more tree-shakeable, beginner-friendly

> 두 라이브러리가 어떻게 다른지 개발자가 직접 설명한 [](https://github.com/bvaughn/react-window#how-is-react-window-different-from-react-virtualized)이 있네요 (둘 중에는 react-window를 사용하라고 권장하고 있습니다)
>
> 토스증권에서는 `react-virtuoso`, `@tanstack/react-virtual`을 상황에 맞게 사용 중이라고 해요
> `react-virtuoso`는 선언적이고, `@tanstack/react-virtual`은 headless라서 커스텀 하기 좋다고 합니다
>
> https://npmtrends.com/@tanstack/react-virtual-vs-react-virtualized-vs-react-virtuoso-vs-react-window
> 4가지를 같이 비교해 보니, 확실히 `react-virtuoso`, `@tanstack/react-virtual`이 가볍긴 하네요
> ![image](https://github.com/user-attachments/assets/8b6eeeb7-e507-4119-b22c-6c79f624bf8f)
### 웹 플랫폼의 발전

`content-visibility: auto`를 설정하면 화면 밖 콘텐츠의 렌더링과 페인팅을 필요한 시점까지 지연할 수 있음
렌더링 비용이 큰 HTML 문서에 적용하면 좋음
동적인 콘텐츠 목록을 렌더링하는 경우에는 `react-window` 같은 라이브러리 사용하는 게 좋음

> https://developer.mozilla.org/en-US/docs/Web/CSS/content-visibility
> [(번역) CSS content-visibility를 이용해 렌더링 성능 향상 시키기](https://velog.io/@superlipbalm/improving-rendering-performance-with-css-content-visibility)

0 comments on commit 195e7f8

Please sign in to comment.