Hone logo
Hone
Problems

Deep Watch Implementation in Vue.js with TypeScript

Deep watching in Vue.js allows you to react to changes within nested properties of your data. The standard watch functionality only triggers when the top-level property changes, not when its children do. This challenge asks you to implement a custom deep watch directive that monitors changes within nested objects and arrays, providing a more granular reactivity system.

Problem Description

You are tasked with creating a useDeepWatch composable function in Vue.js using TypeScript. This composable should take a data property (an object or array) and a callback function as arguments. The callback function should be executed whenever any nested property within the data property changes. The deep watch should handle changes to primitive values, objects, and arrays, including additions, deletions, and modifications of elements within arrays. The composable should also correctly handle the initial value of the watched property.

Key Requirements:

  • Composable Function: The solution must be a Vue.js composable function (useDeepWatch).
  • Deep Reactivity: The callback should be triggered for changes in any nested property.
  • TypeScript: The code must be written in TypeScript with appropriate type annotations.
  • Initial Value Execution: The callback should be executed once when the composable is first called, using the initial value of the watched property.
  • Handles Arrays and Objects: The deep watch must correctly monitor changes within both arrays and objects.
  • No External Libraries: The solution should not rely on external libraries beyond Vue.js and TypeScript.

Expected Behavior:

When a nested property within the watched data changes, the provided callback function should be executed with the new value of the entire data object/array. The callback should receive the updated data as its argument.

Edge Cases to Consider:

  • Null/Undefined Values: Handle cases where nested properties might be null or undefined.
  • Array Mutations: Consider mutations like push, pop, splice, sort, and direct index assignment.
  • Object Property Additions/Deletions: Handle cases where properties are added or removed from the object.
  • Circular References: While not strictly required, consider how your solution would behave (or avoid) circular references to prevent infinite loops. A simple check for circular references is acceptable.
  • Performance: While not a primary focus, avoid unnecessary re-renders or computations.

Examples

Example 1:

Input:
data = { a: { b: 1 } }
callback = (newValue) => console.log("New value:", newValue)

// Inside a Vue component:
import { useDeepWatch } from './deepWatch'; // Assuming you've created a file for useDeepWatch

const data = reactive({ a: { b: 1 } });
useDeepWatch(data, (newValue) => {
  console.log("New value:", newValue);
});

data.a.b = 2; // Output: New value: { a: { b: 2 } }
data.a.c = 3; // Output: New value: { a: { b: 2, c: 3 } }

Example 2:

Input:
data = { items: [1, 2, 3] }
callback = (newValue) => console.log("New value:", newValue)
// Inside a Vue component:
import { useDeepWatch } from './deepWatch';

const data = reactive({ items: [1, 2, 3] });
useDeepWatch(data, (newValue) => {
  console.log("New value:", newValue);
});

data.items[0] = 4; // Output: New value: { items: [4, 2, 3] }
data.items.push(4); // Output: New value: { items: [4, 2, 3, 4] }
data.items.splice(1, 1); // Output: New value: { items: [4, 3, 4] }

Example 3: (Edge Case - Circular Reference)

Input:
data = { a: {} }
data.a.b = data; // Circular reference
callback = (newValue) => console.log("New value:", newValue)

The composable should either gracefully handle this (e.g., by detecting the circular reference and not triggering the callback) or avoid creating the circular reference in the first place. Crashing is not acceptable.

Constraints

  • Time Complexity: The callback execution should be triggered efficiently. Avoid unnecessary iterations or computations.
  • Memory Usage: The solution should not consume excessive memory.
  • Vue 3 Compatibility: The solution must be compatible with Vue 3.
  • Data Type: The data argument must be a plain JavaScript object or array. The composable should handle invalid input gracefully (e.g., by logging a warning and not doing anything).
  • Callback Function: The callback argument must be a function that accepts a single argument (the updated data).

Notes

  • Consider using Proxy to intercept property access and modifications. This is the recommended approach for deep reactivity.
  • Be mindful of performance implications when using Proxy. Avoid creating proxies for deeply nested objects unnecessarily.
  • The initial value execution is crucial. Ensure the callback is called with the initial data when the composable is first used.
  • Think about how to handle array mutations efficiently. Directly modifying array elements should trigger the callback.
  • Error handling and graceful degradation are important. The composable should not crash if it encounters unexpected input.
  • Type safety is paramount. Use TypeScript to ensure the code is well-typed and prevents common errors.
Loading editor...
typescript