Fork of https://github.com/CCherry07/alien-deepsignals ported to alien-signals v2 with support for per-value modification tracking.
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
244 lines
5.1 KiB
244 lines
5.1 KiB
import { createReactiveSystem, Dependency, Link, Subscriber, SubscriberFlags } from 'alien-signals';
|
|
import { ReactiveFlags } from "./contents"
|
|
import { isFunction } from './utils';
|
|
const {
|
|
link,
|
|
propagate,
|
|
endTracking,
|
|
startTracking,
|
|
updateDirtyFlag,
|
|
processComputedUpdate,
|
|
processEffectNotifications,
|
|
} = createReactiveSystem({
|
|
updateComputed(computed: Computed) {
|
|
return computed.update();
|
|
},
|
|
notifyEffect(effect: Effect) {
|
|
effect.notify();
|
|
return true;
|
|
},
|
|
});
|
|
|
|
let activeSub: Subscriber | undefined = undefined;
|
|
let batchDepth = 0;
|
|
|
|
export function startBatch(): void {
|
|
++batchDepth;
|
|
}
|
|
|
|
export function endBatch(): void {
|
|
if (!--batchDepth) {
|
|
processEffectNotifications();
|
|
}
|
|
}
|
|
|
|
export function signal<T>(): Signal<T | undefined>;
|
|
export function signal<T>(oldValue: T): Signal<T>;
|
|
export function signal<T>(oldValue?: T): Signal<T | undefined> {
|
|
return new Signal(oldValue);
|
|
}
|
|
|
|
export class Signal<T = any> implements Dependency {
|
|
public readonly [ReactiveFlags.IS_SIGNAL] = true
|
|
public readonly [ReactiveFlags.SKIP] = true
|
|
// Dependency fields
|
|
subs: Link | undefined = undefined;
|
|
subsTail: Link | undefined = undefined;
|
|
|
|
constructor(
|
|
public currentValue: T
|
|
) { }
|
|
|
|
get(): T {
|
|
if (activeSub !== undefined) {
|
|
link(this, activeSub);
|
|
}
|
|
return this.currentValue;
|
|
}
|
|
|
|
set(value: T): void {
|
|
if (this.currentValue !== value) {
|
|
this.currentValue = value;
|
|
const subs = this.subs;
|
|
if (subs !== undefined) {
|
|
propagate(subs);
|
|
if (!batchDepth) {
|
|
processEffectNotifications();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
get value(): T {
|
|
return this.get();
|
|
}
|
|
|
|
set value(value: T) {
|
|
this.set(value);
|
|
}
|
|
|
|
peek(): T {
|
|
return this.currentValue;
|
|
}
|
|
}
|
|
|
|
export function computed<T>(getter: () => T): Computed<T> {
|
|
return new Computed<T>(getter);
|
|
}
|
|
|
|
export class Computed<T = any> implements Subscriber, Dependency {
|
|
readonly [ReactiveFlags.IS_SIGNAL] = true
|
|
currentValue: T | undefined = undefined;
|
|
|
|
// Dependency fields
|
|
subs: Link | undefined = undefined;
|
|
subsTail: Link | undefined = undefined;
|
|
|
|
// Subscriber fields
|
|
deps: Link | undefined = undefined;
|
|
depsTail: Link | undefined = undefined;
|
|
flags: SubscriberFlags = SubscriberFlags.Computed | SubscriberFlags.Dirty;
|
|
|
|
constructor(
|
|
public getter: () => T
|
|
) { }
|
|
|
|
get(): T {
|
|
const flags = this.flags;
|
|
if (flags & (SubscriberFlags.PendingComputed | SubscriberFlags.Dirty)) {
|
|
processComputedUpdate(this, flags);
|
|
}
|
|
if (activeSub !== undefined) {
|
|
link(this, activeSub);
|
|
}
|
|
return this.currentValue!;
|
|
}
|
|
|
|
update(): boolean {
|
|
const prevSub = activeSub;
|
|
activeSub = this;
|
|
startTracking(this);
|
|
try {
|
|
const oldValue = this.currentValue;
|
|
const newValue = this.getter();
|
|
if (oldValue !== newValue) {
|
|
this.currentValue = newValue;
|
|
return true;
|
|
}
|
|
return false;
|
|
} finally {
|
|
activeSub = prevSub;
|
|
endTracking(this);
|
|
}
|
|
}
|
|
|
|
get value(): Readonly<T> {
|
|
return this.get();
|
|
}
|
|
|
|
peek(): T {
|
|
return this.currentValue!;
|
|
}
|
|
}
|
|
|
|
export function effect<T>(fn: () => T): Effect<T> {
|
|
const e = new Effect(fn);
|
|
e.run();
|
|
return e;
|
|
}
|
|
|
|
export enum EffectFlags {
|
|
/**
|
|
* ReactiveEffect only
|
|
*/
|
|
ALLOW_RECURSE = 1 << 7,
|
|
PAUSED = 1 << 8,
|
|
NOTIFIED = 1 << 9,
|
|
STOP = 1 << 10,
|
|
}
|
|
|
|
export class Effect<T = any> implements Subscriber {
|
|
readonly [ReactiveFlags.IS_SIGNAL] = true
|
|
// Subscriber fields
|
|
deps: Link | undefined = undefined;
|
|
depsTail: Link | undefined = undefined;
|
|
flags: SubscriberFlags = SubscriberFlags.Effect;
|
|
constructor(
|
|
public fn: () => T
|
|
) { }
|
|
|
|
notify(): void {
|
|
const flags = this.flags;
|
|
if (
|
|
flags & SubscriberFlags.Dirty
|
|
|| (flags & SubscriberFlags.PendingComputed && updateDirtyFlag(this, flags))
|
|
) {
|
|
this.scheduler();
|
|
}
|
|
}
|
|
|
|
scheduler(): void {
|
|
if (this.dirty) {
|
|
this.run()
|
|
}
|
|
}
|
|
get active(): boolean {
|
|
return !(this.flags & EffectFlags.STOP)
|
|
}
|
|
|
|
get dirty(): boolean {
|
|
const flags = this.flags
|
|
if (
|
|
flags & SubscriberFlags.Dirty ||
|
|
(flags & SubscriberFlags.PendingComputed && updateDirtyFlag(this, flags))
|
|
) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
run(): T {
|
|
const prevSub = activeSub;
|
|
activeSub = this;
|
|
startTracking(this);
|
|
try {
|
|
return this.fn();
|
|
} finally {
|
|
activeSub = prevSub;
|
|
endTracking(this);
|
|
}
|
|
}
|
|
|
|
stop(): void {
|
|
startTracking(this);
|
|
endTracking(this);
|
|
}
|
|
}
|
|
|
|
export function batch<T>(fn: () => T): T {
|
|
startBatch();
|
|
try {
|
|
return fn();
|
|
} finally {
|
|
endBatch();
|
|
}
|
|
}
|
|
|
|
export function isSignal<T>(r: Signal<T> | unknown): r is Signal<T>
|
|
export function isSignal(s: any): s is Signal {
|
|
return s ? s[ReactiveFlags.IS_SIGNAL] === true : false
|
|
}
|
|
|
|
export type MaybeSignal<T = any> =
|
|
| T
|
|
| Signal<T>
|
|
|
|
export type MaybeSignalOrGetter<T = any> = MaybeSignal<T> | Computed<T> | (() => T)
|
|
|
|
export function unSignal<T>(signal: MaybeSignal<T> | Computed<T>): T {
|
|
return (isSignal(signal) ? signal.value : signal) as T;
|
|
}
|
|
|
|
export function toValue<T>(source: MaybeSignalOrGetter<T>): T {
|
|
return isFunction(source) ? source() : unSignal(source)
|
|
}
|
|
|