UNPKG

rvx

Version:

A signal based rendering library

345 lines (344 loc) 9.41 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. * * @param expr The expression to watch. * @param fn The function to call with the expression result. This is guaranteed to be called at least once immediately. Lifecycle hooks are called before the next function call or when the current lifecycle is disposed. * * @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<T>(expr: Expression<T>, fn: (value: T) => void): void; /** * Watch an expression until the current lifecycle is disposed. * * @param expr The expression to watch. * @param fn The function to call with the expression result when any updates occur. * @returns The first expression result. */ export declare function watchUpdates<T>(expr: Expression<T>, fn: (value: T) => void): T; /** * Run and watch a function until the current lifecycle is disposed. * * Note, that this doesn't separate signal accesses from side effects which makes it easier to accidentally cause infinite loops. If possible, use {@link watch} or {@link watchUpdates} instead. * * @param fn The function to run. Lifecycle hooks are called before the next function call or when the current lifecycle is disposed. */ export declare function effect(fn: () => void): void; /** * 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; /** * Run and watch a function until the current lifecycle is disposed. * * This is similar to {@link effect}, but returns a function to reactively access the latest return value. * * @param fn The function to run. * @returns A function to 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>(fn: 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 a currently evaluating expression is tracking signal accesses. */ export declare function isTracking(): boolean; /** * A function to evaluate an expression while tracking signal accesses. * * See {@link trigger}. */ export interface TriggerPipe { <T>(expr: 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 fn The callback to invoke when a signal is updated. * @returns The pipe to evaluate expressions. */ export declare function trigger(fn: () => void): TriggerPipe; /** * Evaulate an expression. * * 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>;