DEV Community

Cover image for Dependency Tracking Fundamentals (II)
Luciano0322
Luciano0322

Posted on

Dependency Tracking Fundamentals (II)

Introduction

In the previous article, we broke down the core concepts and execution model of dependency tracking.
In this installment, we shift our focus to React’s dependency model—its characteristics, limitations, and how it compares to the explicit dependency graphs used by Signals.


What Is React’s Dependency Model?

React does not have a built-in explicit dependency graph. Instead, it operates using the following workflow:

  1. State Updates (setState execution)

    A state update triggers the entire component to re-execute its render function.

  2. Re-rendering (Reconciliation)

    React compares the new Virtual DOM tree with the previous one (diffing) to determine which parts need to be updated.

  3. Effect Execution (useEffect / useLayoutEffect)

    During the commit phase, effects are conditionally re-executed based on their dependency arrays.

This architecture represents a Pull-based + Scheduler-driven model:
React does not know which specific UI nodes depend on a given piece of state, so it must “pull” by re-running the entire component to ensure correctness.


Problems with React’s Dependency Tracking

Issue Type Description Impact
No explicit dependency graph React cannot precisely map state to UI segments Unnecessary re-renders
Manual dependency arrays Dependencies in useEffect must be manually maintained Easy to introduce bugs or redundant executions
Repeated computation Multiple components reading the same state recompute independently Performance waste, especially in large apps
Difficult dynamic dependencies Conditional dependencies must be manually managed Higher maintenance cost

How Signals Improve the Situation

Signals (as seen in Solid.js, MobX, or Vue refs) address these issues in several ways:

  • Explicit dependency graph

    Dependencies are automatically collected on get, and only the relevant effect or computed nodes are notified on set.

  • Fine-grained updates

    Updates occur at the value level, not at the component level.

  • No manual dependency management

    Dynamic dependencies are added or removed automatically at runtime, eliminating dependency array mistakes.

  • Reduced redundant computation

    Shared computed values are evaluated once and reused wherever needed.


Side-by-Side Example

React

function Counter() {
  const [count, setCount] = useState(0);
  const double = count * 2; // Recomputed on every render

  return (
    <>
      <h1>Hello World!</h1>
      <button onClick={() => setCount(c => c + 1)}>
        {double}
      </button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

What happens on count update:

  • Counter re-renders entirely
  • Hello World re-renders unnecessarily
  • double is recomputed every time

Signal

const [count, setCount] = createSignal(0);
const double = () => count() * 2;

function Counter() {
  return (
    <>
      <h1>Hello World!</h1>
      <button onClick={() => setCount(c => c + 1)}>
        {double()}
      </button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

What happens on count update:

  • Only double is recomputed
  • Only the DOM node that consumes double (the button) is updated

Why Does React Still Remain Dominant?

Despite lacking explicit dependency tracking, React mitigates these issues through several strategies:

  • Virtual DOM diffing optimizations (e.g. key, memo)
  • Automatic batching
  • Selective rendering (React.memo, useMemo, useCallback)

These mechanisms improve performance, but they also require manual optimization effort from developers.


Conclusion

From this discussion, we can draw the following conclusions:

  • React follows a Pull-based model without an explicit dependency graph, which limits fine-grained updates.
  • Signals adopt a Push-based model with an explicit dependency graph, automatically managing dependencies and updating only what is necessary.
  • For performance-critical applications or systems with complex dependency relationships, the Signals model offers clear advantages.

In the next article, we will move into implementation details and gradually derive a simple Signal example from first principles.

Top comments (0)