Hone logo
Hone
Problems

Implementing watchEffect with Cleanup in Vue (TypeScript)

watchEffect in Vue is a powerful tool for reacting to changes in reactive sources. However, unlike watch, it doesn't inherently provide a mechanism for cleanup when the effect is no longer needed. This challenge asks you to implement a simplified version of watchEffect that includes a cleanup function, ensuring resources are released when the effect stops tracking. This is crucial for preventing memory leaks and ensuring efficient application performance.

Problem Description

You are tasked with creating a function useWatchEffect that mimics the behavior of Vue's built-in watchEffect but incorporates a cleanup function. The function should accept a callback function as an argument. This callback will be executed whenever any of the reactive sources it accesses change. Crucially, you must also accept an optional cleanup function as a second argument. This cleanup function will be executed before the callback is executed on subsequent runs and when the effect is stopped.

What needs to be achieved:

  • The useWatchEffect function should take a callback and an optional cleanup function.
  • The callback should be executed immediately upon the first call to useWatchEffect.
  • The callback should be re-executed whenever any reactive dependencies it accesses change.
  • The cleanup function (if provided) should be executed before the callback is re-executed and when the effect is stopped.
  • The effect should be automatically stopped when the component unmounts (simulated by a stop function).

Key Requirements:

  • Use a simple dependency tracking mechanism (e.g., using EffectScope from Vue's internals, or a manual implementation). For simplicity, a manual implementation is preferred.
  • The cleanup function should receive the previous return value of the callback.
  • The stop function should prevent further executions of the callback and cleanup.

Expected Behavior:

  1. Initial execution of the callback.
  2. Subsequent executions of the callback when reactive dependencies change.
  3. Execution of the cleanup function before each callback re-execution and upon stopping the effect.
  4. The effect should be stoppable.

Edge Cases to Consider:

  • What happens if the callback doesn't access any reactive dependencies?
  • What happens if the cleanup function throws an error?
  • What happens if the callback returns a value that needs to be passed to the cleanup function?
  • How to handle asynchronous operations within the callback and cleanup functions? (For this simplified version, assume synchronous operations for the callback and cleanup).

Examples

Example 1:

Input:
useWatchEffect(() => {
  state.count++;
  return state.count;
}, (prevCount) => {
  console.log('Cleanup: Removing event listener for count', prevCount);
});

Output:
// Initial execution:
// state.count is incremented
// "Cleanup: Removing event listener for count [initial value]" is logged (if cleanup is called before the first run)
// Subsequent executions (when state.count changes):
// state.count is incremented
// "Cleanup: Removing event listener for count [previous value]" is logged

Explanation: The callback increments state.count and returns its value. The cleanup function logs a message indicating the removal of an event listener, receiving the previous value of state.count.

Example 2:

Input:
const stopEffect = useWatchEffect(() => {
  console.log('Effect running');
}, () => {
  console.log('Cleanup running');
});

stopEffect();

Output:
// Initial execution:
// "Effect running" is logged
// "Cleanup running" is logged
// No further executions of the effect or cleanup

Explanation: The effect is initially executed, then the cleanup function is executed. Calling stopEffect() prevents any further executions.

Example 3: (Edge Case - No Reactive Dependencies)

Input:
useWatchEffect(() => {
  console.log('Effect running - no dependencies');
});

Output:
// "Effect running - no dependencies" is logged once.  No cleanup is needed.

Explanation: The effect runs once, but since it doesn't depend on any reactive values, it won't re-run. No cleanup is performed.

Constraints

  • The useWatchEffect function should return a function stop that, when called, prevents further executions of the callback and cleanup.
  • The cleanup function should receive the previous return value of the callback.
  • The implementation should be reasonably efficient (avoid unnecessary re-renders or computations).
  • Assume state is a reactive object (e.g., from Vue's reactive function) and state.count is a reactive property.
  • The solution must be written in TypeScript.

Notes

  • Consider using a simple array to track reactive dependencies.
  • The stop function should ensure that the callback and cleanup are not executed again.
  • Focus on the core functionality of watchEffect with cleanup. Error handling and advanced features (like deep reactivity) are not required for this challenge.
  • Think about how to efficiently determine when reactive dependencies have changed. A simple comparison of the previous and current values might suffice.
  • This is a simplified implementation; Vue's actual watchEffect is more complex and optimized. The goal is to understand the fundamental concepts.
Loading editor...
typescript