vuejs3

Combining Transition with KeepAlive in Vue3.js


I took this example from Vue SFC Playground and added a Transition. The problem is that in order for the transition to work, I need to provide the div with the changing key () but that breaks the KeepAlive. The CompA and CompB component reloads as if there is no KeepAlive.

<script setup>
import { shallowRef, } from 'vue'
import CompA from './CompA.vue'
import CompB from './CompB.vue'

const current = shallowRef(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>
    <br/>Curent:{{current.__name}}
    <Transition mode="out-in">
      <div :key="current.__name">
        <KeepAlive>
          <component :is="current" :key="current.__name"></component>
        </KeepAlive>
      </div>
    </Transition>
  </div>
</template>

<style>
.v-enter-active,
.v-leave-active {
  transition: opacity 0.5s ease;
}

.v-enter-from,
.v-leave-to {
  opacity: 0;
}
</style>

///CompA.vue:

<script setup>
import { ref } from 'vue'

const count = ref(0)
</script>

<template>
  <p>Current component: A</p>
  <span>count  : {{ count }}</span>
  <button @click="count++">+</button>
</template>

///CompB.vue:

<script setup>
import { ref } from 'vue'
const msg = ref('')
</script>

<template>
  <p>Current component: B</p>
  <span>Message is: {{ msg }}</span>
  <input v-model="msg">
</template>

Solution

  • <Transition> needs the actual components to have a single root element. Simply wrap your CompA and CompB templates with a div, then remove the div between Transition and KeepAlive

    <Transition mode="out-in">
      <KeepAlive>
        <component :is="current" :key="current.__name"></component>
      </KeepAlive>
    </Transition>
    

    CompA.vue:

    <template>
      <div>
        <p>Current component: A</p>
        <span>count  : {{ count }}</span>
        <button @click="count++">+</button>
      </div>
    </template>
    

    CompB.vue:

    <template>
      <div>
        <p>Current component: B</p>
        <span>Message is: {{ msg }}</span>
        <input v-model="msg">
      </div>
    </template>
    

    working Vue Playground