Hone logo
Hone
Problems

Implementing Watcher Cleanup in Vue's watchEffect

Vue's watchEffect is a powerful tool for running side effects in response to reactive state changes. However, sometimes these side effects involve long-running operations or subscriptions that need to be explicitly cleaned up to prevent memory leaks and unexpected behavior. This challenge focuses on correctly implementing cleanup logic for watchEffect.

Problem Description

You are tasked with creating a Vue component that utilizes watchEffect to perform a side effect. This side effect should involve a simulated asynchronous operation (e.g., a timer or a network request). Crucially, you need to implement the cleanup mechanism provided by watchEffect to ensure that when the dependencies of the watchEffect change and it re-runs, or when the component is unmounted, any ongoing asynchronous operations are properly cancelled or terminated.

Key Requirements:

  1. watchEffect Usage: Use watchEffect to observe reactive dependencies.
  2. Asynchronous Operation: Simulate an asynchronous task within the watchEffect callback. A setTimeout is a good candidate for this.
  3. Cleanup Function: Implement the cleanup function returned by watchEffect. This function should stop or cancel the ongoing asynchronous operation.
  4. State Changes: The watchEffect should re-run when its dependencies change, and the cleanup should be triggered before the new effect runs.
  5. Component Unmount: The cleanup should also be triggered when the component is unmounted.

Expected Behavior:

  • When a dependency changes, the old asynchronous operation should be cancelled, and a new one should start.
  • If the component is unmounted while an asynchronous operation is in progress, that operation should be cancelled.
  • No memory leaks should occur due to uncleaned-up asynchronous tasks.

Edge Cases:

  • Consider scenarios where the asynchronous operation completes before the cleanup is triggered.
  • What happens if the cleanup function itself is called multiple times? (Vue's watchEffect handles this internally, but understanding the principle is important).

Examples

Example 1: Simple Timer Cleanup

// Component setup (simplified)
import { ref, watchEffect } from 'vue';

const counter = ref(0);
let timerId: number | null = null;

watchEffect((onCleanup) => {
  console.log(`watchEffect running with counter: ${counter.value}`);

  // Simulate an asynchronous operation (e.g., a timer)
  timerId = setTimeout(() => {
    console.log(`Timer finished for counter: ${counter.value}`);
  }, 1000);

  // Register the cleanup function
  onCleanup(() => {
    console.log(`Cleaning up timer for counter: ${counter.value}`);
    if (timerId) {
      clearTimeout(timerId);
      timerId = null;
    }
  });
});

// To trigger changes:
// counter.value++;

Explanation: Initially, counter is 0. watchEffect runs. A timer is set for 1 second. If counter is incremented before 1 second passes, the onCleanup function will be called, clearing the previous setTimeout. A new timer will then start for the new counter value.

Example 2: Cleanup during Component Unmount

Imagine the component above is mounted, and counter is incremented. A timer starts. Then, the component is unmounted before the timer finishes. The onCleanup function will execute, clearing the setTimeout, preventing the "Timer finished" log from appearing after the component is gone.

Constraints

  • The simulated asynchronous operation should have a duration of at least 500ms to allow for testing cleanup scenarios.
  • The solution should be implemented within a Vue 3 Composition API component.
  • No external libraries beyond Vue are required.
  • The code should be well-typed using TypeScript.

Notes

  • Think about what state changes will cause watchEffect to re-run.
  • The onCleanup function provided to the watchEffect callback is the key to solving this problem.
  • Consider how you would handle more complex asynchronous operations like actual fetch requests. You'd likely need to use AbortController for those.
  • Success means that no console logs related to the timer finishing appear after the watchEffect has been re-run due to a dependency change or after the component has been unmounted.
Loading editor...
typescript