KeepAlive
KeepAlive는 동적 컴포넌트를 전환할 때 비활성 컴포넌트의 상태를 메모리에 캐싱하여, 다시 돌아왔을 때 상태를 복원하는 내장 컴포넌트입니다.
면접에서 "탭 전환 시 입력값이 사라지지 않게 하려면 어떻게 하나요?"라는 질문에 KeepAlive를 설명할 수 있어야 합니다.
기본 사용법
<script setup lang="ts">
import { shallowRef } from 'vue'
import TabA from './TabA.vue'
import TabB from './TabB.vue'
import TabC from './TabC.vue'
const currentTab = shallowRef(TabA)
const tabs = [
{ label: 'A', component: TabA },
{ label: 'B', component: TabB },
{ label: 'C', component: TabC }
]
</script>
<template>
<div>
<button v-for="tab in tabs" :key="tab.label" @click="currentTab = tab.component">
{{ tab.label }}
</button>
</div>
<!-- KeepAlive 없이 — 전환 시 상태가 사라짐 -->
<!-- <component :is="currentTab" /> -->
<!-- KeepAlive로 감싸기 — 상태가 캐싱됨 -->
<KeepAlive>
<component :is="currentTab" />
</KeepAlive>
</template>
include / exclude — 선택적 캐싱
<template>
<!-- 이름으로 특정 컴포넌트만 캐싱 -->
<KeepAlive include="TabA,TabB">
<component :is="currentTab" />
</KeepAlive>
<!-- 배열로도 가능 -->
<KeepAlive :include="['TabA', 'TabB']">
<component :is="currentTab" />
</KeepAlive>
<!-- 정규식으로 매칭 -->
<KeepAlive :include="/^Tab/">
<component :is="currentTab" />
</KeepAlive>
<!-- 특정 컴포넌트 제외 -->
<KeepAlive exclude="HeavyChart">
<component :is="currentTab" />
</KeepAlive>
</template>
컴포넌트에 name 옵션이 있어야 include/exclude가 동작합니다.
<script setup lang="ts">
defineOptions({
name: 'TabA' // KeepAlive의 include/exclude에서 참조
})
</script>
max — 최대 캐시 인스턴스 수
<template>
<!-- LRU(Least Recently Used) 방식으로 관리 -->
<!-- 최대 10개까지 캐싱, 초과 시 가장 오래된 것부터 파괴 -->
<KeepAlive :max="10">
<component :is="currentTab" />
</KeepAlive>
</template>
생명주기 — activated / deactivated
KeepAlive로 캐싱된 컴포넌트는 mounted/unmounted 대신 activated/deactivated가 호출됩니다.
<script setup lang="ts">
import { onMounted, onUnmounted, onActivated, onDeactivated, ref } from 'vue'
const data = ref(null)
onMounted(() => {
// 처음 한 번만 호출됨
console.log('최초 마운트')
})
onActivated(() => {
// 활성화될 때마다 호출 (캐시에서 복원 시)
console.log('활성화됨 — 데이터 갱신')
// 필요 시 데이터 새로고침
// fetchLatestData()
})
onDeactivated(() => {
// 비활성화될 때 호출 (캐시로 이동 시)
console.log('비활성화됨')
// 타이머 정지, 구독 일시 중지 등
})
onUnmounted(() => {
// KeepAlive에서 제거될 때만 호출 (max 초과 등)
console.log('완전히 파괴됨')
})
</script>
Vue Router와 함께 사용
<!-- App.vue -->
<template>
<RouterView v-slot="{ Component, route }">
<KeepAlive :include="cachedRoutes">
<component :is="Component" :key="route.path" />
</KeepAlive>
</RouterView>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute } from 'vue-router'
// 메타 필드로 캐싱 여부 제어
const route = useRoute()
// 라우트 설정에서 캐싱할 컴포넌트 이름 관리
const cachedRoutes = ['ListView', 'SearchView']
</script>
// router/index.ts
const routes = [
{
path: '/list',
component: () => import('@/views/ListView.vue'),
meta: { keepAlive: true } // 캐싱 대상 표시
},
{
path: '/detail/:id',
component: () => import('@/views/DetailView.vue'),
meta: { keepAlive: false }
}
]
실전 시나리오 — 목록 → 상세 → 목록
<script setup lang="ts">
import { ref, onActivated } from 'vue'
defineOptions({ name: 'ListView' })
const items = ref([])
const scrollPosition = ref(0)
const listRef = ref<HTMLElement>()
// 상세에서 돌아왔을 때 스크롤 위치와 데이터 복원
onActivated(() => {
if (listRef.value) {
listRef.value.scrollTop = scrollPosition.value
}
})
const saveScroll = () => {
scrollPosition.value = listRef.value?.scrollTop || 0
}
</script>
<template>
<div ref="listRef" @scroll="saveScroll" style="overflow-y: auto; height: 100vh;">
<div v-for="item in items" :key="item.id">
<RouterLink :to="`/detail/${item.id}`">{{ item.title }}</RouterLink>
</div>
</div>
</template>
면접 팁
- KeepAlive는 메모리와 UX의 트레이드오프 입니다. 캐싱이 많을수록 메모리를 더 사용하므로
max옵션으로 제한하세요 onActivated에서 데이터 갱신 여부를 판단 하는 로직이 실무에서 중요합니다 — 오래된 데이터를 보여주면 안 되니까요- 목록 → 상세 → 목록 패턴에서 스크롤 위치 복원 은 UX 관점에서 매우 중요한 포인트입니다
요약
KeepAlive는 동적 컴포넌트의 상태를 캐싱하여 전환 시 상태를 유지합니다. include/exclude로 선택적 캐싱, max로 캐시 크기를 제한하고, activated/deactivated 훅으로 활성화 시점을 감지합니다. Vue Router와 함께 사용하면 목록-상세 패턴에서 UX를 크게 개선할 수 있습니다.
댓글 로딩 중...