Hone logo
Hone
Problems

Vue Custom Reactivity System

Vue's reactivity system is a core feature that automatically updates the UI when data changes. This challenge asks you to implement a simplified, custom reactivity system for Vue, allowing you to understand the underlying mechanisms. Building this will deepen your understanding of how Vue tracks dependencies and triggers updates.

Problem Description

You need to create a custom reactivity system that mimics Vue's core functionality. This system should allow you to define reactive data properties and trigger updates in a "virtual DOM" (represented by a simple update function) when these properties change.

Key Requirements:

  1. reactive(obj) function: This function should take a plain JavaScript object and return a new object with its properties made reactive.
  2. effect(callback) function: This function should take a callback function (the "effect") and run it. Whenever a reactive property accessed within the callback changes, the callback should be re-run automatically.
  3. Dependency Tracking: The system must track which effects depend on which reactive properties.
  4. Triggering Updates: When a reactive property is set, the system must identify and re-run all effects that depend on that property.

Expected Behavior:

  • When reactive is called on an object, all its properties should be wrapped in getters and setters.
  • When an effect runs, it should record all reactive properties it reads.
  • When a reactive property's value is changed, all recorded effects that depend on it should be executed.

Edge Cases:

  • Handling nested objects within the reactive object.
  • Ensuring that effects are not re-run unnecessarily (e.g., if a property is set to its current value).
  • What happens if a reactive property is deleted? (For this challenge, you can assume deletion is not a primary concern, but consider how it might be handled).

Examples

Example 1: Simple Property Update

// Setup
const state = reactive({ count: 0 });
let updatedCount = 0;

const incrementEffect = effect(() => {
  updatedCount = state.count;
  console.log(`Count is now: ${state.count}`);
});

// Initial run
console.log("Initial run of effect."); // Logs "Initial run of effect."
// Output: Count is now: 0

// Update the property
state.count++;

// Expected Behavior:
// The effect should re-run automatically.
// Output: Count is now: 1

state.count++;
// Output: Count is now: 2

console.log("Final updatedCount:", updatedCount); // Should be 2

Example 2: Multiple Effects and Dependencies

// Setup
const state = reactive({
  firstName: "John",
  lastName: "Doe",
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
});

let loggedFullName = "";
const fullNameEffect = effect(() => {
  loggedFullName = state.fullName; // Accessing a computed property, which in turn accesses reactive properties
  console.log(`Full name: ${state.fullName}`);
});

let loggedFirstName = "";
const firstNameEffect = effect(() => {
  loggedFirstName = state.firstName;
  console.log(`First name: ${state.firstName}`);
});

// Initial runs
console.log("Initial runs.");
// Output: Full name: John Doe
// Output: First name: John

// Update firstName
state.firstName = "Jane";

// Expected Behavior:
// Both effects should re-run.
// Output: Full name: Jane Doe
// Output: First name: Jane

// Update lastName
state.lastName = "Smith";

// Expected Behavior:
// Only fullNameEffect should re-run.
// Output: Full name: Jane Smith

Example 3: Nested Object Reactivity

// Setup
const state = reactive({
  user: {
    name: "Alice",
    address: {
      city: "Wonderland"
    }
  }
});

let userCity = "";
const userCityEffect = effect(() => {
  userCity = state.user.address.city;
  console.log(`User city: ${state.user.address.city}`);
});

// Initial run
console.log("Initial run.");
// Output: User city: Wonderland

// Update nested property
state.user.address.city = "Looking-Glass Land";

// Expected Behavior:
// The effect should re-run.
// Output: User city: Looking-Glass Land

Constraints

  • The reactive function should only wrap plain JavaScript objects. Arrays and other complex types can be treated as regular objects for this challenge.
  • The effect function should be designed to run its callback at least once upon creation.
  • Your solution should be written in TypeScript.
  • The number of reactive properties and effects can vary, but the system should remain efficient. Avoid excessive re-runs of effects.

Notes

  • You will need to implement the core logic for tracking dependencies (what properties an effect reads) and triggering updates (when a property is set).
  • Consider using a Map or similar data structure to store the relationship between properties and the effects that depend on them.
  • Think about how to handle the initial run of an effect and subsequent updates.
  • This challenge focuses on the fundamental concepts. You don't need to implement a full virtual DOM renderer or complex lifecycle hooks.
  • The console.log statements in the examples are for demonstrating the flow; your primary goal is to ensure the effects themselves are re-run correctly.
Loading editor...
typescript