컴포넌트 LifeCycle & Hook
Nuxt 3은 Vue 3의 Composition API를 기반으로 하며, 이에 따라 Vue 3의 컴포넌트 라이프사이클을 그대로 상속합니다. 이 라이프사이클 훅들은 컴포넌트의 생성부터 파괴까지 다양한 시점에 코드를 실행할 수 있게 해줍니다.
다음은 Vue 3 / Nuxt 3 컴포넌트 라이프사이클의 주요 훅들입니다:
-
setup()
: 컴포넌트가 초기화되는 단계입니다. 이 훅은beforeCreate
와created
훅 사이에서 실행되며, props, context, reactive state 등을 설정합니다. -
onBeforeMount()
: 컴포넌트가 DOM에 마운트되기 바로 전에 호출됩니다. -
onMounted()
: 컴포넌트가 DOM에 마운트된 후에 호출됩니다. 이 훅은 DOM이나 자식 컴포넌트에 접근해야 할 때 유용합니다. -
onBeforeUpdate()
: 반응형 데이터가 변경되어 업데이트 사이클이 시작되기 전에 호출됩니다. -
onUpdated()
: 컴포넌트와 그 자식들이 업데이트된 후에 호출됩니다. -
onBeforeUnmount()
: 컴포넌트가 언마운트되기 바로 전에 호출됩니다. 이 훅은 이벤트 리스너나 타이머를 제거하는 데 사용할 수 있습니다. -
onUnmounted()
: 컴포넌트가 언마운트된 후에 호출됩니다. 이 훅은 컴포넌트가 더 이상 활성화되지 않을 때 필요한 정리 작업을 수행하는 데 사용됩니다.
또한, onErrorCaptured()
와 onRenderTracked()
, onRenderTriggered()
같은 고급 훅들도 제공되며, 이는 에러 처리나 디버깅에 유용합니다.
여기에 setup()
함수 안에서 라이프사이클 훅을 사용하는 간단한 예시가 있습니다:
<template>
<div>{{ count }}</div>
</template>
<script setup>
import { ref, onMounted, onUpdated, onBeforeUnmount } from 'vue'
const count = ref(0)
// 마운트될 때 실행할 작업
onMounted(() => {
console.log('컴포넌트가 마운트되었습니다.')
setInterval(() => {
count.value++
}, 1000)
})
// 데이터가 업데이트될 때 실행할 작업
onUpdated(() => {
console.log('컴포넌트가 업데이트되었습니다. 현재 count는:', count.value)
})
// 언마운트되기 전 실행할 작업
onBeforeUnmount(() => {
console.log('컴포넌트가 언마운트됩니다.')
})
</script>
Nuxt 3에서는 이러한 훅들을 사용하여 SSR을 구현할 때 서버 사이드와 클라이언트 사이드 모두에서 실행되는 코드를 관리하거나, 클라이언트 사이드에서만 실행되어야 하는 동작을 정의할 수 있습니다. 예를 들어, onMounted
는 클라이언트 사이드에서 컴포넌트가 마운트될 때만 실행되기 때문에, 서버 사이드 렌더링에서는 호출되지 않습니다.
Composition API 설명
Nuxt 3은 Vue 3 기반으로 구축되었으며, Vue 3의 Composition API를 완벽하게 지원합니다. Composition API는 데이터와 메서드를 조직하는 새로운 방식을 제공하여, 재사용 가능하고 관리하기 쉬운 코드를 작성할 수 있게 해줍니다. 이 API는 기존의 Options API보다 더 유연하고 구성이 용이한 방식으로 컴포넌트의 로직을 구성할 수 있게 해줍니다.
Composition API 주요 특징
-
Reactive State:
ref
와reactive
함수를 사용하여 반응형 데이터 상태를 만들 수 있습니다.ref
는 기본형 데이터를 반응형으로 만들고,reactive
는 객체 데이터를 반응형으로 만듭니다. -
Computed Properties:
computed
함수를 사용하여 반응형으로 계산된 속성을 만들 수 있습니다. -
Watchers:
watch
와watchEffect
함수로 반응형 데이터의 변화를 관찰하고 그에 반응하는 로직을 실행할 수 있습니다. -
Lifecycle Hooks: Composition API에서는
onMounted
,onUpdated
,onUnmounted
등의 라이프사이클 훅을 사용할 수 있습니다. -
Provide / Inject: 컴포넌트 트리에서 깊은 곳에 있는 컴포넌트에 데이터를 전달하기 위해
provide
와inject
함수를 사용할 수 있습니다. -
Custom Composition Functions: 로직을 추상화하여 재사용 가능한 함수로 만들 수 있으며, 이를 다른 컴포넌트에서 재사용할 수 있습니다.
Nuxt 3에서의 사용 예
Nuxt 3 컴포넌트 내에서 Composition API를 사용하는 기본적인 예시는 다음과 같습니다:
<template>
<div>{{ count }}</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const count = ref(0)
onMounted(() => {
console.log('The component has been mounted.')
})
</script>
위 코드에서 <script setup>
은 Composition API를 사용하겠다는 선언이며, ref
를 사용하여 반응형 데이터 count
를 만들고, onMounted
훅을 사용하여 컴포넌트가 마운트될 때 콘솔에 메시지를 출력합니다.
Nuxt 3에서는 이와 같은 Composition API를 사용하여 서버 측과 클라이언트 측 로직을 모두 통합할 수 있습니다. 예를 들어, 서버 측에서 데이터를 가져와 클라이언트 측에서 반응형으로 만들고, 이를 템플릿에서 직접 사용할 수 있습니다. 또한 Nuxt 3는 서버 사이드 렌더링(SSR)과 정적 사이트 생성(SSG)을 위한 특별한 기능도 함께 제공합니다.
반응형 데이터
Nuxt 3에서 ref
, reactive
, 그리고 readonly
는 Vue 3의 Composition API를 활용한 반응형 데이터 관리를 위한 핵심 함수들입니다. 각각의 함수는 특정한 사용 사례와 특징을 가지고 있습니다.
1. ref()
ref()
함수는 어떤 타입의 값이든 반응형 참조로 만들 수 있습니다. 주로 기본 타입(예: 문자열, 숫자, 불리언)을 반응형 데이터로 만들 때 사용됩니다. ref
는 .value
프로퍼티를 통해 해당 값을 감싸고, 그 값을 통해 반응성을 관리합니다.
import { ref } from 'vue'
export default {
setup() {
const count = ref(0);
function increment() {
count.value++;
}
return { count, increment };
}
}
위 예제에서 count
는 0으로 초기화된 반응형 참조입니다. 이 값을 변경하면 Nuxt/Vue는 관련된 DOM을 자동으로 업데이트합니다.
2. reactive()
reactive()
함수는 객체 또는 배열과 같은 복합 타입의 값을 반응형 객체로 만듭니다. 이 함수를 사용할 때는 내부 속성들이 반응형으로 변환되고, 이 객체의 변경사항이 뷰에 자동으로 반영됩니다.
import { reactive } from 'vue'
export default {
setup() {
const state = reactive({
count: 0
});
function increment() {
state.count++;
}
return { state, increment };
}
}
state
객체는 reactive
를 통해 생성된 반응형 객체입니다. state.count
의 값을 변경하면 이에 응답하여 자동으로 업데이트됩니다.
3. readonly()
readonly()
함수는 reactive
나 ref
를 통해 만들어진 반응형 객체를 수정할 수 없는 읽기 전용 상태로 만듭니다. 이는 데이터를 노출시키되, 외부에서 데이터를 변경하지 못하게 할 때 유용합니다. 이 읽기 전용 객체는 원본 데이터가 변경될 때 업데이트됩니다.
import { reactive, readonly } from 'vue'
export default {
setup() {
const state = reactive({
count: 0
});
const stateAsReadonly = readonly(state);
return { state, stateAsReadonly };
}
}
위 예제에서 stateAsReadonly
는 state
의 읽기 전용 버전입니다. stateAsReadonly.count
를 직접 수정하려고 하면 경고가 표시되며, 변경이 적용되지 않습니다.
이 함수들을 사용함으로써, Nuxt 3와 Vue 3 애플리케이션에서 다양한 상태 관리 패턴을 유연하게 적용할 수 있습니다. 데이터 흐름을 명확하게 하고, 예측 가능하며 유지보수하기 쉬운 코드베이스를 구성하는 데 큰 도움이 됩니다.
Prop / Emit
Nuxt 3에서도 Vue 3의 컴포넌트 모델을 그대로 사용하며, 컴포넌트 간의 데이터와 이벤트 전달에 props
와 emit
을 활용합니다. 이 두 메커니즘은 부모-자식 컴포넌트 간의 커뮤니케이션을 위한 기본적인 방법입니다.
Props
props
는 부모 컴포넌트로부터 자식 컴포넌트로 데이터를 전달할 때 사용합니다. 자식 컴포넌트는 props
를 정의하여 이 값을 받을 수 있습니다.
<!-- ParentComponent.vue -->
<template>
<ChildComponent :user="userData" />
</template>
<script setup>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';
const userData = ref({ name: 'Alice', age: 30 });
</script>
<!-- ChildComponent.vue -->
<template>
<div>{{ user.name }} - {{ user.age }}</div>
</template>
<script setup>
import { defineProps } from 'vue';
const props = defineProps({
user: Object
});
</script>
위의 예에서, ParentComponent
는 userData
라는 반응형 데이터를 가지고 있고, 이를 ChildComponent
의 user
prop으로 전달합니다.
Emit
emit
은 자식 컴포넌트가 부모 컴포넌트로 이벤트를 전송할 때 사용합니다. 이를 위해 자식 컴포넌트는 defineEmits
를 사용하여 이벤트를 정의할 수 있습니다.
<!-- ChildComponent.vue -->
<template>
<button @click="updateUser">Update User</button>
</template>
<script setup>
import { defineEmits } from 'vue';
const emit = defineEmits(['user-updated']);
function updateUser() {
// ... 사용자 업데이트 로직
emit('user-updated', { name: 'Bob', age: 25 });
}
</script>
<!-- ParentComponent.vue -->
<template>
<ChildComponent :user="userData" @user-updated="handleUserUpdated" />
</template>
<script setup>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';
const userData = ref({ name: 'Alice', age: 30 });
function handleUserUpdated(updatedUser) {
userData.value = updatedUser;
}
</script>
ChildComponent
에서 버튼 클릭 이벤트가 발생하면 updateUser
함수가 호출되고, emit
을 통해 user-updated
이벤트가 부모 컴포넌트로 전달됩니다. ParentComponent
에서는 handleUserUpdated
함수를 통해 이 이벤트를 처리하고, userData
를 업데이트합니다.
이렇게 props
와 emit
을 사용하여 컴포넌트 간의 데이터 흐름과 이벤트 처리를 쉽게 관리할 수 있습니다. Nuxt 3는 이러한 Vue의 패턴을 그대로 따르므로, Vue에 익숙한 개발자라면 Nuxt 3에서도 빠르게 개발을 진행할 수 있습니다.
레이아웃
Nuxt 3은 페이지의 레이아웃을 정의하는 강력하고 유연한 시스템을 제공합니다. 레이아웃은 웹 애플리케이션의 전체적인 페이지 구조를 정의하는 데 사용되며, 여러 페이지에 걸쳐 공통되는 디자인 요소들을 쉽게 관리할 수 있게 해줍니다. 예를 들어, 헤더, 사이드바, 푸터 등의 UI 컴포넌트가 모든 페이지에 공통적으로 필요하다면 레이아웃을 통해 이를 구현할 수 있습니다.
기본 레이아웃
Nuxt 3 프로젝트를 생성하면, layouts
디렉토리가 기본적으로 생성됩니다. 이 디렉토리 안에는 애플리케이션의 기본 레이아웃을 정의하는 default.vue
파일이 있습니다.
<!-- layouts/default.vue -->
<template>
<div>
<Header />
<Nuxt />
<Footer />
</div>
</template>
<script setup>
import Header from '@/components/Header.vue'
import Footer from '@/components/Footer.vue'
</script>
<Nuxt />
컴포넌트는 실제 페이지 컴포넌트가 렌더링되는 자리 표시자 역할을 합니다. 즉, 각 페이지의 내용은 <Nuxt />
태그의 위치에 표시됩니다.
사용자 정의 레이아웃
사용자는 layouts
디렉토리 안에 새로운 레이아웃 파일을 추가함으로써, 다양한 레이아웃을 정의할 수 있습니다. 예를 들어, blog.vue
레이아웃을 만들고 싶다면 다음과 같이 할 수 있습니다.
<!-- layouts/blog.vue -->
<template>
<div>
<BlogHeader />
<Nuxt />
<BlogFooter />
</div>
</template>
<script setup>
import BlogHeader from '@/components/BlogHeader.vue'
import BlogFooter from '@/components/BlogFooter.vue'
</script>
이렇게 정의된 레이아웃은 페이지 컴포넌트에서 layout
속성을 사용해 적용할 수 있습니다.
<!-- pages/posts.vue -->
<template>
<!-- 페이지 컴포넌트의 내용 -->
</template>
<script setup>
export default {
layout: 'blog'
}
</script>
레이아웃 변경
Nuxt 3에서는 프로그래매틱하게 레이아웃을 변경할 수도 있습니다. 이를 위해 useLayout
함수를 사용할 수 있습니다.
const { setLayout } = useLayout()
setLayout('blog') // 레이아웃을 'blog'로 변경합니다.
에러 레이아웃
에러 핸들링을 위해 Nuxt 3는 layouts/error.vue
파일을 사용하여 에러 레이아웃을 정의할 수 있습니다. 이 레이아웃은 앱에서 어떠한 에러가 발생했을 때 표시됩니다.
<!-- layouts/error.vue -->
<template>
<div>
<h1>Error occurred!</h1>
<p>{{ error.message }}</p>
</div>
</template>
<script setup>
import { useError } from 'nuxt/app'
const error = useError()
</script>
Nuxt 3 레이아웃 시스템은 매우 유연하고, 다양한 페이지와 앱 상태에 따라 쉽게 레이아웃을 조정하거나 변경할 수 있게 해줍니다. 이는 더 일관되고 효율적인 사용자 경험을 제공하며, 개발자는 코드 중복
을 최소화하면서도 프로젝트의 유지보수성을 높일 수 있습니다.
중첩 라우팅
Nuxt 3에서 중첩 라우팅(Nested Routes)은 디렉토리 구조와 파일 이름을 사용하여 매우 직관적으로 설정할 수 있습니다. 중첩된 라우트를 만들기 위해, pages
디렉토리 내에 경로 구조에 맞게 폴더와 파일을 생성하면 Nuxt는 이 구조를 기반으로 자동으로 라우트를 생성합니다.
예를 들어, 다음과 같은 파일 구조가 있다고 가정해 봅시다:
pages/
--| users/
-----| _id.vue
-----| index.vue
--| users.vue
이 구조는 다음과 같은 라우트를 생성합니다:
/users
:users.vue
에 의해 처리됩니다./users/index
:users/index.vue
에 의해 처리됩니다. (일반적으로/users
와 동일)/users/:id
:users/_id.vue
에 의해 처리됩니다.:id
부분은 동적입니다.
여기서 users/index.vue
는 /users
경로에 대한 ‘기본’ 페이지를 제공하고, users/_id.vue
는 /users
와 같은 경로 아래에 있는 동적인 ID를 가진 하위 페이지를 나타냅니다. _
접두사는 해당 파일이나 폴더가 동적 파라미터를 받는다는 것을 Nuxt에 알려줍니다.
중첩된 뷰 구현하기
Nuxt는 자동으로 router-view
를 삽입하여 해당 위치에 맞는 컴포넌트를 렌더링합니다. 하지만 때로는 부모 라우트가 자식 라우트를 직접 제어해야 할 필요가 있습니다. 이를 위해 <NuxtChild>
컴포넌트를 사용합니다.
users.vue
파일을 다음과 같이 작성할 수 있습니다:
<template>
<div>
<h1>Users</h1>
<NuxtChild />
</div>
</template>
이 구조에서 /users
를 방문하면 Users
제목이 표시되고, /users/123
을 방문하면 동일한 Users
제목 아래에 users/_id.vue
컴포넌트의 내용이 NuxtChild
위치에 렌더링됩니다.
동적 중첩 라우트
중첩된 라우트도 동적일 수 있습니다. 예를 들어, 사용자에 대한 프로필 페이지와 사용자가 작성한 게시글 목록 페이지를 중첩된 라우트로 설정하려면 다음과 같은 구조를 만들 수 있습니다:
pages/
--| users/
-----| _id/
--------| index.vue
--------| posts.vue
이 구조는 다음과 같은 라우트를 생성합니다:
/users/:id
:users/_id/index.vue
에 의해 처리됩니다./users/:id/posts
:users/_id/posts.vue
에 의해 처리됩니다.
이렇게 설정하면, 각 사용자 ID에 따라 프로필 페이지와 게시글 목록 페이지를 중첩된 라우트로 표현할 수 있습니다. :id
부분은 사용자 ID로 대체됩니다.
Nuxt 3는 이처럼 파일과 폴더 이름을 사용하여 강력하고 유연한 라우팅 시스템을 제공하며, 개발자는 복잡한 라우팅 설정 없이도 애플리케이션의 라우트를 쉽게 관리할 수 있습니다.