UNPKG

rvx

Version:

A signal based rendering library

339 lines (338 loc) 9.11 kB
/** * 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>;