mirror of
https://github.com/ant-design/ant-design.git
synced 2026-02-09 02:49:18 +08:00
* docs: fix unexpected storage logic * Update .dumi/hooks/useLocalStorage.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: 遇见同学 <1875694521@qq.com> --------- Signed-off-by: 遇见同学 <1875694521@qq.com> Co-authored-by: 遇见同学 <1875694521@qq.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
114 lines
3.1 KiB
TypeScript
114 lines
3.1 KiB
TypeScript
import React, { useCallback, useEffect } from 'react';
|
|
|
|
const ANT_SYNC_STORAGE_EVENT_KEY = 'ANT_SYNC_STORAGE_EVENT_KEY';
|
|
|
|
const isFunction = (val: any): val is (...args: any[]) => any => {
|
|
return typeof val === 'function';
|
|
};
|
|
|
|
interface Options<T> {
|
|
defaultValue?: T;
|
|
serializer?: (value: T) => string;
|
|
deserializer?: (value: string) => T;
|
|
onError?: (error: unknown) => void;
|
|
}
|
|
|
|
const useLocalStorage = <T>(key: string, options: Options<T> = {}) => {
|
|
const storage = typeof window !== 'undefined' ? localStorage : null;
|
|
|
|
const { serializer, deserializer, onError, defaultValue } = options;
|
|
|
|
const mergedSerializer = typeof serializer === 'function' ? serializer : JSON.stringify;
|
|
|
|
const mergedDeserializer = typeof deserializer === 'function' ? deserializer : JSON.parse;
|
|
|
|
const handleError = typeof onError === 'function' ? onError : console.error;
|
|
|
|
const getStoredValue = () => {
|
|
try {
|
|
const rawData = storage?.getItem(key);
|
|
if (rawData) {
|
|
return mergedDeserializer(rawData);
|
|
}
|
|
} catch (e) {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
handleError(e);
|
|
}
|
|
return defaultValue;
|
|
}
|
|
return defaultValue;
|
|
};
|
|
|
|
const [state, setState] = React.useState<T>(getStoredValue);
|
|
|
|
useEffect(() => {
|
|
setState(getStoredValue());
|
|
}, [key]);
|
|
|
|
const updateState: React.Dispatch<React.SetStateAction<T>> = (value) => {
|
|
const currentState = isFunction(value) ? value(state) : value;
|
|
setState(currentState);
|
|
try {
|
|
let newValue: string | null;
|
|
const oldValue = storage?.getItem(key);
|
|
if (typeof currentState === 'undefined') {
|
|
newValue = null;
|
|
storage?.removeItem(key);
|
|
} else {
|
|
newValue = mergedSerializer(currentState);
|
|
storage?.setItem(key, newValue);
|
|
}
|
|
dispatchEvent(
|
|
new CustomEvent(ANT_SYNC_STORAGE_EVENT_KEY, {
|
|
detail: { key, newValue, oldValue, storageArea: storage },
|
|
}),
|
|
);
|
|
} catch (e) {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
handleError(e);
|
|
}
|
|
}
|
|
};
|
|
|
|
const shouldSync = (ev: StorageEvent) => {
|
|
return ev && ev.key === key && ev.storageArea === storage;
|
|
};
|
|
|
|
const onNativeStorage = useCallback(
|
|
(event: StorageEvent) => {
|
|
if (shouldSync(event)) {
|
|
setState(getStoredValue());
|
|
}
|
|
},
|
|
[key],
|
|
);
|
|
|
|
const shouldSyncCustomEvent = (ev: CustomEvent<{ key: string; storageArea: Storage }>) => {
|
|
return ev?.detail?.key === key && ev?.detail?.storageArea === storage;
|
|
};
|
|
|
|
const onCustomStorage = useCallback(
|
|
(event: Event) => {
|
|
const customEvent = event as CustomEvent;
|
|
|
|
if (shouldSyncCustomEvent(customEvent)) {
|
|
setState(getStoredValue());
|
|
}
|
|
},
|
|
[key],
|
|
);
|
|
|
|
useEffect(() => {
|
|
window?.addEventListener('storage', onNativeStorage);
|
|
window?.addEventListener(ANT_SYNC_STORAGE_EVENT_KEY, onCustomStorage);
|
|
return () => {
|
|
window?.removeEventListener('storage', onNativeStorage);
|
|
window?.removeEventListener(ANT_SYNC_STORAGE_EVENT_KEY, onCustomStorage);
|
|
};
|
|
}, [key, onNativeStorage, onCustomStorage]);
|
|
|
|
return [state, updateState] as const;
|
|
};
|
|
|
|
export default useLocalStorage;
|