import _get from 'lodash.get';
import _set from 'lodash.set';
import _debounce from 'lodash.debounce';
import {shallowEqual} from "./shallow-equal";
const IS_STORAGE_INITIALIZED = '$$IS_STORAGE_INITIALIZED$$';
interface PersistConfig {
save: (storageName: string, storageData: any) => void;
load: (storageName: string) => Promise<any>;
}
type WatcherFn = (nextValue: any, preValue: any, storage: Storage) => void;
interface Watcher {
path: string,
watcher: WatcherFn,
preValue: any
}
class Storage {
private readonly storageName: string;
private storageData: Record<string, any>;
private watcherList: Watcher[];
private persistConfig: PersistConfig | null;
private readonly persistStorageFn: any;
constructor(storageName: string) {
this.storageName = storageName;
this.storageData = {};
this.watcherList = []; // {path, watcher, preValue}
this.persistConfig = null; // {save:(storageName,storageData)=>{}, load:(storageName)=>{}}
this.persistStorageFn = _debounce(() => {
this.persistStorage();
}, 100);
setTimeout(() => {
this.persistStorageInit();
}, 1);
}
setPersistConfig(persistConfig: PersistConfig) {
this.persistConfig = persistConfig;
}
initStorageData(storageData: Record<string, any>) {
this.storageData = storageData;
this.storageData[IS_STORAGE_INITIALIZED] = true;
this.notifyWatcher();
}
getValue(path: string) {
return _get(this.storageData, path);
}
setValue(path: string, value: any) {
_set(this.storageData, path, value);
this.notifyWatcher();
this.persistStorageFn()
}
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;
}
}
}
persistStorage() {
const persistConfig = this.persistConfig;
if (!persistConfig) {
return;
}
const {save} = persistConfig;
save(this.storageName, this.storageData);
}
persistStorageInit() {
const persistConfig = this.persistConfig;
if (!persistConfig) {
this.initStorageData({});
return;
}
const {load} = persistConfig;
load(this.storageName).then((storageData) => {
if (storageData && typeof storageData === "object") {
this.initStorageData(storageData);
} else {
this.initStorageData({});
}
}, () => {
this.initStorageData({});
});
}
}
class StorageManager {
private storageMap: Record<string, Storage> = {};
getStorage(storageName: string) {
if (!this.storageMap[storageName]) {
this.storageMap[storageName] = new Storage(storageName);
}
return this.storageMap[storageName];
}
}
const storageManager = new StorageManager();
export {
storageManager,
IS_STORAGE_INITIALIZED
}
import localforage from '@ali/ascp-shared-local-forage'
/**
* 使用 localStorage 存储
*/
const localStoragePersist = {
save: (storageName, storageData) => {
localStorage.setItem(storageName, JSON.stringify(storageData));
},
load: (storageName) => {
return new Promise((resolve) => {
const str = localStorage.getItem(storageName);
if (str) {
resolve(JSON.parse(str))
} else {
resolve(null);
}
})
}
}
/**
* 使用 localforage 存储
*/
const localForagePersist = {
save: (storageName, storageData) => {
localforage.setItem(storageName, storageData);
},
load: (storageName) => {
return localforage.getItem(storageName);
}
};
export {
localStoragePersist,
localForagePersist
}
import {useEffect, useState} from 'react';
import {storageManager} from "../utils/storage";
function useStorageValue(storageName, persistConfig, path) {
const storage = storageManager.getStorage(storageName);
storage.setPersistConfig(persistConfig)
const [value, setValue] = useState(() => {
return storage.getValue(path);
});
useEffect(() => {
const watcher = (nowValue) => {
setValue(nowValue);
};
storage.watch(path, watcher);
return () => {
storage.unwatch(path, watcher);
}
}, [storageName, path]);
return value;
}
export {
useStorageValue
}