react useCreateWatchedStore 轻量级高性能状态管理库
import {useContext, useEffect, useMemo, useRef, useState} from 'react'; import _get from "lodash.get"; import _set from "lodash.set"; import {shallowEqual} from "../utils/shallowEqual"; export const KEY_SAVE_TICK_COUNT = 'KEY_SAVE_TICK_COUNT'; export type WatcherFn = (nextValue: any, preValue: any, storage: WatchedStore) => void; export interface Watcher { path: string, watcher: WatcherFn, preValue: any } export class WatchedStore { private readonly storeData: Record<string, any>; private watcherList: Watcher[]; private tickCount = 0; constructor(initialValues:any = {}) { this.storeData = initialValues; this.watcherList = []; // {path, watcher, preValue} } getValue(path: string) { return _get(this.storeData, path); } setValue(path: string, value: any) { this.tickCount = this.tickCount + 1; _set(this.storeData, path, value); _set(this.storeData, KEY_SAVE_TICK_COUNT, this.tickCount); this.notifyWatcher(); } watch(path: string, watcher: WatcherFn) { if (typeof watcher !== "function") { throw 'watcher must function'; } this.watcherList.push({path, watcher, preValue: undefined}); } unwatch(path: string, watcher: WatcherFn) { this.watcherList = this.watcherList.filter((obj) => { return obj.path !== path && obj.watcher !== watcher; }); } notifyWatcher() { const watcherList = this.watcherList || []; for (let i = 0; i < watcherList.length; i++) { const {path, watcher, preValue} = watcherList[i]; const nextValue = this.getValue(path); if (!shallowEqual(nextValue, preValue)) { watcher(nextValue, preValue, this); watcherList[i].preValue = nextValue; } } } } function useCreateWatchedStore(initialValues: any = {}): WatchedStore { return useMemo(() => { return new WatchedStore(initialValues); }, []); } function useGetWatchedStore(context: any): WatchedStore { return useContext(context) as WatchedStore; } function useGetWatchedValue(context: any, path: string): any { const store = useGetWatchedStore(context); const valueRef = useRef<any>(); const [value, setValue] = useState(() => { const nowValue = store.getValue(path); valueRef.current = nowValue; return nowValue; }); useEffect(() => { const init = () => { const nowValue = store.getValue(path); if (!shallowEqual(valueRef.current, nowValue)) { valueRef.current = nowValue; setValue(nowValue); } } init(); const watcher = (nowValue: any) => { valueRef.current = nowValue; setValue(nowValue); }; store.watch(path, watcher); return () => { store.unwatch(path, watcher); } }, [store, path]); return value; } export { useCreateWatchedStore, useGetWatchedStore, useGetWatchedValue }
import React from 'react'; import {useCreateWatchedStore, useGetWatchedValue, useGetWatchedStore} from "../hooks/useWatchedStore"; const context = React.createContext({}); const WatchedStoreProvider = context.Provider; function SubComp1() { const count = useGetWatchedValue(context, 'count'); return ( <div> {count} </div> ); } function SubComp2() { const store = useGetWatchedStore(context); const count = useGetWatchedValue(context, 'count'); return ( <button onClick={() => { const getCount = () => { return store.getValue("count"); } store.setValue('count', getCount() + 1); store.setValue('count', getCount() + 1); store.setValue('count', getCount() + 1); store.setValue('count', getCount() + 1); }}> {count} </button> ); } function DemoUseWatchedStore() { const store = useCreateWatchedStore({ count: 0 }); return ( <WatchedStoreProvider value={store}> <SubComp1/> <SubComp1/> <SubComp1/> <SubComp1/> <SubComp2/> </WatchedStoreProvider> ); } export { DemoUseWatchedStore }