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:
- Source Function: The
watchfunction should accept asourceargument, which can be a function. - Callback Execution: When the
sourcefunction returns a new value, the providedcallbackfunction should be executed with the new value and the old value. - Initial Execution: The
callbackshould be executed immediately with the initial value returned by thesourcefunction andundefinedas the old value. - Unwatching: The
watchfunction must return an object with anunwatchmethod. Calling this method should stop further executions of thecallback. - Dependency Tracking (Simulated): For this challenge, you don't need to implement full-blown Vue reactivity. Assume that changes within the
sourcefunction 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
sourcefunction 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
sourcefunction 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
watchfunction will be called with asourcethat is a function and acallbackthat is also a function. - The
sourcefunction 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
sourcefunction. 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
unwatchfunction 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.