ptrs
Version:
Manage your state with pointers.
156 lines (151 loc) • 7.17 kB
TypeScript
/**
* 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 };