cover

<KeepAlive><component> 컴포넌트와 함께 사용하면서 동적으로 바뀐 컴포넌트가 다시 사라지지 않게 도와주는 컴포넌트입니다. 어떻게 사용하는 지 자세히 알아볼까요?


KeepAlive 컴포넌트

Vue에는 컴포넌트를 is 속성에 따라 다르게 변경시켜주는 component 컴포넌트가 있습니다.

html
      <component :is="compName" />
    

보통은 component가 바뀌면 당연히 원래 컴포넌트는 마운트 해제됩니다. 그리고 다시 해당 컴포넌트로 바꾸면 다시 처음부터 마운트가 되고 상태들이 초기화될겁니다. 공식 홈페이지에서는 KeepAlive를 사용한 예제가 있는데, 그 중 일부를 보겠습니다.

html
      <script setup>
import { ref } from "vue";
import CompA from "./components/CompA.vue";
import CompB from "./components/CompB.vue";

const current = ref(CompA);
</script>

<template>
  <div class="demo">
    <label><input type="radio" v-model="current" :value="CompA" /> A</label>
    <label><input type="radio" v-model="current" :value="CompB" /> B</label>
    <component :is="current"></component>
  </div>
</template>
    

이를 실행하면 다음과 같이 나옵니다.

loading
1. component 컴포넌트로 A 컴포넌트로 바꾼 다음 count 값을 올렸습니다.
2. component 컴포넌트로 B 컴포넌트로 바꾼 다음 “안녕”이란 글자를 적었습니다.
3. component 로 다시 A 컴포넌트로 바꾸니 count가 초기값(0)으로 바뀌어있습니다.
4. component로 다시 B 컴포넌트로 바꾸니 글자가 사라졌습니다.

컴포넌트가 바뀌니 당연히 기존 원래 것이 사라지고 다시 초기화되어 나타납니다. 하지만 이 경우는 기존 컴포넌트가 유지되어 원래 값을 유지하길 바랄겁니다. 이럴 땐 단순히 KeepAlive컴포넌트에 넣어주기만 하면 됩니다.

html
      <KeepAlive>
  <component :is="current" />
</KeepAlive>
    
loading

기존 컴포넌트가 마운트 해제 되지 않고 남아있는 것을 볼 수 있습니다.

info
      만약 컴포넌트의 template 속성을 쓰는 것이라면 <keep-alive> 로 사용해야합니다.
    

Include로 포함할 것들 정하기

KeepAlive는 어떤 컴포넌트든 캐싱하여 마운트해제되지 않도록 해줍니다. 만일 어떤 컴포넌트는 남겨두길 원한다면 include속성을 사용하면 됩니다.

html
      <!-- 여러 개하는 경우 콤마(,)로 구분하면 됩니다. -->
<KeepAlive include="CompA,CompB">
  <component :is="current" />
</KeepAlive>

<!-- v-bind + 정규식도 가능합니다. -->
<KeepAlive :include="/Comp.*/">
  <component :is="current" />
</KeepAlive>

<!-- v-bind + 배열도 가능합니다. -->
<KeepAlive :include="['CompA', 'CompB']">
  <component :is="current" />
</KeepAlive>
    

include에 포함되지 않는 컴포넌트들은 바뀌었을 때 자동으로 마운트 해제됩니다. 이 때 문자열과 비교하는 값은 컴포넌트의 name 속성입니다.

info
      3.2.24 버전부터 <script setup> 은 자동으로 파일이름을 기준으로 name 값을 넣어줍니다.
    

exclude로 제외할 것들 정하기

반대로 exclude로 제외할 것들도 정할 수 있습니다.

html
      <KeepAlive exclude="CompB">
  <component :is="current" />
</KeepAlive>
    

이 경우 컴포넌트가 바뀌면 CompA 컴포넌트는 캐싱되어 유지되고 CompB는 마운트해제 됩니다.

Include와 Exclude 같이 사용하기

물론 includeexclude를 같이 사용할 수 있습니다. 정규식으로 Comp가 들어간 컴포넌트 중에 CompA만 빼고 유지되게 하는 예제입니다.

html
      <KeepAlive :include="/Comp.*/" exclude="CompA">
  <component :is="current"></component>
</KeepAlive>
    

캐싱 최대치 정하기

컴포넌트를 캐싱해두어 나중에 불러와도 내부 상태를 기억하는 것은 좋아보이지만, 기억해야둬야할 컴포넌트가 많을수록 성능에 문제가 생길 수 있습니다. 그래서 이를 max 속성으로 제한둘 수 있습니다.

html
      <KeepAlive :max="5">
  <component :is="current" />
</KeepAlive>
    

이 때 기억하는 방식은 LRU(Least recently used) 방식을 사용합니다. 이해를 돕기 위해 5개로 제한하고 A부터 E까지 컴포넌트를 차례대로 캐싱했다고 해보겠습니다.

javascript
      [A, B, C, D, E]
    

여기서 F라는 새로운 컴포넌트를 캐싱해야한다면, 가장 오래전에 캐싱했던 A를 제거하고 F를 맨 뒤에 넣습니다.

javascript
      // 오래된 A 제거하고 F를 캐싱
[B, C, D, E, F]
    

여기서 D를 다시 캐싱해야한다면 기존 것을 참조해서 맨 뒤에 넣습니다.

javascript
      // 기존 D를 가장 최근으로 옮김
[B, C, E, F, D]
    

다시 정리하자면 max 속성을 정하면 LRU 방식으로 캐싱해서 기억해둡니다. 캐싱되지 않은 컴포넌트는 당연히 다시 새롭게 초기화 됩니다.

Activated 훅과 Deactivated 훅

컴포넌트를 캐싱해서 기억하니 마운트하는 것이 아니라 어디서 불러오는 것일 겁니다. 그래서 mounted / Unmounted 훅을 사용할 수 없습니다. 대신 Activated / Deactivated훅을 사용하면 됩니다.

html
      <template>
  <p>Comp A</p>
</template>

<script setup>
import { onMounted, onUnmounted, onActivated, onDeactivated } from "vue";

onMounted(() => {
  console.log("CompA Mounted");
});

onUnmounted(() => {
  console.log("CompA Unmounted");
});

onActivated(() => {
  console.log("CompA Activated");
});

onDeactivated(() => {
  console.log("CompA Deactivated");
});
</script>
    
loading

처음에 컴포넌트를 불러올 떄에는 mountedactivated훅이 불러와지고 바뀌면서 deactivated, activated가 번갈아가면서 호출되는 것을 볼 수 있습니다.

만약 CompB가 CompA의 자식 컴포넌트로 들어갔다면 훅이 같이 호출되며 그 순서는 자식이 먼저입니다.

html
      <template>
  <p>
    Comp A
    <CompB />
  </p>
</template>

<script setup>
import CompB from "./CompB.vue";
import { onMounted, onUnmounted, onActivated, onDeactivated } from "vue";

onMounted(() => {
  console.log("CompA Mounted");
});

onUnmounted(() => {
  console.log("CompA Unmounted");
});

onActivated(() => {
  console.log("CompA Activated");
});

onDeactivated(() => {
  console.log("CompA Deactivated");
});
</script>
    
loading