Hone logo
Hone
Problems

Vue Effect Scheduler: Batching and Debouncing Effects

Effect schedulers are crucial for optimizing performance in reactive systems like Vue. This challenge asks you to implement a simplified effect scheduler that batches and debounces effects, preventing unnecessary re-renders and computations. This is useful for scenarios where effects are triggered frequently by changes in reactive data, but the actual work performed by the effect doesn't need to happen on every trigger.

Problem Description

You need to create a EffectScheduler class in TypeScript that manages a list of effects. Each effect is a function that should be executed when certain dependencies change. The scheduler should provide the following functionalities:

  1. addEffect(effect: () => void, dependencies: (Ref<any> | any)[], options?: { debounce?: number, batch?: boolean }): Adds a new effect to the scheduler.

    • effect: The function to be executed when dependencies change.
    • dependencies: An array of reactive dependencies (Vue Ref objects or primitive values) that the effect relies on. The effect should only run when these dependencies change.
    • options: An optional object with the following properties:
      • debounce: (number, optional) If provided, the effect will be debounced for this number of milliseconds. The effect will only run after the specified delay from the last dependency change.
      • batch: (boolean, optional, default: false) If true, multiple dependency changes within a short period will be batched into a single effect execution.
  2. removeEffect(effect: () => void): Removes an effect from the scheduler.

  3. trigger(dependency: Ref<any> | any): Simulates a change in a dependency. This should trigger the appropriate effects based on their dependencies, respecting debounce and batching options.

The scheduler should maintain a queue of effects waiting to be executed, and handle debouncing and batching logic correctly. It should also ensure that effects are only executed when their dependencies have actually changed.

Examples

Example 1:

Input:
Scheduler initialized.
addEffect(() => console.log("Effect 1"), [ref1], { debounce: 500 });
ref1.value = 1;
ref1.value = 2;
await new Promise(resolve => setTimeout(resolve, 600));
ref1.value = 3;

Output:
console.log("Effect 1") (printed once after 600ms when ref1.value is 3)

Explanation:
The first two changes to `ref1` are debounced. After 600ms, the effect is executed with the latest value of `ref1` (3).

Example 2:

Input:
Scheduler initialized.
addEffect(() => console.log("Effect 2"), [ref2], { batch: true });
ref2.value = 1;
ref2.value = 2;
ref2.value = 3;
await new Promise(resolve => setTimeout(resolve, 100));
ref2.value = 4;

Output:
console.log("Effect 2") (printed twice)

Explanation:
The first three changes to `ref2` are batched. After 100ms (assuming a default batching interval, see Notes), the batch is flushed, and the effect is executed with `ref2.value = 3`. The subsequent change to `ref2.value = 4` triggers a new batch.

Example 3: (Edge Case - Removing an effect)

Input:
Scheduler initialized.
const effectToRemove = () => console.log("Effect to Remove");
addEffect(effectToRemove, [ref3]);
removeEffect(effectToRemove);
ref3.value = 1;

Output:
(No output to console)

Explanation:
The effect is removed before it has a chance to be triggered by the change in `ref3`.

Constraints

  • debounce time must be a non-negative number.
  • Dependencies can be Vue Ref objects or primitive values (numbers, strings, booleans).
  • The batching interval (if batch is true) is assumed to be 100ms. This is a hardcoded value for simplicity.
  • The scheduler should handle multiple effects with different dependencies and options correctly.
  • The scheduler should not introduce memory leaks.

Notes

  • You can use setTimeout for debouncing.
  • For batching, you can use a timer to periodically flush the batch queue.
  • Consider using a Map or Set to efficiently store and remove effects.
  • This is a simplified scheduler; a real-world Vue scheduler would be more complex and integrated with Vue's reactivity system. Focus on the core concepts of debouncing and batching.
  • Assume that ref1, ref2, and ref3 are Vue Ref objects initialized elsewhere. You don't need to create them.
  • The trigger function is for testing purposes and simulates dependency changes. In a real Vue application, dependency changes would happen automatically as reactive data is modified.
  • Error handling (e.g., invalid options) is not required for this challenge.
Loading editor...
typescript