Vue Dependency Tracking System
Vue.js relies on a sophisticated dependency tracking system to enable its reactive nature. This system allows Vue to know which components or computed properties need to re-render or re-evaluate when a piece of reactive data changes. This challenge asks you to build a simplified version of this dependency tracking mechanism. Understanding this core concept is crucial for mastering Vue's reactivity.
Problem Description
Your task is to implement a simplified dependency tracking system inspired by Vue's reactivity. You will create a Dependency class and a track and trigger mechanism.
What needs to be achieved:
DependencyClass: Create a class namedDependency. This class will be responsible for holding a collection of "dependents" (e.g., watchers, effects, computed properties) that are interested in a particular piece of reactive data.trackFunction: Implement a global functiontrack(dep: Dependency). This function will register the currently active "dependent" with the providedDependencyinstance. A "dependent" is something that is currently being evaluated or observed.triggerFunction: Implement a global functiontrigger(dep: Dependency). This function will notify all registered dependents within a givenDependencyinstance that the data they are interested in has changed.- Simulated Reactive Object: You'll need to create a simple way to associate a
Dependencywith a reactive property. For demonstration purposes, we'll simulate this by having a function that returns aDependencyinstance for a given key.
Key Requirements:
- The
Dependencyclass should have methods to:- Add a dependent (
depend()). - Notify all dependents (
notify()).
- Add a dependent (
- There must be a way to identify the "currently active dependent" that
trackshould add to the dependency. For this challenge, you can simulate this by having a global variable or a simple mechanism to set and retrieve the active dependent. - The
trackandtriggerfunctions should interact with theDependencyinstances. - You should be able to simulate a scenario where a property is "read" (triggering
track) and then "written to" (triggeringtrigger).
Expected Behavior:
When a reactive property is accessed (e.g., read), its corresponding Dependency should be "tracked," meaning the current active dependent is added to it. When that reactive property is modified, its corresponding Dependency should be "triggered," meaning all dependents that were previously tracked for this property are notified.
Edge Cases:
- A dependency might be tracked multiple times by the same dependent. Ensure duplicates are handled appropriately (e.g., only store unique dependents).
- A dependency might be triggered when no dependents are currently registered. This should not cause errors.
Examples
Let's assume we have a simplified mechanism to get the Dependency for a property and a way to set/get the currently active dependent.
Simulated Setup:
// In a real Vue, this would be managed more intricately
let activeDependent: (() => void) | null = null;
function setActiveDependent(dependent: (() => void) | null) {
activeDependent = dependent;
}
// Simple map to hold dependencies for different keys
const depMap = new Map<string, Dependency>();
function getDependency(key: string): Dependency {
if (!depMap.has(key)) {
depMap.set(key, new Dependency());
}
return depMap.get(key)!;
}
Example 1: Basic Tracking and Triggering
// Setup
const myData = { count: 0 };
const countDep = getDependency('count');
// A simulated dependent (e.g., a computed property or watcher function)
const computedCount = () => {
// Simulate reading the 'count' property
const dep = getDependency('count');
track(dep); // Track this dependent for 'count'
return myData.count;
};
// Simulate the dependent being active
setActiveDependent(() => {
// This function represents what happens when 'count' changes
console.log('Count changed! New value:', myData.count);
});
// --- Simulation ---
// 1. "Reading" count for the first time (e.g., component mounts)
console.log("Accessing count for tracking...");
const initialCount = computedCount(); // This will call track() internally
console.log("Initial count value:", initialCount);
// Expected Output:
// Accessing count for tracking...
// Initial count value: 0
// 2. "Modifying" count
console.log("Modifying count to 1...");
myData.count = 1;
trigger(countDep); // Notify dependents of 'count'
// Expected Output:
// Modifying count to 1...
// Count changed! New value: 1
// --- Resetting for next part of simulation ---
depMap.clear();
setActiveDependent(null);
Example 2: Multiple Dependents
// Setup
const user = { name: 'Alice' };
const nameDep = getDependency('name');
// Dependent 1: Displays the name
const DisplayName = () => {
const dep = getDependency('name');
track(dep);
console.log(`Displaying name: ${user.name}`);
};
// Dependent 2: Logs a greeting when name changes
const GreetingLogger = () => {
const dep = getDependency('name');
track(dep);
console.log(`Hello, ${user.name}!`);
};
// Simulate dependents being active
const dependent1Func = () => { console.log("Display Name updated."); };
const dependent2Func = () => { console.log("Greeting Logger updated."); };
// --- Simulation ---
// 1. "Reading" name for both dependents
setActiveDependent(dependent1Func);
DisplayName(); // Tracks dependent1Func for 'name'
setActiveDependent(dependent2Func);
GreetingLogger(); // Tracks dependent2Func for 'name'
// 2. "Modifying" name
console.log("Modifying name...");
user.name = 'Bob';
trigger(nameDep); // Notify both dependents
// Expected Output:
// Displaying name: Alice
// Hello, Alice!
// Modifying name...
// Display Name updated.
// Greeting Logger updated.
// --- Resetting ---
depMap.clear();
setActiveDependent(null);
Example 3: Re-tracking and Cleanup (Conceptual)
While not directly testable with simple console.log, consider this scenario:
If a component that was tracking a property is unmounted, its dependent should ideally be removed from the Dependency's list to prevent memory leaks. This challenge focuses on the core track and trigger, but in a real system, you'd have mechanisms for this cleanup.
Constraints
- The
Dependencyclass must be implemented in TypeScript. - The
trackandtriggerfunctions must be globally available (or callable in a way that reflects global scope). - The simulation of active dependents can be a simple global variable or a passed parameter.
- Performance is not a primary concern for this challenge, but the design should be conceptually efficient (e.g., not iterating through thousands of dependents unnecessarily if not needed).
Notes
- Think about how
trackidentifies which dependent to add. You'll need a mechanism to represent the "current" dependent. - The
Dependencyclass is central. It needs to store a collection of dependents. What data structure is appropriate for this? - The
triggermethod should iterate through its stored dependents and invoke some form of "update" or "callback" on each. - This challenge is about the core mechanics. You don't need to build a full Vue proxy or render loop. Focus on the
Dependency,track, andtriggerinteraction.