UNPKG

ptrs

Version:

Manage your state with pointers.

156 lines (151 loc) 7.17 kB
/** * A pointer references to an actual value. A pointer is stable and won't change * when the value changes. The value of a pointer can be accessed and changed. * * ``` * pointer() // gets the value * pointer(newValue) // sets a new value * ``` */ type Pointer<T> = (NonNullable<T> extends object ? (PointerFunc<T> & ComplexPointer<NonNullable<T>>) : PointerFunc<T>) & NeverFunctionProp<T>; /** * Defines a valid pointer value. */ type PointerValue<T> = T extends UndefinedToOptional<T> ? T : UndefinedToOptional<T>; /** * Makes sure that a type is not a pointer. */ type NoPointer<T> = T extends Pointer<any> ? never : T; /** * The property should be handled like a pointer. */ type PointerPropertyTypePointer = "pointer"; /** * The property is a function that mutates the pointer. * Can also be used for get-set properties where also the getter * changes the pointer. */ type PointerPropertyTypeMutate = "mutate"; /** * The pointer is a function that computes values without changing the pointer. * This can be used as a performance optimization, because "mutate", the default * function behavior, has an overhead of creating new pointer values. * Can also be used for properties that just act as a getter. */ type PointerPropertyTypeReadonly = "readonly"; /** * The property has a readonly get and a mutating set function. */ type PointerPropertyTypeGetSet = "get-set"; /** * All possible pointer property types. */ type PointerPropertyType = PointerPropertyTypePointer | PointerPropertyTypeMutate | PointerPropertyTypeReadonly | PointerPropertyTypeGetSet; declare const PointerProperties: unique symbol; /** * Valid pointer signatures */ type PointerFunc<T> = { (): T; <V extends T>(value: NoPointer<V>): void; }; type ComplexPointer<T extends object> = T extends Array<any> ? ArrayPointer<T> : ObjectPointer<T>; type ArrayPointer<T> = T extends Array<infer I> ? { readonly [P in number]: Pointer<I>; } & Omit<T, number> : never; type ObjectPointer<T extends object> = { [P in keyof T]-?: T[P] extends Function ? ObjectPointerFunctionProperty<T, P> : ObjectPointerProperty<T, P>; }; type ObjectPointerProperty<T extends object, P extends keyof T> = T extends { [PointerProperties]: Partial<{ [PP in P]: Exclude<PointerPropertyType, PointerPropertyTypePointer>; }>; } ? T[P] : Pointer<T[P]>; type ObjectPointerFunctionProperty<T extends object, P extends keyof T> = T extends { [PointerProperties]: Partial<{ [PP in P]: PointerPropertyTypePointer; }>; } ? Pointer<T[P]> : T[P]; /** * We can't hide function properties, otherwise the pointer would not be callable. * But we can make them never, so that they are not "usable". */ type NeverFunctionProp<T> = { [P in Exclude<keyof Function, keyof NonNullable<T>>]: never; }; /** * Convert undefined value to optional property. */ type UndefinedToOptional<T> = T extends object ? (T extends any[] ? UndefinedToOptionalArray<T> : (T extends Function ? T : UndefinedToOptionalObject<T>)) : T; type UndefinedToOptionalArray<T> = T extends Array<infer I> ? Array<UndefinedToOptional<I>> : T; type UndefinedToOptionalObject<T> = { [K in keyof T as undefined extends T[K] ? never : K]: UndefinedToOptional<T[K]>; } & { [K in keyof T as undefined extends T[K] ? K : never]?: UndefinedToOptional<Exclude<T[K], undefined>>; }; /** * Creates a pointer for a value. The given value is the initial value of the pointer. * The setter is called when the pointer is set to a new value, so an external state, like * a variable or useState, can be updated. However, that external state should not update its value anymore. * Updates must be done through the pointer itself, otherwise the pointer won't see the changes. * To enforce this the value will be frozen. * * @param value The initial value of the pointer. * @param setter A function that will be called when the pointer is set. */ declare const createPointer: <T>(value: PointerValue<T>, setter?: (newData: typeof value) => void) => Pointer<typeof value>; /** * Adds a schema to a value that defines how a pointer for the given value should behave. * @param objectOrClass * @param map A map defining the properties. This map will be frozen, so it can't be changed later. * @returns */ declare const pointerSchema: <T extends object, M extends Partial<Record<keyof T, PointerPropertyType>>, O extends T & { [PointerProperties]: M; }>(objectOrClass: T, map: M) => O; /** * Creates a pointer to a new state. * @param initialValue The initial value of the pointer/state. * @param usePointerHook If true, the pointer will be registered with the usePointer hook. */ declare const useStatePointer: <T>(initialValue: PointerValue<T>, usePointerHook?: boolean) => Pointer<PointerValue<T>>; /** * Basically like useStatePointer, but the pointer value updates if the given value changes. * @param value The value which the pointer holds. * @param usePointerHook If true, the pointer will be registered with the usePointer hook. */ declare const useMemoPointer: <T>(value: PointerValue<T>, usePointerHook?: boolean) => Pointer<PointerValue<T>>; /** * Pointers are stable, which means that e.g. a component with a pointer prop will not re-render * when the pointer value changes. If re-rendering is needed, this hook can be used. * A re-render will happen when the value of one pointer changes. * The list of pointers can be different on each call/render (the order or count can change). * As an alternative, the `ptrs` component can be used, which will automatically * re-render when any pointer used in the component changes. * @param pointers A list of pointers that should cause a re-render when their value changes. */ declare const usePointer: (...pointers: Pointer<any>[]) => void; /** * ptrs watches for pointer usage in the component. * Conditional usage is no problem, unlike with usePointer (can't be used in ifs, like any other hook). * The component will re-render if the component used a pointer's value during rendering, that now changed. * It only watches for itself, not for pointer usage in its children. * * Conditional rendering works like: * ``` * if (pointerA()) { * console.log(pointerB()); // change of pointerB will only re-render if pointerA was truthy * } * ``` * @param component The component to watch * @returns A wrapped component that behaves like the original component, but watches for pointer usage. */ declare const ptrs: <P extends {}>(component: React.FunctionComponent<P>) => (props: P) => ReturnType<typeof component>; /** * Subscribes to pointer changes. * @param subscriber The subscriber function that will be called when the pointer changes. * @param pointer One or more pointers to subscribe to. * @returns Function that can be used to unsubscribe from pointers. No argument -> all pointers will be unsubscribed. */ declare const subscribe: <T>(subscriber: (p: Pointer<T>) => void, ...pointer: Pointer<T>[]) => (...pointers: Pointer<T>[]) => void; export { type NoPointer, type Pointer, type PointerValue, createPointer, pointerSchema, ptrs, subscribe, useMemoPointer, usePointer, useStatePointer };