Last active
January 28, 2025 10:05
-
-
Save adnanalbeda/d2b260e47d0729af0da484412e468acc to your computer and use it in GitHub Desktop.
reactivity-vanillaJS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { SignalGet, SignalUnsubscribe } from "./signal"; | |
export type EffectDepsValues<T extends SignalGet<unknown>[]> = { | |
[k in keyof T]: T[k] extends SignalGet<infer R> ? R : never; | |
}; | |
export type EffectCallback< | |
T extends [SignalGet<unknown>, ...SignalGet<unknown>[]] | |
> = (values: EffectDepsValues<T>) => unknown; | |
export type EffectCleanup = { (): void; isExecuted(): boolean }; | |
export type CreateEffect = < | |
T extends [SignalGet<unknown>, ...SignalGet<unknown>[]] | |
>( | |
deps: T, | |
effect: EffectCallback<T> | |
) => EffectCleanup; | |
export function vEffect< | |
T extends [SignalGet<unknown>, ...SignalGet<unknown>[]] | |
>(deps: T, effect: EffectCallback<T>): EffectCleanup { | |
// to check if should fire | |
let watchingDeps: boolean | undefined = true; | |
// getters and their values | |
deps = Object.freeze(deps); | |
let values = Object.seal(deps.map((v) => v())) as EffectDepsValues<T>; | |
// build notifiers and store their cleanup function. | |
let notifiersCleanup: SignalUnsubscribe[] = deps.map((x, i) => | |
x.subscribe((v) => { | |
if (!watchingDeps) return; | |
values[i] = v; | |
effect(values); | |
}) | |
); | |
// cleanup function: | |
// 1- stop subscribed notifications from running effect. | |
// 2- run notifiers cleanup. | |
// 3- make resources undefined as they will no longer be used. | |
function cleanup() { | |
if (!watchingDeps) | |
throw new Error("Cannot run effect cleanup more than once."); | |
watchingDeps = undefined; | |
notifiersCleanup.forEach((x) => x()); | |
notifiersCleanup = undefined as unknown as SignalUnsubscribe[]; | |
deps = undefined as unknown as T; | |
values = undefined as unknown as EffectDepsValues<T>; | |
} | |
cleanup.isExecuted = () => !watchingDeps; | |
Object.freeze(cleanup); | |
return cleanup; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export type SignalGet<T> = { | |
(): T; | |
subscribe: SignalSubscribe<T>; | |
}; | |
export type SignalSet<T> = { | |
(newValue: T): void; | |
}; | |
export type SignalSubscribeNotifier<T> = { | |
(updatedValue: T): void; | |
}; | |
export type SignalUnsubscribe = { (): void; isExecuted(): boolean }; | |
export type SignalSubscribe<T> = { | |
(notifier: SignalSubscribeNotifier<T>): SignalUnsubscribe; | |
}; | |
export type Signal<T> = [SignalGet<T>, SignalSet<T>, SignalSubscribe<T>]; | |
export type CreateSignal<T> = { | |
(): Signal<T | undefined>; | |
(v: T): Signal<T>; | |
}; | |
export function vSignal<T>(): Signal<T | undefined>; | |
export function vSignal<T>(initialValue: T): Signal<T>; | |
export function vSignal<T>(initialValue?: T) { | |
let value = initialValue as T; | |
// value get accessor | |
function get() { | |
return value; | |
} | |
// update value then notify subscribed notifiers of the change. | |
function set(newValue: T) { | |
if (value === newValue) return; | |
value = newValue as T; | |
notifySubscribedNotifiers(value); | |
} | |
// Value changed event callbacks | |
let subscribedNotifiers: SignalSubscribeNotifier<T>[] = []; | |
function notifySubscribedNotifiers(value: T) { | |
// notify (run) each subscribed value changed notifier (callback) about the new value. | |
subscribedNotifiers.forEach(async (x) => await x(value)); | |
} | |
// add value notifier and returns its unsubscribe. | |
function subscribe(notifier: SignalSubscribeNotifier<T>): SignalUnsubscribe { | |
let watching: boolean | undefined = true; | |
subscribedNotifiers.push(notifier); | |
// remove notifiers from subscribed list of notifiers. | |
function unsubscribe() { | |
if (!watching) | |
throw new Error( | |
"Cannot run unsubscribe from this signal more than once." | |
); | |
watching = undefined; | |
// remove notifier from list of notifiers. | |
subscribedNotifiers = subscribedNotifiers.filter((v) => v !== notifier); | |
notifier = undefined as unknown as SignalSubscribeNotifier<T>; // cleanup callback | |
} | |
unsubscribe.isExecuted = () => !watching; | |
Object.freeze(unsubscribe); | |
return unsubscribe; | |
} | |
get.subscribe = subscribe; | |
return [get, set, subscribe]; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment