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
}