'How to destroy/unmount vue.js 3 components?

I have a fairly large vue.js 2 application that has a dynamic tabs mechanism. Users can interact with the application opening and closing tabs, each tab represents a route. To achieve this I'm using vue router and keep alive, like the example bellow.

<template>
  <div id="app">
    <keep-alive>
      <router-view :key="routerViewKey"/>
    </keep-alive>
  </div>
</template>

When users click the close tab button, the $destroy function is called to remove its component from the cache. I'm migrating this application from vue 2 to vue 3, however, reading the breaking changes documentation for vue.js 3 we can see this:

Removed APIs

$destroy instance method. Users should no longer manually manage the lifecycle of individual Vue components.

I didn't find any alternatives so far, so how can I programmatically destroy/unmount a cached component inside keep-alive in vue.js 3?

Edit 1 (Apr/22): So far it's still impossible to achieve what $destroy did on vue.js 2 in vue.js 3. There is currently a RFC to solve this issue (https://github.com/vuejs/rfcs/discussions/283), but unfortunately it's opened for more than a year without any feedback.



Solution 1:[1]

The destroy hook has been replaced with unmounted. I guess you can import unmounted from the composition APIs.

Solution 2:[2]

You can find the unmount command here: https://v3.vuejs.org/api/application-api.html#unmount

Unfortunately, if you want to do it inside your app, the documentation doesn't have any way to do it. However, after analyzing the object, I found a way to do it. You can achieve it by using this: this.$.appContext.app.unmount();

I'm not a huge fan of this solution, given that it can no longer work in a future version, but it works well on my project.

EDIT: Another way is to extend the Vue object according to this: https://github.com/vuejs/vue-next/issues/1802 and https://github.com/pearofducks/mount-vue-component

I have slightly improved the function:

function mount(component, { props, children, element, app } = {}) {
    let el = element
  
    let vNode = createVNode(component, props, children)
    vNode.destroy = () => {
        if (el) render(null, el)
        el = null
        vNode = null
    }
    if (app?._context) vNode.appContext = app._context
    if (el) render(vNode, el)
    else if (typeof document !== 'undefined' ) render(vNode, el = document.createElement('div'))
  
    const destroy = () => {
        if (el) render(null, el)
        el = null
        vNode = null
    }
  
    return { vNode, destroy, el }
}

You can now track which component you have as children, destroy it from the parent AND the children by using this: this.$.vnode.destroy();

However, the new official way seems to use createApp now.

Solution 3:[3]

Try with v-if

<template>
  <div
    v-if="!close"
    :class="`z-50 absolute py-4 mx-4 ${position}`"
    data-testid="notification"
  >
      ...
      <div v-if="enableClose" class="cursor-pointer absolute right-1 top-3 text-gray-500 text-2xl" @click="onClose">
        &times;
      </div>
    </Flash>
  </div>
</template>

<script lang="ts">
import {defineComponent, render} from "vue";
import Flash from "@/core/shared/components/Layout/Flash.vue";

export default defineComponent({
  name: "Toast",
  components: {
    Flash,
  },
  props: {
    ...
    enableClose: {
      type: Boolean,
      default: true,
    },
  },
  computed: {
    ...
  },
  data() {
    return {
      close: false
    };
  },
  methods: {
    onClose() {
      this.close = true;
    },
  },
});
</script>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Cristiano Soleti
Solution 2
Solution 3 PaKo Anguiano