KeepAlive는 동적 컴포넌트를 전환할 때 비활성 컴포넌트의 상태를 메모리에 캐싱하여, 다시 돌아왔을 때 상태를 복원하는 내장 컴포넌트입니다.

면접에서 "탭 전환 시 입력값이 사라지지 않게 하려면 어떻게 하나요?"라는 질문에 KeepAlive를 설명할 수 있어야 합니다.


기본 사용법

VUE
<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 — 선택적 캐싱

VUE
<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가 동작합니다.

VUE
<script setup lang="ts">
defineOptions({
  name: 'TabA'  // KeepAlive의 include/exclude에서 참조
})
</script>

max — 최대 캐시 인스턴스 수

VUE
<template>
  <!-- LRU(Least Recently Used) 방식으로 관리 -->
  <!-- 최대 10개까지 캐싱, 초과 시 가장 오래된 것부터 파괴 -->
  <KeepAlive :max="10">
    <component :is="currentTab" />
  </KeepAlive>
</template>

생명주기 — activated / deactivated

KeepAlive로 캐싱된 컴포넌트는 mounted/unmounted 대신 activated/deactivated가 호출됩니다.

VUE
<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와 함께 사용

VUE
<!-- 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>
TYPESCRIPT
// 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 }
  }
]

실전 시나리오 — 목록 → 상세 → 목록

VUE
<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를 크게 개선할 수 있습니다.

댓글 로딩 중...