Skip to content

jkup/learn-signals

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

25 Commits
 
 
 
 
 
 
 
 

Repository files navigation

🚦 Simple Educational Signals Implementation

A minimal, easy-to-understand implementation of JavaScript Signals based on the TC39 Signals Proposal. This implementation prioritizes clarity and educational value over performance optimizations.

🎯 Purpose

This library is designed for people with solid JavaScript knowledge to get a better understanding of the TC39 Signals proposal.

Signals have become extremely popular in the JavaScript framework ecosystem. Some popular examples are:

✨ Key Features

📦 Core API

  • Signal.State<T> - Writable reactive state
  • Signal.Computed<T> - Derived values that automatically track dependencies
  • Signal.subtle.Watcher - Low-level API for observing signal changes

⚛️ Effect API (not part of the TC39 proposal)

  • effect(fn) - Side effects that run when dependencies change. This is just added for educational purposes. In reality, this would be handled by the framework.

🔄 Key Characteristics

  • Auto-tracking: Computed signals automatically discover their dependencies
  • Pull-based: Computations are lazy and only run when accessed
  • Cached: Results are memoized until dependencies change
  • Synchronous notifications: Watchers are notified immediately when signals change

📖 Detailed Examples

Auto-tracking Dependencies

const firstName = new Signal.State("John");
const lastName = new Signal.State("Doe");

// This computed automatically tracks both firstName and lastName
const fullName = new Signal.Computed(() => {
  console.log("Computed function called!");
  return `${firstName.get()} ${lastName.get()}`;
});

// First access - the function runs!
console.log(fullName.get());
// Output: "Computed function called!"
// Output: "John Doe"

// Second access - the function does not run!
console.log(fullName.get());
// Output: "John Doe"

// Third access - still cached
console.log(fullName.get());
// Output: "John Doe"

// Now change a dependency - this invalidates the cache
firstName.set("Jane");

// Next access triggers re-computation because firstName changed
console.log(fullName.get());
// Output: "Computed function called!"
// Output: "Jane Doe"

// Accessing again - cached again until next dependency change
console.log(fullName.get());
// Output: "Jane Doe" (no "Computing..." message)

🏗️ How It Works

Auto-tracking Magic

When a computed signal runs, it sets itself as the "currently computing" signal. Any state signals accessed during this time automatically register the computed as a dependent.

// When this computed runs:
const computed = new Signal.Computed(() => {
  return state1.get() + state2.get(); // Both state1 and state2 track this computed
});

Pull-based Evaluation

Computed signals are lazy - they only recalculate when someone actually reads their value, even if dependencies changed earlier.

state.set(newValue); // Marks computeds as "stale" but doesn't run them
// ... other code ...
const result = computed.get(); // NOW the computation runs

🔍 Differences from Production Implementations

This educational version omits some optimizations found in production signals:

  • No performance optimizations (prioritizes clarity)
  • No advanced scheduling (basic effect implementation)
  • No memory optimizations (uses simple data structures)
  • No async support (synchronous only)
  • Simplified error handling (basic error propagation)

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published