Hone logo
Hone
Problems

Implementing a Custom Reactivity Transform in Vue.js

Vue.js's reactivity system is a core feature that automatically tracks dependencies and updates the DOM when data changes. This challenge asks you to implement a simplified version of a reactivity transform, specifically focusing on transforming a plain JavaScript object into a reactive one. This exercise will deepen your understanding of Vue's reactivity principles and how it works under the hood.

Problem Description

You are tasked with creating a function reactiveTransform that takes a plain JavaScript object as input and returns a reactive version of that object. The reactive version should exhibit the following behavior:

  1. Tracking: When a property of the reactive object is accessed, it should be "tracked" (in a simplified way – we'll use a dependencies map for this).
  2. Triggering: When a property of the reactive object is modified, any functions that depend on that property should be notified and executed.
  3. Getter/Setter Interception: The function should intercept property access (getting) and modification (setting) to achieve tracking and triggering.

The dependencies map will store functions that need to be executed when a specific property changes. The keys of the map will be property names (strings), and the values will be arrays of functions.

Key Requirements:

  • The reactiveTransform function must return a new object, not modify the original.
  • The returned object should behave like a normal JavaScript object regarding property access and modification.
  • The dependencies map should be private to the reactive object.
  • The triggering mechanism should only execute functions that are explicitly registered as dependencies.

Expected Behavior:

When a property is accessed, a function passed to track should be added to the dependencies map for that property. When a property is set, all functions in the dependencies map for that property should be executed.

Examples

Example 1:

Input:
const data = { name: 'Alice', age: 30 };
const reactiveData = reactiveTransform(data);

function updateName(newName: string) {
  console.log(`Name updated to: ${newName}`);
}

track('name', updateName);

reactiveData.name = 'Bob';

Output:
// Console output:
// Name updated to: Bob

Explanation: The reactiveTransform function creates a reactive version of the data object. The track function registers updateName as a dependency on the name property. When reactiveData.name is set to 'Bob', the updateName function is executed.

Example 2:

Input:
const data = { count: 0 };
const reactiveData = reactiveTransform(data);

let result = 0;

track('count', () => {
  result = reactiveData.count * 2;
});

reactiveData.count = 5;

Output:
// Console output: (nothing directly printed, but result is updated)
// result = 10

Explanation: The track function registers a function as a dependency on the count property. When reactiveData.count is set to 5, the registered function is executed, updating the result variable.

Example 3: (Edge Case - Nested Objects)

Input:
const data = { user: { name: 'Charlie' } };
const reactiveData = reactiveTransform(data);

track('user.name', (newName: string) => {
  console.log(`User's name updated to: ${newName}`);
});

reactiveData.user.name = 'David';

Output:
// Console output:
// User's name updated to: David

Explanation: This demonstrates that the transform should work with nested properties. The track function is called with a string representing the nested property path.

Constraints

  • The input object can contain any valid JavaScript data types (strings, numbers, booleans, arrays, other objects, etc.).
  • The track function will always be called with a string representing the property name (e.g., "name", "user.age") and a function to execute when the property changes.
  • The dependencies map should not be exposed externally.
  • The solution should be performant enough to handle a reasonable number of tracked properties (e.g., up to 100). Avoid unnecessary iterations or complex data structures.
  • The property names passed to track can be nested (e.g., "user.age"). The transform should handle these nested paths correctly.

Notes

  • You'll need to use Proxy to intercept property access and modification.
  • Consider how to handle nested properties within the dependencies map. A simple approach is to use a string representation of the path (e.g., "user.age").
  • The track function is provided as a helper to register dependencies. You don't need to implement it. It's assumed to exist in the environment.
  • Focus on the core reactivity transform logic. Error handling and advanced features (like computed properties) are not required for this challenge.
  • The goal is to understand the fundamental principles of reactivity and how Vue.js achieves it.
function reactiveTransform<T extends object>(target: T): T {
  // Your code here
  return target;
}

function track<K extends string>(key: K, cb: (newValue: any) => void): void {
  // Placeholder - not required to implement
}
Loading editor...
typescript