Hone logo
Hone
Problems

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:

  1. Dependency Class: Create a class named Dependency. 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.
  2. track Function: Implement a global function track(dep: Dependency). This function will register the currently active "dependent" with the provided Dependency instance. A "dependent" is something that is currently being evaluated or observed.
  3. trigger Function: Implement a global function trigger(dep: Dependency). This function will notify all registered dependents within a given Dependency instance that the data they are interested in has changed.
  4. Simulated Reactive Object: You'll need to create a simple way to associate a Dependency with a reactive property. For demonstration purposes, we'll simulate this by having a function that returns a Dependency instance for a given key.

Key Requirements:

  • The Dependency class should have methods to:
    • Add a dependent (depend()).
    • Notify all dependents (notify()).
  • There must be a way to identify the "currently active dependent" that track should 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 track and trigger functions should interact with the Dependency instances.
  • You should be able to simulate a scenario where a property is "read" (triggering track) and then "written to" (triggering trigger).

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 Dependency class must be implemented in TypeScript.
  • The track and trigger functions 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 track identifies which dependent to add. You'll need a mechanism to represent the "current" dependent.
  • The Dependency class is central. It needs to store a collection of dependents. What data structure is appropriate for this?
  • The trigger method 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, and trigger interaction.
Loading editor...
typescript