Hone logo
Hone
Problems

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:

  1. Define Reactive Properties: Register reactive properties with the tracker. These properties will be simple values (numbers, strings, booleans).
  2. Define Computed Properties: Register computed properties with the tracker. Computed properties are functions that depend on reactive properties.
  3. Track Dependencies: When a computed property function is executed, the tracker should automatically record which reactive properties are accessed within that function.
  4. Get Dependencies: Retrieve the list of reactive properties that a computed property depends on.

Key Requirements:

  • The tracker should use a Map to store computed properties and their dependencies.
  • The tracker should use a WeakMap to 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 getDependencies method 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 value property for reactive properties and a function for computed properties.
  • The DependencyTracker class must be implemented in TypeScript.
  • The getDependencies method 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 Proxy objects 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 WeakMap is crucial for preventing memory leaks. If you use a regular Map to 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?
Loading editor...
typescript