Reactive Dependency Tracking in Vue (TypeScript)
Dependency tracking is a core concept in reactive frameworks like Vue. This challenge asks you to implement a simplified version of dependency tracking, focusing on how a computed property knows which reactive properties it depends on. This is crucial for Vue to efficiently re-evaluate computed properties only when their dependencies change.
Problem Description
You are tasked with creating a DependencyTracker class that can track dependencies between reactive properties and computed properties. The DependencyTracker should allow you to:
- Define Reactive Properties: Register reactive properties with the tracker. These properties will be simple values (numbers, strings, booleans).
- Define Computed Properties: Register computed properties with the tracker. Computed properties are functions that depend on reactive properties.
- Track Dependencies: When a computed property function is executed, the tracker should automatically record which reactive properties are accessed within that function.
- Get Dependencies: Retrieve the list of reactive properties that a computed property depends on.
Key Requirements:
- The tracker should use a
Mapto store computed properties and their dependencies. - The tracker should use a
WeakMapto store reactive properties and their subscribers (computed properties that depend on them). This prevents memory leaks. - The dependency tracking should be dynamic. If a computed property accesses a new reactive property during execution, it should be added to the dependency list.
- The tracker should handle multiple computed properties depending on the same reactive property.
Expected Behavior:
- When a computed property is registered, it should be associated with an empty array of dependencies.
- When a computed property function is executed, the tracker should identify all reactive properties accessed within the function and add them to the computed property's dependency list.
- The
getDependenciesmethod should return a copy of the dependency array, not the original array itself.
Edge Cases to Consider:
- Computed properties that don't depend on any reactive properties.
- Computed properties that access the same reactive property multiple times.
- Reactive properties that are never used by any computed properties.
- Computed properties that are registered but never executed.
Examples
Example 1:
Input:
tracker = new DependencyTracker();
reactive1 = { value: 1 };
reactive2 = { value: 2 };
computed1 = (tracker: DependencyTracker) => {
return reactive1.value + reactive2.value;
}
tracker.registerReactive(reactive1);
tracker.registerReactive(reactive2);
tracker.registerComputed("computed1", computed1);
computed1(tracker); // Execute the computed property
Output:
tracker.getDependencies("computed1") // Returns: [{ value: 1 }, { value: 2 }]
Explanation: Computed property 'computed1' accesses reactive1 and reactive2, so they are added to its dependency list.
Example 2:
Input:
tracker = new DependencyTracker();
reactive1 = { value: 1 };
computed1 = (tracker: DependencyTracker) => {
return reactive1.value * 2;
}
tracker.registerReactive(reactive1);
tracker.registerComputed("computed1", computed1);
computed1(tracker);
Output:
tracker.getDependencies("computed1") // Returns: [{ value: 1 }]
Explanation: Computed property 'computed1' accesses reactive1, so it's added to its dependency list.
Example 3:
Input:
tracker = new DependencyTracker();
reactive1 = { value: 1 };
reactive2 = { value: 2 };
computed1 = (tracker: DependencyTracker) => {
return reactive1.value;
}
tracker.registerReactive(reactive1);
tracker.registerReactive(reactive2);
tracker.registerComputed("computed1", computed1);
computed1(tracker);
Output:
tracker.getDependencies("computed1") // Returns: [{ value: 1 }]
Explanation: Computed property 'computed1' only accesses reactive1. reactive2 is not used.
Constraints
- Reactive properties and computed properties are represented as plain JavaScript objects with a
valueproperty for reactive properties and a function for computed properties. - The
DependencyTrackerclass must be implemented in TypeScript. - The
getDependenciesmethod should return a new array, not the original dependency array. - The tracker should be reasonably efficient. Avoid unnecessary iterations or complex data structures.
- The tracker should not throw errors if a computed property attempts to access a non-existent reactive property (it should simply not add it to the dependency list).
Notes
- Consider using
Proxyobjects to automatically track property access within the computed property function. However, for simplicity, you can manually track dependencies by wrapping the computed property function execution in a custom function that monitors property access. - The
WeakMapis crucial for preventing memory leaks. If you use a regularMapto store reactive property subscribers, the reactive properties might not be garbage collected even if they are no longer used by any computed properties. - Focus on the core dependency tracking logic. Error handling and input validation can be simplified for this exercise.
- Think about how to handle the case where a computed property is registered but never executed. Should it still have an empty dependency list?