This is a pure signal implementation in Javascript, an improved version by ChatGPT and Blackbox from the code provided in :
export class Signal extends EventTarget {
#value;
#listeners = new Set();
#isNotifying = false; // Flag to track if we are currently notifying listeners
get value() {
return this.#value;
}
set value(value) {
if (this.#value === value) return;
this.#value = value;
this.#notify();
}
constructor(value) {
super();
this.#value = value;
}
/**
* Registers an effect function that will be called when the signal changes.
* @param {Function} fn - The effect function to run on change.
* @returns {Function} A cleanup function to unregister the effect.
*/
effect(fn) {
const wrappedFn = () => {
try {
fn();
} catch (error) {
console.error("Effect error:", error);
}
};
wrappedFn(); // Run the effect once immediately
this.#listeners.add(wrappedFn);
this.addEventListener("change", wrappedFn);
return () => {
this.#listeners.delete(wrappedFn);
this.removeEventListener("change", wrappedFn);
};
}
#notify() {
if (this.#listeners.size > 0 && !this.#isNotifying) {
this.#isNotifying = true; // Set the flag to prevent re-entrance
queueMicrotask(() => {
this.dispatchEvent(new CustomEvent("change"));
this.#isNotifying = false; // Reset the flag after notifying
});
}
}
valueOf() {
return this.#value;
}
toString() {
return String(this.#value);
}
}
export class Computed extends Signal {
#fn;
#deps;
constructor(fn, deps) {
super(Computed.#computeInitialValue(fn, deps));
this.#fn = fn;
this.#deps = deps;
for (const dep of deps) {
if (dep instanceof Signal) {
dep.effect(() => this.#update());
} else {
console.warn("Computed dependency is not a Signal:", dep);
throw new TypeError("All dependencies must be instances of Signal.");
}
}
}
static #computeInitialValue(fn, deps) {
try {
return fn(...deps.map(dep => dep.value));
} catch (error) {
console.error("Error computing initial value of Computed:", error);
return undefined;
}
}
#update() {
try {
const newValue = this.#fn(...this.#deps.map(dep => dep.value));
if (this.value !== newValue) {
super.value = newValue; // Update using Signal's setter
}
} catch (error) {
console.error("Error updating Computed value:", error);
}
}
}
/**
* Creates a new Signal instance with the given initial value.
* @param {*} initialValue - The initial value of the signal.
* @returns {Signal} A new Signal instance.
*/
export const signal = (initialValue) => new Signal(initialValue);
/**
* Creates a new Computed instance that derives its value from the given function and dependencies.
* @param {Function} fn - The function to compute the value.
* @param {Signal[]} deps - An array of Signal instances that the computed value depends on.
* @returns {Computed} A new Computed instance.
*/
export const computed = (fn, deps) => new Computed(fn, deps);
Top comments (0)