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:
- Tracking: When a property of the reactive object is accessed, it should be "tracked" (in a simplified way – we'll use a
dependenciesmap for this). - Triggering: When a property of the reactive object is modified, any functions that depend on that property should be notified and executed.
- 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
reactiveTransformfunction 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
dependenciesmap 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
trackfunction 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
dependenciesmap 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
trackcan be nested (e.g., "user.age"). The transform should handle these nested paths correctly.
Notes
- You'll need to use
Proxyto intercept property access and modification. - Consider how to handle nested properties within the
dependenciesmap. A simple approach is to use a string representation of the path (e.g., "user.age"). - The
trackfunction 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
}