Skip to content

Commit 19186eb

Browse files
committed
refactor(hooks): refactor storage with useSyncExternalStore
1 parent 9161231 commit 19186eb

File tree

2 files changed

+64
-42
lines changed

2 files changed

+64
-42
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import type { AbstractStorage } from './storage';
2+
3+
import { useMemo } from 'react';
4+
5+
class Store {
6+
private _listeners: (() => void)[] = [];
7+
private _key: any;
8+
private _storage: AbstractStorage<any, any>;
9+
10+
constructor(key: any, storage: AbstractStorage<any, any>) {
11+
this._key = key;
12+
this._storage = storage;
13+
}
14+
15+
subscribe(onStoreChange: () => void) {
16+
this._listeners = this._listeners.concat([onStoreChange]);
17+
return () => {
18+
this._listeners = this._listeners.filter((f) => f !== onStoreChange);
19+
};
20+
}
21+
22+
getSnapshot() {
23+
return this._storage.getItem(this._key);
24+
}
25+
26+
emitChange() {
27+
for (const listener of this._listeners) {
28+
listener();
29+
}
30+
}
31+
}
32+
33+
export const stores = new Map<any, Store>();
34+
35+
export function useStore(key: any, storage: AbstractStorage<any, any>) {
36+
return useMemo(() => {
37+
let store = stores.get(key);
38+
if (!store) {
39+
store = new Store(key, storage);
40+
stores.set(key, store);
41+
}
42+
return {
43+
subscribe: store.subscribe.bind(store),
44+
getSnapshot: store.getSnapshot.bind(store),
45+
emitChange: store.emitChange.bind(store),
46+
};
47+
}, [key, storage]);
48+
}

packages/hooks/src/storage/useStorage.ts

Lines changed: 16 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@ import type { AbstractParserOptions } from './parser';
22
import type { AbstractStorage } from './storage';
33

44
import { isNull, isUndefined } from 'lodash';
5-
import { useEffect, useMemo, useState } from 'react';
5+
import { useMemo, useSyncExternalStore } from 'react';
66

7-
import { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect';
87
import { LocalStorageService } from './localStorage';
98
import { STRING_PARSER } from './parser';
10-
11-
const updates = new Map<any, Set<(...args: any[]) => any>>();
9+
import { useStore, stores } from './store';
1210

1311
interface UseStorageMethod<V> {
1412
set: (value: V) => void;
@@ -34,61 +32,37 @@ export function useStorage<V, K = string>(
3432

3533
const { serializer, deserializer } = isUndefined(parser) ? PARSER.plain : PARSER[parser];
3634

37-
const res = useMemo<{ readonly value: any } & UseStorageMethod<V>>(() => {
38-
const updateKey = (ov: any) => {
39-
const cbs = updates.get(key);
40-
if (cbs) {
41-
for (const cb of cbs) {
42-
cb(ov);
43-
}
44-
}
45-
};
46-
return {
35+
const store = useStore(key, SERVICE);
36+
const value = useSyncExternalStore(store.subscribe, store.getSnapshot);
37+
38+
return useMemo<{ readonly value: any } & UseStorageMethod<V>>(
39+
() => ({
4740
get value() {
48-
const originValue = SERVICE.getItem(key);
49-
if (isNull(originValue)) {
41+
if (isNull(value)) {
5042
return defaultValue ?? null;
5143
}
5244

53-
return deserializer(originValue);
45+
return deserializer(value);
5446
},
5547
set: (value) => {
5648
const originValue = serializer(value);
5749
SERVICE.setItem(key, originValue);
58-
updateKey(originValue);
50+
store.emitChange();
5951
},
6052
remove: () => {
6153
SERVICE.removeItem(key);
62-
updateKey(null);
54+
store.emitChange();
6355
},
64-
};
65-
}, [SERVICE, defaultValue, deserializer, key, serializer]);
66-
const [, setOriginValue] = useState(SERVICE.getItem(key));
67-
68-
// eslint-disable-next-line react-hooks/exhaustive-deps
69-
useIsomorphicLayoutEffect(() => {
70-
const updatesOfKey = updates.get(key);
71-
if (isUndefined(updatesOfKey)) {
72-
updates.set(key, new Set([setOriginValue]));
73-
} else if (!updatesOfKey.has(setOriginValue)) {
74-
updatesOfKey.add(setOriginValue);
75-
}
76-
});
77-
78-
useEffect(() => {
79-
updates.get(key)?.delete(setOriginValue);
80-
}, [key]);
81-
82-
return res;
56+
}),
57+
[SERVICE, defaultValue, deserializer, key, serializer, store, value]
58+
);
8359
}
8460

8561
useStorage.SERVICE = new LocalStorageService() as AbstractStorage<any, any>;
8662
useStorage.PARSER = STRING_PARSER as AbstractParserOptions<any>;
8763
useStorage.clear = () => {
8864
useStorage.SERVICE.clear();
89-
for (const [, cbs] of updates) {
90-
for (const cb of cbs) {
91-
cb(null);
92-
}
65+
for (const [, store] of stores) {
66+
store.emitChange();
9367
}
9468
};

0 commit comments

Comments
 (0)