UNPKG

wana

Version:

Easy observable state for React

351 lines (326 loc) 13.8 kB
import { ReactElement, Ref, RefAttributes } from 'react'; interface Disposable { /** Release any memory that would otherwise leak */ dispose: () => void; } declare type Falsy = false | null | undefined; /** For accessing the proxied state */ declare const $$: unique symbol; /** For storing observers and the observable proxy */ declare const $O: unique symbol; /** For injecting custom proxy traps */ declare const $T: unique symbol; /** Watch the properties of the given observable object */ declare const shallowChanges: (target: ObserverTarget, onChange: (change: Change) => void) => { onChange: (change: Change) => void; dispose(): void; }; /** A function that can subscribe to observable objects. */ declare abstract class Observer implements Disposable { observed: ReadonlySet<ObservedSlot>; onChange: ((change: Change) => void) | null; /** The current nonce of our observed values combined */ get nonce(): number; dispose(): void; } /** @internal */ declare class Observable<T extends object = any> extends Map<ObservedKey, ObservedSlot> { readonly source?: T | undefined; readonly proxy: T | undefined; constructor(source?: T | undefined); /** Return true if at least one observer exists for the given key. */ has(key: ObservedKey): boolean; /** Return a `Set` of observers for the given key, even if not observed. */ get(key: ObservedKey): ObservedSlot; /** Create an observer for the given key. */ observe(key: ObservedKey, onChange: (change: Change) => void): { onChange: (change: Change) => void; dispose(): void; }; } /** Mutable state with an associated observable */ declare type ObserverTarget = object & { [$O]?: Observable; }; /** Any value acting as an object key */ declare type ObservedKey = any; /** An observer set with metadata about what's being observed */ declare class ObservedSlot extends Set<ChangeObserver> { readonly owner: Observable; readonly key: ObservedKey; nonce: number; constructor(owner: Observable, key: ObservedKey); onChange(change: Change): void; } /** An observed mutation of an observable object. */ interface Change<T = any> { op: 'add' | 'replace' | 'remove' | 'splice' | 'clear' | 'define'; target: object; key?: any; value?: T; oldValue?: T; } /** The most basic observer of changes. */ interface ChangeObserver extends Disposable { onChange: ((change: Change) => void) | null; } /** Run an effect when its tracked values change. */ declare function auto(effect: () => void, config?: AutoConfig): Auto; interface AutoConfig { /** When true, react to changes immediately. By default, changes are delayed until the next microtask loop */ sync?: boolean; /** By default, rerun the last effect (after any delay) */ onDirty?: Auto['onDirty']; /** By default, errors are rethrown */ onError?: Auto['onError']; /** Run an effect after an `observer` is activated */ onCommit?: Auto['onCommit']; /** Run an effect after being disposed */ onDispose?: Auto['onDispose']; } declare class Auto { sync: boolean; dirty: boolean; nonce: number; observer: AutoObserver | null; onDirty: (this: Auto) => void; onError: (this: Auto, error: Error) => void; onCommit: (observer: AutoObserver) => void; onDispose: () => void; constructor(config?: AutoConfig); run<T extends Function>(compute: T): any; /** Rerun the last effect and commit its observer */ rerun(): any; /** * Replace the current `observer`. * * Pass a nonce to bail out if observed values have since changed. */ commit(observer: AutoObserver, nonce?: number): boolean; dispose(): void; /** * @internal * Create an observer and start observing. * * The given `effect` is used by `rerun` calls made after * the new observer is committed. */ start(effect: Function): AutoObserver; /** * @internal * Stop observing and reset the `dirty` flag. */ stop(): this; /** * @internal * Replace the current `observer` when appropriate. */ clear(): void; protected _onChange(change: Change): void; protected _onDirty(): void; } declare class AutoObserver extends Observer { readonly effect: Function; observed: Set<ObservedSlot>; constructor(effect: Function); } /** * An observable getter that memoizes its result. * * The memoization is observed, so when a dependency changes, * the memoized value is released and observers are notified. */ interface Derived<T = any> extends Disposable { /** The underlying observable */ [$O]?: Observable; /** The underlying observer */ auto: Auto; /** Get the current value */ (): T; } declare function isDerived(value: unknown): value is Derived; /** Convert all `Derived<T>` property types into `T` */ declare type WithDerived<T extends object> = { [P in keyof T]: T[P] extends Derived<infer U> ? U : T[P]; }; /** * Pass an **object** to receive an observable proxy. * Pass a **function** to receive an observable getter. * Anything else is returned as-is. */ declare function o<T>(value: T): T extends () => infer U ? Derived<U> : T extends Function ? Derived : T; /** * Create a promise to resolve when the given condition returns true. * Any observable access in the condition is tracked. */ declare const when: (condition: () => boolean) => Promise<void>; declare type WatchedState = ObserverTarget & { forEach?: (cb: (value: any, key: any, ctx: any) => void) => void; }; declare type ChangeHandler = (change: Change) => void; /** * Watch a single property. * If its value is observable, watch it recursively. */ declare function watch<P>(root: Map<P, any>, key: P, onChange: (change: Change) => void): Watcher; declare function watch<T extends object, P extends string & keyof T>(root: T, key: P, onChange: (change: Change) => void): Watcher; /** * Watch an observable tree for changes. * Only observable objects are searched for watchable values. */ declare function watch(root: object, onChange: ChangeHandler): Watcher; /** An observer of deep changes */ declare class Watcher extends Observer { readonly root: object; readonly key?: any; observed: Set<ObservedSlot>; counts: Map<object, number>; constructor(root: object, onChange: ChangeHandler, key?: any); watch: (value: any, key?: any, ctx?: any) => void; unwatch: (value: any, key?: any, ctx?: any) => void; dispose(): void; protected _watch(target: WatchedState): void; protected _unwatch(target: WatchedState): void; } /** * Get the original object from an observable proxy, * or wrap a function to disable observation inside it. * * Read `no` as "non-observable", essentially the reverse of the `o` function. */ declare function no<T>(value: T): T; /** Run an effect without any observable tracking */ declare function noto<T>(effect: () => T): T; interface DebugState { name: string; actions?: any[]; renders?: number; } /** Get the `Auto` object for the current `withAuto` component being rendered. */ declare function getCurrentAuto(): Auto | null; /** * Get the `DebugState` object of the `target` object. * * Returns `undefined` if target was never passed to `setDebug`. */ declare function getDebug(target: object): DebugState; /** * Set the `DebugState` object of the `target` object. */ declare function setDebug<T>(target: T, debug: DebugState): T; /** Safely add an action to a `DebugState` object */ declare function addDebugAction(target: any, action: any): void; declare type OnRender = (auto: Auto, depth: number, component: React.FunctionComponent<any>) => void; declare type Globals = { /** Notify the current observer. */ observe: ((target: ObserverTarget, key: any) => void) | null; /** The `Auto` object for the current `withAuto` component being rendered. */ auto: Auto | null; /** For debugging re-renders. Only called in development. */ onRender: OnRender | null; /** For spying on every change event. */ onChange: ((change: Change) => void) | null; }; declare const globals: Globals; /** * Flush the queue of delayed reactions. * * Returns `true` if the queue wasn't flushed entirely. * * Useful when testing `wana`-integrated components/hooks. */ declare function flushSync(): boolean; interface Props { observer?: AutoObserver; mounted?: boolean; } /** * Call the returned function to set the "mounting state" for the * given `Auto` object. Observation is postponed until mounted. */ declare function mountAuto(auto: Auto): (props: Props) => void; declare const ObjectTraps: ProxyHandler<object>; declare function useO<T extends object>(state: Exclude<T, Function>, deps?: readonly any[]): WithDerived<T>; /** * Create an observable getter that is managed by React. * This lets you memoize an expensive combination of observable values. */ declare function useO<T>(create: () => () => T, deps?: readonly any[]): Derived<T>; /** Create observable component state. */ declare function useO<T>(create: () => Exclude<T, Function>, deps?: readonly any[]): T; /** Memoize an object and return its observable proxy. Non-objects are returned as-is. */ declare function useO<T>(state: T, deps?: readonly any[]): T; declare type Deps$1 = readonly any[]; declare type EffectReturn = void | (() => void | undefined); /** Wrap a `useEffect` call with magic observable tracking */ declare function useAuto(effect: () => EffectReturn, deps?: Deps$1): Auto; /** * Combine a `useEffect` call with an `Auto` instance that invokes an * unobserved side `effect` when the `compute` function returns a new value. */ declare function useAuto<T>(compute: () => T, effect: (value: T) => EffectReturn, deps?: Deps$1): Auto; interface Component<P = any> { (props: P): ReactElement | null; displayName?: string; } interface RefForwardingComponent<T = any, P = any> { (props: P, ref: Ref<T>): ReactElement | null; displayName?: string; } declare type RefForwardingAuto<T extends RefForwardingComponent> = T & ((props: T extends RefForwardingComponent<infer U, infer P> ? P & RefAttributes<U> : never) => ReactElement | null); /** Wrap a component with magic observable tracking */ declare function withAuto<T extends Component>(render: T): T; /** Wrap a component with `forwardRef` and magic observable tracking */ declare function withAuto<T extends RefForwardingComponent>(render: T): RefForwardingAuto<T>; /** * Create an observable getter that is managed by React. * This lets you memoize an expensive combination of observable values. * * If the `compute` argument changes over the course of a component's lifetime, * its value should be added to the `deps` array. * * When `deps` are changed, the derived state is reset. This is useful when * the `compute` function is using a variable from another function scope. */ declare function useDerived<T>(compute: () => T, deps?: readonly any[]): Derived<T>; declare function useDerived<T>(compute: (() => T) | Falsy, deps?: readonly any[]): Derived<T> | null; declare function useDerived<T>(compute: () => T, discard: (memo: T, oldMemo: T | undefined) => boolean, deps?: readonly any[]): Derived<T>; declare function useDerived<T>(compute: (() => T) | Falsy, discard: (memo: T, oldMemo: T | undefined) => boolean, deps?: readonly any[]): Derived<T> | null; /** Listen for shallow changes to an observable object. */ declare function useChanges(target: ObserverTarget, onChange: (change: Change) => void, deps?: readonly any[]): void; declare type Deps = readonly any[]; declare type UnmountFn = () => undefined | void; declare type Source<T = any> = { [key: string]: T; } | ReadonlyArray<T> | ReadonlySet<T> | ReadonlyMap<any, T>; declare type Effect<T extends Source> = T extends Source<infer U> ? T extends ReadonlyMap<infer P, U> ? (value: U, key: P) => UnmountFn | void : T extends ReadonlyArray<U> | ReadonlySet<U> ? (value: U) => UnmountFn | void : (value: U, key: string) => UnmountFn | void : never; /** * Create a layout effect for every key of your `values` object. * * Values are passed to your `effect` function as they are added and replaced. * The `effect` can return a cleanup function to call for removed values. * * Map objects use their keys */ declare function useEffects<T extends Source>(source: T, effect: Effect<T>, deps?: Deps): void; /** * An alternative to `withAuto` that re-renders the caller * when the given observable value is changed. * * If you pass an object without a key, the entire object * is observed. * * ⚠️ This hook has performance drawbacks! It cannot automatically * batch React updates, so you need to wrap changes with `batchedUpdates` * to avoid multiple re-renders. It also cannot wait for ancestor * components to re-render first, which also leads to excessive re-renders. */ declare function useBinding<T extends object, P extends keyof T>(target: T extends ReadonlyMap<any, any> ? never : T, key: P): T[P]; declare function useBinding<K, V>(target: ReadonlyMap<K, V>, key: K): V | undefined; declare function useBinding<T>(target: Derived<T>): T; declare function useBinding<T extends object>(target: T): T; /** @internal */ declare const useAutoContext: () => { depth: number; }; export { $$, $O, $T, Auto, AutoConfig, Change, ChangeObserver, Derived, ObjectTraps, Observable, ObservedSlot, Observer, ObserverTarget, Watcher, addDebugAction, auto, flushSync, getCurrentAuto, getDebug, globals, isDerived, mountAuto, no, noto, o, setDebug, shallowChanges, useAuto, useAutoContext, useBinding, useChanges, useDerived, useEffects, useO, watch, when, withAuto };