Initial implementation of the project structure and basic functionality.
This commit is contained in:
164
docs/curriculum/02-rendering-lifecycle.md
Normal file
164
docs/curriculum/02-rendering-lifecycle.md
Normal file
@@ -0,0 +1,164 @@
|
||||
# 2. Rendering 흐름 & Lifecycle
|
||||
|
||||
## SSR → Hydration → CSR 전환 흐름
|
||||
|
||||
Nuxt SSR은 세 단계로 구성된다.
|
||||
|
||||
```
|
||||
[브라우저 요청]
|
||||
↓
|
||||
[서버] setup() 실행 → HTML 생성 → 클라이언트로 전송
|
||||
↓
|
||||
[브라우저] HTML 즉시 표시 (사용자에게 보임)
|
||||
↓
|
||||
[브라우저] Vue JS 로드 → Hydration (서버 HTML에 이벤트 연결)
|
||||
↓
|
||||
[브라우저] onMounted() 실행 → 완전한 SPA처럼 동작
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Lifecycle 훅 실행 위치
|
||||
|
||||
| 훅 | 서버 | 클라이언트 | 설명 |
|
||||
|----|------|------------|------|
|
||||
| `setup()` | ✅ | ✅ | 컴포넌트 초기화, 양쪽 모두 실행 |
|
||||
| `onServerPrefetch()` | ✅ | ❌ | 서버에서만 실행, SSR용 데이터 페칭 |
|
||||
| `onBeforeMount()` | ❌ | ✅ | DOM 마운트 직전 |
|
||||
| `onMounted()` | ❌ | ✅ | DOM 마운트 완료, 브라우저 API 사용 가능 |
|
||||
| `onBeforeUnmount()` | ❌ | ✅ | 컴포넌트 제거 직전 |
|
||||
| `onUnmounted()` | ❌ | ✅ | 컴포넌트 제거 완료 |
|
||||
|
||||
---
|
||||
|
||||
## 실전 코드 예시
|
||||
|
||||
### setup()에서의 서버/클라이언트 분기
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
// setup()은 서버와 클라이언트 양쪽에서 실행됨
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
// 서버/클라이언트 환경 확인
|
||||
if (import.meta.server) {
|
||||
console.log('서버에서만 출력')
|
||||
}
|
||||
|
||||
if (import.meta.client) {
|
||||
console.log('클라이언트에서만 출력')
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### onServerPrefetch — 서버에서 데이터 미리 준비
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
const data = ref(null)
|
||||
|
||||
// 서버에서 먼저 데이터를 가져오고, 클라이언트로 상태 전달
|
||||
onServerPrefetch(async () => {
|
||||
data.value = await $fetch('/api/products')
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
> `useAsyncData` / `useFetch`가 내부적으로 `onServerPrefetch`를 사용한다.
|
||||
> 직접 쓸 일은 드물지만, 동작 원리 이해에 중요하다.
|
||||
|
||||
### onMounted — 브라우저 전용 작업
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
// ❌ 잘못된 예: setup()에서 window 접근 → 서버에서 오류 발생
|
||||
// const width = window.innerWidth
|
||||
|
||||
// ✅ 올바른 예: onMounted에서 브라우저 API 사용
|
||||
const windowWidth = ref(0)
|
||||
|
||||
onMounted(() => {
|
||||
windowWidth.value = window.innerWidth
|
||||
|
||||
// 이벤트 리스너, setTimeout, localStorage 등 모두 여기서
|
||||
window.addEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
// 반드시 정리해야 메모리 누수를 막는다
|
||||
window.removeEventListener('resize', handleResize)
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Hydration이란?
|
||||
|
||||
서버가 생성한 정적 HTML에 Vue의 반응형 시스템과 이벤트 핸들러를 연결하는 과정이다.
|
||||
|
||||
```
|
||||
서버 HTML: <button>클릭</button> ← 이벤트 없음
|
||||
↓ Hydration
|
||||
클라이언트: <button @click="handleClick">클릭</button> ← 이벤트 연결됨
|
||||
```
|
||||
|
||||
### Hydration 불일치(Mismatch) 주의
|
||||
|
||||
서버와 클라이언트에서 다른 결과를 렌더링하면 경고가 발생한다.
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
// ❌ 문제: 서버(undefined)와 클라이언트(실제값) 결과가 다름
|
||||
const isClient = ref(typeof window !== 'undefined')
|
||||
|
||||
// ✅ 해결: ClientOnly 컴포넌트 사용
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 브라우저에서만 렌더링해야 하는 컴포넌트 -->
|
||||
<ClientOnly>
|
||||
<BrowserOnlyComponent />
|
||||
<template #fallback>
|
||||
<p>로딩 중...</p>
|
||||
</template>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Nuxt 전용 훅 (useNuxtApp)
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
const nuxtApp = useNuxtApp()
|
||||
|
||||
// 페이지 전환 이벤트
|
||||
nuxtApp.hook('page:start', () => {
|
||||
console.log('페이지 전환 시작')
|
||||
})
|
||||
|
||||
nuxtApp.hook('page:finish', () => {
|
||||
console.log('페이지 전환 완료')
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 요약
|
||||
|
||||
```
|
||||
서버 실행: setup() → onServerPrefetch()
|
||||
↓
|
||||
HTML 생성 및 전송
|
||||
↓
|
||||
클라이언트 수신: HTML 즉시 표시
|
||||
↓
|
||||
Hydration (JS 연결)
|
||||
↓
|
||||
setup() → onBeforeMount() → onMounted()
|
||||
↓
|
||||
이후 SPA처럼 동작
|
||||
```
|
||||
Reference in New Issue
Block a user