Hone logo
Hone
Problems

Vue KeepAlive Component Implementation

Vue's built-in <keep-alive> component is a powerful tool for optimizing performance by caching component instances. This challenge asks you to reimplement a simplified version of <keep-alive> in TypeScript for Vue 3, allowing you to understand its core mechanics and how it manages component lifecycles.

Problem Description

Your task is to create a Vue 3 component named MyKeepAlive that mimics the basic functionality of Vue's <keep-alive>. This component should wrap other components and cache their instances when they are not currently being displayed. When a previously cached component is shown again, its state should be restored instead of re-initializing it.

Key Requirements:

  1. Caching Mechanism: Store instances of components that are rendered but then unmounted.
  2. Re-rendering: When a cached component is about to be re-rendered, its instance should be retrieved from the cache and mounted again.
  3. State Preservation: The internal state of the cached component should be preserved.
  4. Dynamic Component Support: MyKeepAlive should work with Vue's <component :is="..."> dynamic component rendering.
  5. Multiple Cached Components: It should be able to cache multiple different components.

Expected Behavior:

  • When a component is rendered for the first time, it mounts normally.
  • When the dynamic component changes to a different component, the previously rendered component is cached.
  • When the dynamic component changes back to a previously cached component, the cached instance is re-used, and its state (e.g., form input values, internal component state) is preserved.
  • When a component is no longer needed and is not actively cached (e.g., it's been replaced by several other components and is no longer within the active include or exclude filters, if implemented later), its instance should be destroyed.

Edge Cases:

  • Handling the initial render.
  • Switching between multiple components.
  • Switching back and forth between two specific components repeatedly.

Examples

Let's consider a simple Counter component for demonstration.

<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

<script lang="ts" setup>
import { ref, onMounted, onUnmounted } from 'vue';

const count = ref(0);

const increment = () => {
  count.value++;
};

onMounted(() => {
  console.log('Counter mounted!');
});

onUnmounted(() => {
  console.log('Counter unmounted!');
});
</script>

Example 1: Initial Render and Switch

Input:

A parent component that uses MyKeepAlive to wrap a dynamic component.

<template>
  <div>
    <button @click="showCounter">Show Counter</button>
    <button @click="showAnother">Show Another</button>

    <MyKeepAlive>
      <component :is="currentComponent" />
    </MyKeepAlive>
  </div>
</template>

<script lang="ts" setup>
import { ref, shallowRef, defineComponent } from 'vue';
import CounterComponent from './Counter.vue'; // Assume Counter.vue is defined as above

const currentComponent = shallowRef(null);

const AnotherComponent = defineComponent({
  template: '<div>This is another component.</div>',
  setup() {
    console.log('AnotherComponent initialized!');
  }
});

const showCounter = () => {
  currentComponent.value = CounterComponent;
};

const showAnother = () => {
  currentComponent.value = AnotherComponent;
};

// Initial state
showCounter();
</script>

Expected Output in Console:

  1. Counter mounted!
  2. Counter mounted! (when showCounter is clicked again)
  3. AnotherComponent initialized! (when showAnother is clicked)
  4. Counter unmounted! (when showAnother is clicked, if not cached)
  5. Counter mounted! (when showCounter is clicked again)

Explanation:

When showCounter is called initially, CounterComponent mounts. When showAnother is called, CounterComponent is unmounted and should be cached. When showCounter is called again, the cached CounterComponent instance should be re-mounted without its onMounted hook firing again if it was the first time it was mounted, but its state (the count) should be preserved. For simplicity in this challenge, we'll focus on the core caching.

Example 2: State Preservation

Input:

Using the same parent component and CounterComponent as Example 1.

  1. Click "Show Counter".
  2. Click the "Increment" button within the CounterComponent several times (e.g., count reaches 3).
  3. Click "Show Another".
  4. Click "Show Counter" again.

Expected Behavior:

When "Show Counter" is clicked the second time (after showing "Another"), the CounterComponent should be displayed, and the count should still be 3, not 0. The onMounted hook might log again depending on the exact implementation of re-use vs. recreation.

Constraints

  • Your MyKeepAlive component should be implemented using the Vue 3 Composition API and TypeScript.
  • The component should accept a default slot, where the dynamic component will be placed.
  • You do not need to implement include or exclude props for this challenge.
  • Focus on the core caching logic.

Notes

  • Think about how Vue renders VNodes and how you can intercept and manage component rendering.
  • Consider using the createVNode and render functions from Vue.
  • You'll likely need to maintain a map or object to store the cached component VNodes or instances.
  • Pay attention to the lifecycle hooks (onMounted, onUnmounted) of the components being managed. How can you ensure they are called appropriately upon caching and retrieval?
  • The challenge is about implementing the caching mechanism. The actual dynamic component rendering will be handled by Vue's <component :is="...">. Your MyKeepAlive component's job is to control which component is rendered by the <component> tag and to manage its lifecycle when it's swapped out.
Loading editor...
typescript