resy
Version:
React State Easy
360 lines (347 loc) • 17.2 kB
TypeScript
/**
* resy
* An easy-to-use React data state manager
* created by liushanbao <1262300490@qq.com>
* (c) 2020-05-05-2025-3-02
* Released under the MIT License.
*/
import { Component, PureComponent } from 'react';
type Callback = () => void;
type PrimitiveState = Record<number | string, any>;
type AnyFn = (...args: unknown[]) => unknown;
type ValueOf<S extends PrimitiveState> = S[keyof S];
type MapType<S extends PrimitiveState> = Map<keyof S, ValueOf<S>>;
type PrimitiveValueType = "Number" | "String" | "Boolean" | "Undefined" | "Null" | "Symbol";
type ComplexValueType = "Object" | "Function" | "Array" | "Date" | "RegExp" | "Map" | "Set" | "WeakMap" | "WeakSet" | "ArrayIterator" | "SetIterator" | "MapIterator" | "WeakRef" | "BigInt" | "Arguments" | "Promise" | "AsyncFunction" | "FormData" | "Blob" | "File" | "Error" | "CustomEvent" | "Storage" | "WebSocket" | "ArrayBuffer" | "DataView" | "Uint8Array" | "Int8Array" | "Uint8ClampedArray" | "Int16Array" | "Uint16Array" | "Int32Array" | "Uint32Array" | "Float32Array" | "Float64Array" | "BigInt64Array" | "BigUint64Array" | "XMLHttpRequest" | "Headers" | "Request" | "Response" | "Window" | "Global";
type NativeDataType = PrimitiveValueType | ComplexValueType;
/** Type of unsubscribe */
type Unsubscribe = () => void;
/**
* The parameter type of the subscription listening function
* @description subscribe possesses the characteristic scheduling mechanism of Resy itself,
* as well as the ability to handle batch updates.
* effectState is the data state for a batch change,
* while prevState and nextState represent the data states before and after a batch change.
*/
type ListenerParams<S extends PrimitiveState> = {
effectState: Readonly<Partial<S>>;
nextState: Readonly<S>;
prevState: Readonly<S>;
};
/** Type of monitoring callback for subscription */
type ListenerType<S extends PrimitiveState> = (data: ListenerParams<S>) => void;
/** Type of subscribe */
type SubscribeType<S extends PrimitiveState> = {
/**
* @param listener
* @param stateKeys Array of monitored data attributes
* @return unsubscribe
*/
subscribe(listener: ListenerType<S>, stateKeys?: (keyof S)[]): Unsubscribe;
};
type SubscriptionRefType<S extends PrimitiveState> = {
listener: ListenerType<S>;
stateKeys?: (keyof S)[];
};
declare const __CLASS_CONNECT_STORE_KEY__: unique symbol;
declare const __CLASS_THIS_POINTER_STORES_KEY__: unique symbol;
declare const __CLASS_STATE_REF_SET_KEY__: unique symbol;
/** This is the data type returned by the class after connecting to the store */
type ClassStoreType<S extends PrimitiveState> = S & StoreCoreUtils<S> & Readonly<SetOptionsType> & Readonly<GetOptionsType>;
type ClassConnectStoreType = {
[__CLASS_CONNECT_STORE_KEY__]<S extends PrimitiveState>(): ClassStoreType<S>;
};
/**
* @description The second parameter configuration item of createStore
*/
interface StoreOptions {
/**
* @description Whether to reset and restore the data to its initial state
* when all modules used by the current page are unmount.
* In the case of global data such as login information or themes,
* it would be set to false,
* so that the resulting loginStore or themeStore can take effect globally across the system.
* @default true
*/
readonly unmountRestore?: boolean;
/**
* @description The abstract name or namespace of the store,
* when it is difficult to distinguish the states of the same key by mixing stores in a complex module,
* which helps identify specific stores and facilitates finding component elements during debugging.
* @default undefined
*/
readonly namespace?: string;
/**
* @description For the configuration options of defineStore,
* the function properties of defineStore can possess the capability to update and render state data.
* This configuration is not commonly used;
* it aims to maintain a certain level of openness and flexibility in its application.
* @default undefined
*/
readonly enableMarcoActionStateful?: boolean;
}
interface InnerStoreOptions extends StoreOptions {
/**
* @description Configuration for useConciseState hooks (Internal use, do not use externally)
* @default undefined
*/
readonly __useConciseState__?: boolean;
/**
* @description This feature is designed to be used with the defineStore function.
* It enables and optimizes various usage scenarios for stores defined using the defineStore macro.
* @default undefined
*/
readonly __enableMacros__?: boolean;
/**
* @description Call name of function, used for internal processing of error message prompts.
* @default "createStore"
*/
readonly __functionName__?: string;
}
/** Some of the core tool method types of store */
type StoreCoreUtils<S extends PrimitiveState> = Readonly<SetStateType<S> & SyncUpdateType<S> & RestoreType<S> & SubscribeType<S>>;
type StoreHookUtils<S extends PrimitiveState> = Readonly<UseStoreType<S> & UseSubscriptionType<S>>;
/** Tool method type of store */
type StoreUtils<S extends PrimitiveState> = StoreCoreUtils<S> & StoreHookUtils<S> & Readonly<SetOptionsType> & Readonly<GetOptionsType>;
/** The type of store returned by createStore */
type Store<S extends PrimitiveState> = S & StoreUtils<S>;
interface StoreType<S extends PrimitiveState> {
readonly store: Store<S>;
}
/** Update the data type of the parameter */
type State<S extends PrimitiveState> = Partial<S> | S | null;
/**
* The type of update parameter is function parameter
* @description The presence of prevState is necessary.
* In complex business updating logic and event loops,
* being able to directly obtain the previous synchronized state
* through simple synchronous code is a very smooth and simple method.
*/
type StateFnType<S extends PrimitiveState> = (prevState: Readonly<S>) => State<S>;
type SetStateAction<S extends PrimitiveState> = State<S> | StateFnType<S>;
/** Type of setState */
type SetStateType<S extends PrimitiveState> = {
/**
* @param state
* @param callback
*/
setState(state: SetStateAction<S>, callback?: StateCallback<S>): void;
};
/**
* Type of callback functions for setState, syncUpdate, and restore
* @description The existence of the nextState parameter in the callback is also necessary for reasons similar to prevState.
*/
type StateCallback<S extends PrimitiveState> = (nextState: Readonly<S>) => void;
/** Type of syncUpdate */
type SyncUpdateType<S extends PrimitiveState> = {
/**
* @param state
* @param callback
*/
syncUpdate(state: SetStateAction<S>, callback?: StateCallback<S>): void;
};
/** Type of restore */
type RestoreType<S extends PrimitiveState> = {
/**
* @param callback
* @description The reason for not naming it reset is due to
* the consideration of scenarios where the createStore parameter might be a function.
* In such cases, logically speaking, it's not so much about resetting but rather about restoring.
* As for what state it restores to depends on the result returned by the execution of the initialization function.
* Hence, the choice of the name restore instead of reset.
*/
restore(callback?: StateCallback<S>): void;
};
/**
* Type of setOptions
* @description The reason why changing StoreOptions is permissible is due to the needs of business scenarios
* and the desire to keep development channels open and flexible.
* This is because the static execution of createStore is by itself a limitation.
* If there is a need for sudden changes in certain scenarios
* and the static parameter settings cannot be made changeable at that time,
* it would be a constraint. The existence of setOptions can lift this static restriction,
* and setOptions should be considered an aid. However, generally speaking,
* the configuration of StoreOptions itself should meet and satisfy the vast majority of usage scenarios,
* so the occurrences where setOptions is needed are still relatively rare.
*/
type SetOptionsType = {
setOptions(options: Readonly<{
unmountRestore: boolean;
}>): void;
};
/**
* @description When executed in conjunction with "setOptions",
* it allows users to make different coding decisions based on various configurations,
* while being aware of the current settings.
* 🌟 Different from the considerations for the parameter types of "setOptions",
* "getOptions" returns a configuration object for all settings.
* This is because these read-only settings do not affect code security,
* and the parameters for "setOptions" are only aimed at the "unmountRestore" configuration item.
* Providing all configuration items may also be for the convenience of subsequent internal coding considerations.
*/
type GetOptionsType = {
getOptions(): InnerStoreOptions;
};
/** type of useStore */
type UseStoreType<S extends PrimitiveState> = {
useStore(): ClassicStore<S>;
};
/**
* @description The store returned by `createStore`,
* which then calls the result returned by `useStore`,
* is referred to as the classical type of store.
*/
type ClassicStore<S extends PrimitiveState> = Omit<Store<S>, "useStore">;
/** A preprocessed store that is ready for immediate rendering */
type MacroStore<S extends PrimitiveState> = ClassicStore<S> & StoreType<S>;
/** The function type returned by definiteStore */
type UseMacroStore<S extends PrimitiveState> = () => MacroStore<S>;
/**
* type of useSubscription
* @description It`s advantage is that you only need to consider the data you want to subscribe to,
* rather than the psychological burden to consider whether the data reference inside the function can get the latest value.
* UseSubscription will reduce your mental burden and allow you to use it normally.
*/
interface UseSubscriptionType<S extends PrimitiveState> {
useSubscription(listener: ListenerType<S>, stateKeys?: (keyof S)[]): void;
}
/** Type of key disabled in the initialization parameters */
type InitialStateForbiddenKeys = keyof StoreUtils<PrimitiveState> | "store";
/** The type of this context in function properties (actions) within initialState. */
type StateThis<S extends PrimitiveState> = {
[K in keyof S]: K extends InitialStateForbiddenKeys ? never : S[K];
} & Store<S>;
/** Parameter types disabled for initialization of InitialState */
type PrimateForbiddenType = number | string | null | Symbol | boolean | Set<any> | Map<any, any> | Array<any> | WeakSet<any> | WeakMap<any, any> | WeakRef<any> | RegExp | BigInt | Date;
/** Parameter types with this type pointing to identification */
type StateWithThisType<S extends PrimitiveState> = S extends PrimateForbiddenType ? never : S & {
[K in keyof S]: K extends InitialStateForbiddenKeys ? never : S[K];
} & ThisType<StateThis<S>>;
/** Type of initialize data */
type InitialState<S extends PrimitiveState> = (() => StateWithThisType<S>) | StateWithThisType<S>;
/**
* created by liushanbao
* @description An easy-to-use React state manager
* @author liushanbao
* @date 2022-05-05
* @name createStore
*/
/**
* createStore
* created by liushanbao
* @description Create a state storage container that can be used globally
* @author liushanbao
* @date 2022-05-05
* @param initialState
* @param options
* @return Store<S>
*/
declare const createStore: <S extends PrimitiveState>(initialState?: InitialState<S>, options?: StoreOptions) => Store<S>;
/**
* useStore api
* @description useStore(store) === store.useStore()
* @param store
* @return store
*/
declare const useStore: <S extends PrimitiveState>(store: Store<S>) => ClassicStore<S>;
/**
* A concise version of useState
* @description The functionality of useConciseState is not limited to just a concise syntax on the surface.
* Its deeper capability is to deconstruct the store and provide sub-components with a doorway
* that allows for comprehensive control over the store's data, rendering, updates, and subscriptions.
* @example:
* const { count, text, setState } = useConciseState({ count: 0, text: "hello" });
* equivalent to:
* const [count, setCount] = useState(0);
* const [text, setText] = useState("hello");
* 🌟 useConciseState is relatively simple and clear to use compared to useState when dealing with multiple data states.
* 🌟 Furthermore, within useConciseState, the store attribute can be parsed out, and through the store,
* the latest data values of various items can be accessed,
* compensating for the shortfall in useState where the latest values of attribute data cannot be retrieved.
* @param initialState
* @return MacroStore<S>
*/
declare const useConciseState: <S extends PrimitiveState>(initialState?: InitialState<S>) => MacroStore<S>;
/**
* @description Define the preprocessed store type for the createStore macro,
* which essentially represents the return type of the useStore function.
*
* 🌟 This API primarily follows the macro definition convention from the C programming language,
* hence the use of "define" as a prefix for naming.
*
* 🌟 `defineStore` will render the functions within the store incapable of updating,
* unlike `createStore`, and they will merely serve as actions.
* If you have a specific reason to enable function properties with the capability to update rendering,
* you can turn it on through the configuration option "enableMarcoActionStateful".
*
* 🌟 In the future, `defineStore` will gradually replace `createStore` as the norm for usage,
* and it possesses more rationality.
* The existence of `defineStore` also implies that Resy's focus will shift towards hooks components,
* moving away from an adherence to class components.
*
* 🌟 `defineStore`, in fact, returns the `useStore` function,
* which on one hand reflects the characteristics of functional programming,
* and on the other hand, stems from React's restrictions on hook usage
*
* 😊 If React had no restrictions on hook usage,
* I would prefer to directly return the final store instead of redundantly calling `useStore()` again.
*
* @example
* ```tsx
* interface StateModel {
* count: number;
* increase(): void;
* }
*
* const useStore = defineStore<StateModel>({
* count: 0,
* increase() {
* this.count += 1;
* },
* });
*
* const App = () => {
* const { count, increase } = useStore();
*
* return (
* <>
* <p>count:{count}</p>
* <button onClick={increase}>increase</button>
* </>
* );
* };
* ```
*/
declare const defineStore: <S extends PrimitiveState>(initialState?: InitialState<S>, options?: StoreOptions) => UseMacroStore<S>;
/**
* @description Hook of subscribe
* It`s advantage is that you only need to consider the data you want to subscribe to,
* rather than the psychological burden to consider whether the data reference inside the function can get the latest value.
* UseSubscription will reduce your mental burden and allow you to use it normally.
*/
declare const useSubscription: <S extends PrimitiveState>(store: Store<S>, listener: ListenerType<S>, stateKeys?: (keyof S)[]) => void;
/**
* @class ComponentWithStore
* @classdesc The public base class can connect to the store
*/
declare class ComponentWithStore<P extends PrimitiveState = {}, S extends PrimitiveState = {}, SS = any> extends Component<P, S, SS> {
constructor(props: P);
static displayName: undefined | string;
[__CLASS_STATE_REF_SET_KEY__]: Set<keyof S>;
[__CLASS_CONNECT_STORE_KEY__]: ValueOf<ClassConnectStoreType> | undefined;
[__CLASS_THIS_POINTER_STORES_KEY__]: Set<Store<any>>;
connectStore<S extends PrimitiveState>(store: Store<S>): ClassStoreType<S>;
}
/**
* @class PureComponentWithStore
* @classdesc The public base class can connect to the store
*/
declare class PureComponentWithStore<P extends PrimitiveState = {}, S extends PrimitiveState = {}, SS = any> extends PureComponent<P, S, SS> {
constructor(props: P);
static displayName: undefined | string;
[__CLASS_STATE_REF_SET_KEY__]: Set<keyof S>;
[__CLASS_CONNECT_STORE_KEY__]: ValueOf<ClassConnectStoreType> | undefined;
[__CLASS_THIS_POINTER_STORES_KEY__]: Set<Store<any>>;
connectStore<S extends PrimitiveState>(store: Store<S>): ClassStoreType<S>;
}
export { type AnyFn, type Callback, type ClassStoreType, type ClassicStore, type ComplexValueType, ComponentWithStore, type GetOptionsType, type InitialState, type InitialStateForbiddenKeys, type InnerStoreOptions, type ListenerParams, type ListenerType, type MacroStore, type MapType, type NativeDataType, type PrimateForbiddenType, type PrimitiveState, type PrimitiveValueType, PureComponentWithStore, type RestoreType, type SetOptionsType, type SetStateAction, type SetStateType, type State, type StateCallback, type StateFnType, type StateThis, type StateWithThisType, type Store, type StoreCoreUtils, type StoreHookUtils, type StoreOptions, type StoreType, type StoreUtils, type SubscribeType, type SubscriptionRefType, type SyncUpdateType, type Unsubscribe, type UseStoreType, type UseSubscriptionType, type ValueOf, createStore, defineStore, useConciseState, useStore, useSubscription };