rvx
Version:
A signal based rendering library
339 lines (338 loc) • 9.11 kB
TypeScript
/**
* Represents the source that a signal has been derived from.
*
* When deriving a signal, the source should be passed via the signal constructor or shorthand.
* This has no impact on how a signal behaves, but allows other APIs to locate metadata about a signal's source.
*
* @example
* ```js
* function trim(source: Signal<string>) {
* const input = $(source.value, source);
* ...
* return input;
* }
* ```
*/
export type SignalSource = Signal<unknown> | undefined;
/**
* Represents a value that changes over time.
*/
export declare class Signal<T> {
#private;
/**
* Create a new signal.
*
* @param value The initial value.
* @param source The {@link SignalSource source} this signal has been derived from.
*/
constructor(value: T, source?: SignalSource);
/**
* Reactively access the current value.
*/
get value(): T;
/**
* Set the current value.
*
* If the new value is the same as the previous one, no observers are notified.
*
* @example
* ```tsx
* import { $, watch } from "rvx";
*
* const count = $(0);
*
* watch(count, count => {
* console.log("Count:", count);
* });
*
* count.value++;
* ```
*/
set value(value: T);
/**
* The {@link SignalSource source}, this signal has been derived from.
*/
get source(): SignalSource;
/**
* The root {@link SignalSource source}, this signal has been derived from or this signal itself if it hasn't been derived.
*/
get root(): Signal<unknown>;
/**
* Update the current value in place.
*
* @param fn A function to update the value. If false is returned, observers are not notified.
*
* @example
* ```tsx
* import { $, watch } from "rvx";
*
* const items = $([]);
*
* watch(items, items => {
* console.log("Items:", items);
* });
*
* items.update(items => {
* items.push("foo");
* items.push("bar");
* });
* ```
*/
update(fn: (value: T) => void | boolean): void;
/**
* Check if this signal has any active observers.
*/
get active(): boolean;
/**
* Manually access this signal.
*/
access(): void;
/**
* Manually notify observers.
*
* During batches, notifications are deferred.
*/
notify(): void;
/**
* Pass this signal to a function and get it's result.
*
* @example
* ```tsx
* const value = $(42);
*
* <TextInput value={
* value
* .pipe(parseInt)
* .pipe(trim)
* } />
* ```
*/
pipe<A extends any[], R>(fn: (self: this, ...args: A) => R, ...args: A): R;
}
/**
* Create a new signal.
*
* @param value The initial value.
* @param source The {@link SignalSource source} this signal has been derived from.
* @returns The signal.
*/
export declare function $(): Signal<void>;
export declare function $<T>(value: T, source?: SignalSource): Signal<T>;
/**
* A value, signal or function to get a value.
*
* @example
* ```tsx
* import { $, watch } from "rvx";
*
* const message = $("Example");
*
* // Not reactive:
* watch(message.value, message => {
* console.log("A:", message);
* });
*
* // Reactive:
* watch(message, message => {
* console.log("B:", message);
* });
*
* // Reactive:
* watch(() => message.value, message => {
* console.log("C:", message);
* });
*
* message.value = "Hello World!";
* ```
*/
export type Expression<T> = T | Signal<T> | (() => T);
/**
* Utility to get the result type of an expression.
*/
export type ExpressionResult<T> = T extends Expression<infer R> ? R : never;
/**
* Utility type for expressions that should never be static values.
*
* This can be used instead of the {@link Expression} type in places where accepting static values doesn't make sense.
*/
export type Reactive<T> = Signal<T> | (() => T);
/**
* Watch an expression until the current lifecycle is disposed.
*
* + Both the expression and effect are called at least once immediately.
* + Lifecycle hooks from the expression or effect are called before a signal update is processed or when the current lifecycle is disposed.
*
* @param expr The expression to watch.
* @param effect An optional function to call with each expression result without tracking signal accesses.
*
* @example
* ```tsx
* import { $, watch } from "rvx";
*
* const count = $(0);
*
* // Capture teardown hooks registered by "watch":
* const dispose = capture(() => {
* // Start watching:
* watch(count, count => {
* console.log("Count:", count);
* });
* });
*
* count.value = 1;
*
* // Stop watching:
* dispose();
*
* count.value = 2;
* ```
*/
export declare function watch(expr: () => void): void;
export declare function watch<T>(expr: Expression<T>, effect: (value: T) => void): void;
/**
* Watch an expression until the current lifecycle is disposed.
*
* @param expr The expression to watch.
* @param effect A function to call with each subsequent expression result without tracking signal accesses.
* @returns The first expression result.
*/
export declare function watchUpdates<T>(expr: Expression<T>, effect: (value: T) => void): T;
/**
* Defer signal updates until a function finishes.
*
* + When nesting batches, updates are processed after the most outer batch has completed.
* + When updates cause immediate side effects, these side effects will run as part of the batch.
*
* @param fn The function to run.
* @returns The function's return value.
*
* @example
* The example below outputs `5` and `9` once. Without batching the output would be `5, 7, 9`.
* ```tsx
* import { batch, $, watch } from "rvx";
*
* const a = $(2);
* const b = $(3);
*
* watch(() => a.value + b.value, value => {
* console.log("Sum:", value);
* });
*
* batch(() => {
* a.value = 4;
* b.value = 5;
* });
* ```
*/
export declare function batch<T>(fn: () => T): T;
/**
* {@link watch Watch} an expression and get a function to reactively access it's result.
*
* @param expr The expression to watch.
* @returns A function to reactively access the latest result.
*
* @example
* ```tsx
* import { $, memo, watch } from "rvx";
*
* const count = $(42);
*
* const computed = memo(() => someExpensiveComputation(count.value));
*
* watch(computed, count => {
* console.log("Count:", count);
* });
* ```
*/
export declare function memo<T>(expr: Expression<T>): () => T;
/**
* Run a function while not tracking signal accesses.
*
* This is the opposite of {@link track}.
*
* @param fn The function to run.
* @returns The function's return value.
*
* @example
* ```tsx
* import { $, untrack, watch } from "rvx";
*
* const a = $(2);
* const b = $(3);
*
* watch(() => a.value + untrack(() => b.value), sum => {
* console.log("Sum:", sum);
* });
*
* a.value = 4;
* b.value = 5;
* ```
*/
export declare function untrack<T>(fn: () => T): T;
/**
* Run a function while tracking signal accesses. This is the default behavior.
*
* This is the opposite of {@link untrack}.
*
* @param fn The function to run.
* @returns The function's return value.
*/
export declare function track<T>(fn: () => T): T;
/**
* Check if signal accesses are currently tracked.
*/
export declare function isTracking(): boolean;
/**
* Run a function while tracking signal accesses to invoke the trigger callback when updated.
*
* See {@link trigger}.
*/
export interface TriggerPipe {
<T>(fn: Expression<T>): T;
}
/**
* Create an expression evaluator pipe that calls a function once when any accessed signals from the latest evaluated expression are updated.
*
* + When the lifecycle at which the pipe was created is disposed, the callback function will not be called anymore.
* + It is guaranteed that the function is called before any other observers like {@link watch} or {@link effect} are notified.
* + If pipes are nested, the callback for the most inner one is called first.
*
* @param callback The callback to invoke when a signal is updated.
* @returns The pipe to evaluate expressions.
*/
export declare function trigger(callback: () => void): TriggerPipe;
/**
* Manually evaluate an expression in the current context.
*
* This can be used to access reactive and non reactive inputs.
*
* @param expr The expression to evaluate.
* @returns The expression result.
*
* @example
* ```tsx
* import { $, get } from "rvx";
*
* const count = $(42);
*
* get(42) // 42
* get(count) // 42
* get(() => 42) // 42
* ```
*/
export declare function get<T>(expr: Expression<T>): T;
export type MapFn<I, O> = (input: I) => O;
/**
* Map an expression value while preserving if the expression is static or not.
*
* @example
* ```tsx
* import { $, map, get } from "rvx";
*
* const count = $(42);
* const doubleCount = map(count, value => value * 2);
*
* get(doubleCount) // 84
* ```
*/
export declare function map<I, O>(input: Expression<I>, mapFn: MapFn<I, O>): Expression<O>;