UNPKG

ron-store

Version:

Lightweight store for React apps

134 lines (132 loc) 3.87 kB
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { useEffect, useRef, useState } from "react"; export type PackageType = Record<string, any>; //Cho phép dev chỉ tương tác với các reducer qua IPackage thôi export interface IPackage<T extends PackageType> { getState(): T; setState(callback: (state: T) => T): void; } export type Action<T extends PackageType> = ( pkg: IPackage<T>, //Cho phép dev chỉ tương tác với các reducer qua IPackage thôi payload?: any ) => void; export type Reducer<T extends PackageType> = Record<string, Action<T>>; type SubcribeCallback<T extends PackageType> = (state: T) => void; /** * Class Package */ export class Package<T extends PackageType, R extends Reducer<T>> implements IPackage<T> { private state$: T; private reducers$: R; private listeners$: Record<number, SubcribeCallback<T>> = {}; constructor({ initState, reducers }: { initState: T; reducers: R }) { this.state$ = initState; this.reducers$ = reducers; } getState() { return this.state$; } get state() { return this.state$; } get reducers() { return this.reducers$; } setState(callback: (state: T) => T) { this.state$ = callback(this.state$); } get listeners() { return this.listeners$; } /** * * @param callback * @returns * Nếu listenerId trùng thì đệ quy */ subcribe(callback: SubcribeCallback<T>): number { const listenerId = Date.now(); if (this.listeners$[listenerId]) { return this.subcribe(callback); } this.listeners$[listenerId] = callback; return listenerId; } unsubcribe(listenerId: number) { delete this.listeners$[listenerId]; } } /** * usePackage */ export interface UsePackageReturn<T extends PackageType, R extends Reducer<T>> { state: T; dispatch: (name: keyof R, payload?: any) => void; subcribe: (callback: SubcribeCallback<T>) => number; unsubcribe: (listenerId: number) => void; } export function usePackage<T extends PackageType, R extends Reducer<T>>({ pkg, }: { pkg: Package<T, Reducer<T>>; }): UsePackageReturn<T, R> { const isMountedRef = useRef(false); // Xử lý mounted n lần khi gọi usePackage const packageRef = useRef(pkg); const [state$, setState$] = useState<T>(pkg.state); const doAction = (reducer: Action<T>, payload?: any) => { reducer(packageRef.current, payload); setState$(packageRef.current.state); }; const dispatch = (name: keyof R, payload?: any) => { const reducers = packageRef.current.reducers as R; const reducer = reducers[name]; if (reducer) { doAction(reducer, payload); return; } else { throw new Error(`[Package] <dispatch: ${String(name)} > not found`); } }; const subcribe = (callback: SubcribeCallback<T>) => { return packageRef.current.subcribe(callback); }; const unsubcribe = (listenerId: number) => { packageRef.current.unsubcribe(listenerId); }; useEffect(() => { if (isMountedRef.current) { Object.entries(packageRef.current.listeners).forEach(([, callback]) => { callback(state$); }); } else { isMountedRef.current = true; } }, [state$]); return { state: state$, dispatch: dispatch, subcribe: subcribe, unsubcribe: unsubcribe, }; } /** * Cho phép đăng ký một subcribe cho package */ export function usePkgSubcribe<T extends PackageType>({ pkg, action, }: { pkg: Package<T, Reducer<T>>; action: SubcribeCallback<T>; }) { const { subcribe, unsubcribe } = usePackage({ pkg: pkg }); useEffect(() => { const id = subcribe(action); return () => { unsubcribe(id); }; }, []); }