generated from muhandojeon/study-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
290 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |