Hone logo
Hone
Problems

Vue Watch Source Implementation

Vue's watch option is a powerful feature for reacting to data changes. While directly watching reactive properties or computed properties is common, watch also supports more advanced "source" definitions, allowing you to watch complex expressions or even other watchers. This challenge will focus on implementing a core aspect of this advanced watch functionality.

Problem Description

Your task is to implement a function that simulates the behavior of Vue's watch option when provided with a "source" function. This function will accept a callback and return an object with an unwatch method to stop observing.

Key Requirements:

  1. Source Function: The watch function should accept a source argument, which can be a function.
  2. Callback Execution: When the source function returns a new value, the provided callback function should be executed with the new value and the old value.
  3. Initial Execution: The callback should be executed immediately with the initial value returned by the source function and undefined as the old value.
  4. Unwatching: The watch function must return an object with an unwatch method. Calling this method should stop further executions of the callback.
  5. Dependency Tracking (Simulated): For this challenge, you don't need to implement full-blown Vue reactivity. Assume that changes within the source function will trigger a re-evaluation when necessary. You'll focus on the mechanism of detecting changes and calling the callback.

Expected Behavior:

The watch function should monitor the value returned by the source function. When this value changes, the callback is invoked. The callback should receive two arguments: the new value and the previous value. The first invocation of the callback should happen synchronously upon setting up the watcher.

Edge Cases:

  • What happens if the source function throws an error? (For this challenge, you can assume the source function will not throw errors, or if it does, the watcher will stop.)
  • What if the source function returns the same value multiple times in a row? The callback should not be executed if the value hasn't changed.

Examples

Example 1:

// Simulate a simple reactive variable (for demonstration purposes)
let counter = 0;
const incrementCounter = () => {
  counter++;
  console.log('Counter:', counter);
};

const sourceFn = () => counter;
const callback = (newValue: number, oldValue: number) => {
  console.log(`Counter changed from ${oldValue} to ${newValue}`);
};

// Assume a function `watch` is defined as per the problem
const unwatch = watch(sourceFn, callback);

// Simulate a change
incrementCounter();
// Expected Console Output (order may vary slightly due to synchronous nature of initial call):
// Counter: 1
// Counter changed from undefined to 1
// Counter: 2
// Counter changed from 1 to 2

// Clean up
unwatch();

Explanation: Initially, counter is 0. The sourceFn returns 0. The callback is called with newValue: 0, oldValue: undefined. Then, incrementCounter is called, changing counter to 1. sourceFn now returns 1. The callback is called with newValue: 1, oldValue: 0. This process continues. Calling unwatch() stops further executions.

Example 2:

const data = {
  user: {
    name: 'Alice',
    age: 30,
  },
};

const sourceFn = () => data.user.age;
const callback = (newValue: number, oldValue: number) => {
  console.log(`User age changed from ${oldValue} to ${newValue}`);
};

const unwatch = watch(sourceFn, callback);

// Simulate a change
data.user.age = 31;
// Expected Console Output:
// User age changed from undefined to 30 (initial call)
// User age changed from 30 to 31

// Simulate no change
data.user.age = 31;
// No callback execution

// Simulate another change
data.user.age = 32;
// Expected Console Output:
// User age changed from 31 to 32

unwatch();

Explanation: The sourceFn watches data.user.age. Initially, it's 30, so the callback runs with undefined and 30. When age becomes 31, the callback runs with 30 and 31. A subsequent attempt to set age to 31 again does not trigger the callback because the value hasn't changed. Finally, setting age to 32 triggers the callback.

Constraints

  • The watch function will be called with a source that is a function and a callback that is also a function.
  • The source function is guaranteed to return a value.
  • The number of watchers created and the frequency of changes will not exceed typical application usage.
  • The implementation should be efficient in terms of memory and CPU usage, though no specific time complexity is mandated for this simulation.

Notes

  • Think about how you would detect a change in the value returned by the source function. You'll need to store the previous value somewhere.
  • The initial call to the callback is crucial for setting up the initial state.
  • Consider how to ensure that the unwatch function correctly cleans up any ongoing observation.
  • This challenge is about the pattern of watching and reacting, not about building a full reactive system. You're simulating the observable behavior.
Loading editor...
typescript