@lobehub/chat 0.70.4 → 0.71.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/package.json +8 -5
- package/src/layout/AppMobileLayout.tsx +0 -5
- package/src/pages/chat/index.page.tsx +8 -0
- package/src/store/global/hooks/useHydrated.ts +3 -5
- package/src/store/global/hooks/useOnFinishHydrationGlobal.ts +3 -5
- package/src/store/middleware/createHyperStorage/index.ts +56 -42
- package/src/store/middleware/createHyperStorage/indexedDB.ts +26 -0
- package/src/store/middleware/createHyperStorage/localStorage.ts +17 -0
- package/src/store/middleware/createHyperStorage/type.ts +5 -0
- package/src/store/middleware/createHyperStorage/urlStorage.ts +60 -0
- package/src/store/session/hooks/index.ts +1 -0
- package/src/store/session/hooks/useEffectAfterHydrated.ts +22 -0
- package/src/store/session/hooks/useOnFinishHydrationSession.ts +5 -10
- package/src/store/session/hooks/useSessionHydrated.ts +6 -8
- package/src/store/session/store.ts +2 -0
- package/src/store/middleware/createHyperStorage/urlSearch.ts +0 -17
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,56 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
### [Version 0.71.1](https://github.com/lobehub/lobe-chat/compare/v0.71.0...v0.71.1)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2023-09-09**</sup>
|
|
8
|
+
|
|
9
|
+
#### 🐛 Bug Fixes
|
|
10
|
+
|
|
11
|
+
- **misc**: Fix mobile route.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### What's fixed
|
|
19
|
+
|
|
20
|
+
- **misc**: Fix mobile route, closes [#165](https://github.com/lobehub/lobe-chat/issues/165) ([d5e03b6](https://github.com/lobehub/lobe-chat/commit/d5e03b6))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
## [Version 0.71.0](https://github.com/lobehub/lobe-chat/compare/v0.70.4...v0.71.0)
|
|
31
|
+
|
|
32
|
+
<sup>Released on **2023-09-09**</sup>
|
|
33
|
+
|
|
34
|
+
#### ✨ Features
|
|
35
|
+
|
|
36
|
+
- **misc**: Migrate localStorage to indexedDB.
|
|
37
|
+
|
|
38
|
+
<br/>
|
|
39
|
+
|
|
40
|
+
<details>
|
|
41
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
42
|
+
|
|
43
|
+
#### What's improved
|
|
44
|
+
|
|
45
|
+
- **misc**: Migrate localStorage to indexedDB, closes [#160](https://github.com/lobehub/lobe-chat/issues/160) ([7f96deb](https://github.com/lobehub/lobe-chat/commit/7f96deb))
|
|
46
|
+
|
|
47
|
+
</details>
|
|
48
|
+
|
|
49
|
+
<div align="right">
|
|
50
|
+
|
|
51
|
+
[](#readme-top)
|
|
52
|
+
|
|
53
|
+
</div>
|
|
54
|
+
|
|
5
55
|
### [Version 0.70.4](https://github.com/lobehub/lobe-chat/compare/v0.70.3...v0.70.4)
|
|
6
56
|
|
|
7
57
|
<sup>Released on **2023-09-09**</sup>
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/chat",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Lobe Chat is an open-source chatbot client using
|
|
3
|
+
"version": "0.71.1",
|
|
4
|
+
"description": "Lobe Chat is an open-source chatbot client using Vercel AI SDK, Typescript and Next.js",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chatbot",
|
|
7
7
|
"ChatGPT",
|
|
8
|
-
"
|
|
8
|
+
"LobeHub",
|
|
9
9
|
"typescript",
|
|
10
|
-
"
|
|
10
|
+
"Vercel AI SDK",
|
|
11
|
+
"Next.js",
|
|
12
|
+
"OpenAI"
|
|
11
13
|
],
|
|
12
14
|
"homepage": "https://github.com/lobehub/lobe-chat",
|
|
13
15
|
"bugs": {
|
|
@@ -80,12 +82,13 @@
|
|
|
80
82
|
"fast-deep-equal": "^3",
|
|
81
83
|
"gpt-tokenizer": "^2",
|
|
82
84
|
"i18next": "^23",
|
|
85
|
+
"idb-keyval": "^6",
|
|
83
86
|
"immer": "^10",
|
|
84
87
|
"lodash-es": "^4",
|
|
85
88
|
"lucide-react": "latest",
|
|
86
89
|
"nanoid": "^4",
|
|
87
90
|
"next": "13.4.7",
|
|
88
|
-
"openai": "^4.
|
|
91
|
+
"openai": "^4.5.0",
|
|
89
92
|
"polished": "^4",
|
|
90
93
|
"react": "^18",
|
|
91
94
|
"react-dom": "^18",
|
|
@@ -6,7 +6,6 @@ import { Flexbox } from 'react-layout-kit';
|
|
|
6
6
|
|
|
7
7
|
import SafeSpacing from '@/components/SafeSpacing';
|
|
8
8
|
import MobileTabBar from '@/features/MobileTabBar';
|
|
9
|
-
import { useOnFinishHydrationSession, useSessionStore } from '@/store/session';
|
|
10
9
|
|
|
11
10
|
const useStyles = createStyles(({ css, cx, stylish }) => ({
|
|
12
11
|
container: cx(
|
|
@@ -49,10 +48,6 @@ const AppMobileLayout = memo<AppMobileLayoutProps>(
|
|
|
49
48
|
({ children, showTabBar, navBar, style, className }) => {
|
|
50
49
|
const { styles, cx } = useStyles();
|
|
51
50
|
|
|
52
|
-
useOnFinishHydrationSession(() => {
|
|
53
|
-
useSessionStore.setState({ isMobile: true });
|
|
54
|
-
}, []);
|
|
55
|
-
|
|
56
51
|
return (
|
|
57
52
|
<>
|
|
58
53
|
<Head>
|
|
@@ -4,6 +4,7 @@ import { memo } from 'react';
|
|
|
4
4
|
import { Flexbox } from 'react-layout-kit';
|
|
5
5
|
|
|
6
6
|
import { useSessionStore } from '@/store/session';
|
|
7
|
+
import { useEffectAfterSessionHydrated } from '@/store/session/hooks';
|
|
7
8
|
import { agentSelectors } from '@/store/session/selectors';
|
|
8
9
|
import { genSiteHeadTitle } from '@/utils/genSiteHeadTitle';
|
|
9
10
|
|
|
@@ -22,6 +23,13 @@ const Chat = memo(() => {
|
|
|
22
23
|
const pageTitle = genSiteHeadTitle([avatar, title].filter(Boolean).join(' '));
|
|
23
24
|
const RenderLayout = mobile ? MobileLayout : DesktopLayout;
|
|
24
25
|
|
|
26
|
+
useEffectAfterSessionHydrated(
|
|
27
|
+
(store) => {
|
|
28
|
+
store.setState({ isMobile: mobile });
|
|
29
|
+
},
|
|
30
|
+
[mobile],
|
|
31
|
+
);
|
|
32
|
+
|
|
25
33
|
return (
|
|
26
34
|
<>
|
|
27
35
|
<Head>
|
|
@@ -9,11 +9,9 @@ export const useGlobalHydrated = () => {
|
|
|
9
9
|
const [isInit, setInit] = useState(hasInited);
|
|
10
10
|
|
|
11
11
|
useEffect(() => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
setInit(true);
|
|
16
|
-
}
|
|
12
|
+
useGlobalStore.persist.onFinishHydration(() => {
|
|
13
|
+
if (!isInit) setInit(true);
|
|
14
|
+
});
|
|
17
15
|
}, []);
|
|
18
16
|
|
|
19
17
|
return isInit;
|
|
@@ -9,10 +9,8 @@ import { GlobalStore, useGlobalStore } from '../store';
|
|
|
9
9
|
*/
|
|
10
10
|
export const useOnFinishHydrationGlobal = (fn: (state: GlobalStore) => void, deps: any[] = []) => {
|
|
11
11
|
useEffect(() => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
fn(useGlobalStore.getState());
|
|
12
|
+
useGlobalStore.persist.onFinishHydration(() => {
|
|
13
|
+
fn(useGlobalStore.getState());
|
|
14
|
+
});
|
|
17
15
|
}, deps);
|
|
18
16
|
};
|
|
@@ -1,28 +1,40 @@
|
|
|
1
|
-
import { isEmpty } from 'lodash-es';
|
|
2
1
|
import { PersistStorage } from 'zustand/middleware';
|
|
3
2
|
import { StorageValue } from 'zustand/middleware/persist';
|
|
4
3
|
|
|
4
|
+
import { createIndexedDB } from './indexedDB';
|
|
5
5
|
import { createKeyMapper } from './keyMapper';
|
|
6
|
+
import { createLocalStorage } from './localStorage';
|
|
6
7
|
import { HyperStorageOptions } from './type';
|
|
7
|
-
import {
|
|
8
|
+
import { creatUrlStorage } from './urlStorage';
|
|
8
9
|
|
|
9
10
|
export const createHyperStorage = <T extends object>(
|
|
10
11
|
options: HyperStorageOptions,
|
|
11
12
|
): PersistStorage<T> => {
|
|
12
13
|
const hasUrl = !!options.url;
|
|
13
|
-
|
|
14
|
-
const
|
|
14
|
+
const useIndexedDB = options.localStorage?.mode === 'indexedDB';
|
|
15
|
+
const dbName = options.localStorage?.dbName || 'indexedDB';
|
|
15
16
|
|
|
16
17
|
const { mapStateKeyToStorageKey, getStateKeyFromStorageKey } = createKeyMapper(options);
|
|
17
18
|
|
|
19
|
+
const indexedDB = createIndexedDB(dbName);
|
|
20
|
+
const localStorage = createLocalStorage();
|
|
21
|
+
const urlStorage = creatUrlStorage(options.url?.mode);
|
|
18
22
|
return {
|
|
19
|
-
getItem: (name): StorageValue<T
|
|
23
|
+
getItem: async (name): Promise<StorageValue<T>> => {
|
|
20
24
|
const state: any = {};
|
|
21
25
|
let version: number | undefined;
|
|
26
|
+
|
|
22
27
|
// ============== 处理 Local Storage ============== //
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
let localState: StorageValue<T> | undefined;
|
|
29
|
+
|
|
30
|
+
// 如果使用 indexedDB,优先从 indexedDB 中获取
|
|
31
|
+
if (useIndexedDB) {
|
|
32
|
+
localState = await indexedDB.getItem(name);
|
|
33
|
+
}
|
|
34
|
+
// 如果 indexedDB 中不存在,则再试试 localStorage
|
|
35
|
+
if (!localState) localState = localStorage.getItem(name);
|
|
36
|
+
|
|
37
|
+
if (localState) {
|
|
26
38
|
version = localState.version;
|
|
27
39
|
for (const [k, v] of Object.entries(localState.state)) {
|
|
28
40
|
const key = getStateKeyFromStorageKey(k, 'localStorage');
|
|
@@ -33,31 +45,39 @@ export const createHyperStorage = <T extends object>(
|
|
|
33
45
|
// ============== 处理 URL Storage ============== //
|
|
34
46
|
// 不从 URL 中获取 version,由于 url 状态是临时态 不作为版本控制的依据
|
|
35
47
|
if (hasUrl) {
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
const urlState = urlStorage.getItem();
|
|
49
|
+
|
|
50
|
+
if (urlState) {
|
|
51
|
+
for (const [k, v] of Object.entries(urlState.state)) {
|
|
52
|
+
const key = getStateKeyFromStorageKey(k, 'url');
|
|
53
|
+
// 当存在 UrlSelector 逻辑,且 key 有效时,才将状态加入终态 state
|
|
54
|
+
if (hasUrl && key) {
|
|
55
|
+
state[key] = v;
|
|
56
|
+
}
|
|
43
57
|
}
|
|
44
58
|
}
|
|
45
59
|
}
|
|
46
60
|
|
|
47
61
|
return { state, version };
|
|
48
62
|
},
|
|
49
|
-
removeItem: (key)
|
|
63
|
+
removeItem: async (key) => {
|
|
64
|
+
// ============== 处理 Local Storage ============== //
|
|
65
|
+
|
|
66
|
+
if (useIndexedDB) {
|
|
67
|
+
await indexedDB.removeItem(key);
|
|
68
|
+
} else {
|
|
69
|
+
localStorage.removeItem(key);
|
|
70
|
+
}
|
|
71
|
+
|
|
50
72
|
// ============== 处理 URL Storage ============== //
|
|
51
73
|
if (hasUrl) {
|
|
52
|
-
const searchParameters = new URLSearchParams(getUrlSearch());
|
|
53
|
-
|
|
54
74
|
const storageKey = getStateKeyFromStorageKey(key, 'url');
|
|
55
|
-
if (storageKey) searchParameters.delete(storageKey);
|
|
56
75
|
|
|
57
|
-
|
|
76
|
+
urlStorage.removeItem(storageKey);
|
|
58
77
|
}
|
|
59
78
|
},
|
|
60
|
-
|
|
79
|
+
|
|
80
|
+
setItem: async (name, newValue) => {
|
|
61
81
|
// ============== 处理 Local Storage ============== //
|
|
62
82
|
const localState: Record<string, any> = {};
|
|
63
83
|
for (const [k, v] of Object.entries(newValue.state)) {
|
|
@@ -65,30 +85,24 @@ export const createHyperStorage = <T extends object>(
|
|
|
65
85
|
if (localKey) localState[localKey] = v;
|
|
66
86
|
}
|
|
67
87
|
|
|
68
|
-
|
|
88
|
+
if (useIndexedDB) {
|
|
89
|
+
await indexedDB.setItem(name, localState, newValue.version);
|
|
90
|
+
} else {
|
|
91
|
+
localStorage.setItem(name, localState, newValue.version);
|
|
92
|
+
}
|
|
69
93
|
|
|
70
94
|
// ============== 处理 URL Storage ============== //
|
|
71
95
|
if (hasUrl) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (typeof v === 'string') {
|
|
85
|
-
searchParameters.set(urlKey, v);
|
|
86
|
-
} else {
|
|
87
|
-
searchParameters.set(urlKey, JSON.stringify(v));
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
setUrlSearch(searchParameters);
|
|
96
|
+
// 转换 key 为目标 key
|
|
97
|
+
const finalEntries = Object.entries(newValue.state)
|
|
98
|
+
.map(([k, v]) => {
|
|
99
|
+
const urlKey = mapStateKeyToStorageKey(k, 'url');
|
|
100
|
+
if (!urlKey) return undefined;
|
|
101
|
+
return [urlKey, v];
|
|
102
|
+
})
|
|
103
|
+
.filter(Boolean) as [string, any][];
|
|
104
|
+
|
|
105
|
+
urlStorage.setItem(name, Object.fromEntries(finalEntries));
|
|
92
106
|
}
|
|
93
107
|
},
|
|
94
108
|
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createStore, delMany, getMany, setMany } from 'idb-keyval';
|
|
2
|
+
import { StorageValue } from 'zustand/middleware/persist';
|
|
3
|
+
|
|
4
|
+
export const createIndexedDB = <State extends any>(dbName: string = 'indexedDB') => ({
|
|
5
|
+
getItem: async <T extends State>(name: string): Promise<StorageValue<T> | undefined> => {
|
|
6
|
+
const [version, state] = await getMany(['version', 'state'], createStore(dbName, name));
|
|
7
|
+
|
|
8
|
+
if (!state) return undefined;
|
|
9
|
+
|
|
10
|
+
return { state, version };
|
|
11
|
+
},
|
|
12
|
+
removeItem: async (name: string) => {
|
|
13
|
+
await delMany(['version', 'state'], createStore(dbName, name));
|
|
14
|
+
},
|
|
15
|
+
setItem: async (name: string, state: any, version: number | undefined) => {
|
|
16
|
+
const store = createStore(dbName, name);
|
|
17
|
+
|
|
18
|
+
await setMany(
|
|
19
|
+
[
|
|
20
|
+
['version', version],
|
|
21
|
+
['state', state],
|
|
22
|
+
],
|
|
23
|
+
store,
|
|
24
|
+
);
|
|
25
|
+
},
|
|
26
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { StorageValue } from 'zustand/middleware/persist';
|
|
2
|
+
|
|
3
|
+
export const createLocalStorage = <State extends any>() => ({
|
|
4
|
+
getItem: <T extends State>(name: string): StorageValue<T> | undefined => {
|
|
5
|
+
const string = localStorage.getItem(name);
|
|
6
|
+
|
|
7
|
+
if (string) return JSON.parse(string) as StorageValue<T>;
|
|
8
|
+
|
|
9
|
+
return undefined;
|
|
10
|
+
},
|
|
11
|
+
removeItem: (name: string) => {
|
|
12
|
+
localStorage.removeItem(name);
|
|
13
|
+
},
|
|
14
|
+
setItem: <T extends State>(name: string, state: T, version: number | undefined) => {
|
|
15
|
+
localStorage.setItem(name, JSON.stringify({ state, version }));
|
|
16
|
+
},
|
|
17
|
+
});
|
|
@@ -2,6 +2,11 @@ export type StorageSelector = string | Record<string, string>;
|
|
|
2
2
|
|
|
3
3
|
export interface HyperStorageOptions {
|
|
4
4
|
localStorage?: {
|
|
5
|
+
dbName?: string;
|
|
6
|
+
/**
|
|
7
|
+
* @default 'localStorage'
|
|
8
|
+
*/
|
|
9
|
+
mode?: 'indexedDB' | 'localStorage';
|
|
5
10
|
selectors: StorageSelector[];
|
|
6
11
|
};
|
|
7
12
|
url?: {
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { isEmpty } from 'lodash-es';
|
|
2
|
+
import { StorageValue } from 'zustand/middleware/persist';
|
|
3
|
+
|
|
4
|
+
interface UrlSearchHelper {
|
|
5
|
+
getUrlSearch: () => string;
|
|
6
|
+
setUrlSearch: (params: URLSearchParams) => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const createUrlSearch = (mode: 'search' | 'hash' = 'hash'): UrlSearchHelper => {
|
|
10
|
+
if (mode === 'hash')
|
|
11
|
+
return {
|
|
12
|
+
getUrlSearch: () => location.hash.slice(1),
|
|
13
|
+
setUrlSearch: (params: URLSearchParams) => (location.hash = params.toString()),
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
getUrlSearch: () => location.search.slice(1),
|
|
18
|
+
setUrlSearch: (params: URLSearchParams) => (location.search = params.toString()),
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const creatUrlStorage = <State extends object>(mode: 'hash' | 'search' = 'hash') => {
|
|
23
|
+
const { setUrlSearch, getUrlSearch } = createUrlSearch(mode);
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
getItem: <T extends State>(): StorageValue<T> | undefined => {
|
|
27
|
+
const searchParameters = new URLSearchParams(getUrlSearch());
|
|
28
|
+
|
|
29
|
+
if (searchParameters.size === 0) return undefined;
|
|
30
|
+
|
|
31
|
+
const state = Object.fromEntries(searchParameters.entries()) as T;
|
|
32
|
+
|
|
33
|
+
return { state };
|
|
34
|
+
},
|
|
35
|
+
removeItem: (key?: string) => {
|
|
36
|
+
const searchParameters = new URLSearchParams(getUrlSearch());
|
|
37
|
+
if (key) searchParameters.delete(key);
|
|
38
|
+
|
|
39
|
+
setUrlSearch(searchParameters);
|
|
40
|
+
},
|
|
41
|
+
setItem: <T extends State>(name: string, state: T) => {
|
|
42
|
+
const searchParameters = new URLSearchParams(getUrlSearch());
|
|
43
|
+
|
|
44
|
+
for (const [urlKey, v] of Object.entries(state)) {
|
|
45
|
+
if (isEmpty(v)) {
|
|
46
|
+
searchParameters.delete(urlKey);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (typeof v === 'string') {
|
|
51
|
+
searchParameters.set(urlKey, v);
|
|
52
|
+
} else {
|
|
53
|
+
searchParameters.set(urlKey, JSON.stringify(v));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
setUrlSearch(searchParameters);
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
import { useSessionStore } from '../store';
|
|
4
|
+
|
|
5
|
+
export const useEffectAfterSessionHydrated = (
|
|
6
|
+
fn: (session: typeof useSessionStore) => void,
|
|
7
|
+
deps: any[] = [],
|
|
8
|
+
) => {
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const hasRehydrated = useSessionStore.persist.hasHydrated();
|
|
11
|
+
|
|
12
|
+
if (hasRehydrated) {
|
|
13
|
+
// 等价 useEffect 多次触发
|
|
14
|
+
fn(useSessionStore);
|
|
15
|
+
} else {
|
|
16
|
+
// 等价于 useEffect 第一次触发
|
|
17
|
+
useSessionStore.persist.onFinishHydration(() => {
|
|
18
|
+
fn(useSessionStore);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}, deps);
|
|
22
|
+
};
|
|
@@ -5,17 +5,12 @@ import { SessionStore, useSessionStore } from '../store';
|
|
|
5
5
|
/**
|
|
6
6
|
* 当 Session 水合完毕后才会执行的 useEffect
|
|
7
7
|
* @param fn
|
|
8
|
-
* @param deps
|
|
9
8
|
*/
|
|
10
|
-
export const useOnFinishHydrationSession = (
|
|
11
|
-
fn: (state: SessionStore) => void,
|
|
12
|
-
deps: any[] = [],
|
|
13
|
-
) => {
|
|
9
|
+
export const useOnFinishHydrationSession = (fn: (state: SessionStore) => void) => {
|
|
14
10
|
useEffect(() => {
|
|
15
|
-
const hasRehydrated = useSessionStore.persist.hasHydrated();
|
|
16
11
|
// 只有当水合完毕后再开始做操作
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
},
|
|
12
|
+
useSessionStore.persist.onFinishHydration(() => {
|
|
13
|
+
fn(useSessionStore.getState());
|
|
14
|
+
});
|
|
15
|
+
}, []);
|
|
21
16
|
};
|
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useState } from 'react';
|
|
2
2
|
|
|
3
3
|
import { useSessionStore } from '../store';
|
|
4
|
+
import { useOnFinishHydrationSession } from './useOnFinishHydrationSession';
|
|
4
5
|
|
|
5
6
|
export const useSessionHydrated = () => {
|
|
6
7
|
// 根据 sessions 是否有值来判断是否已经初始化
|
|
7
8
|
const hasInited = !!Object.values(useSessionStore.getState().sessions).length;
|
|
8
|
-
const [isInit, setInit] = useState(hasInited);
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
const hasRehydrated = useSessionStore.persist.hasHydrated();
|
|
10
|
+
const [isInit, setInit] = useState(hasInited);
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}, []);
|
|
12
|
+
useOnFinishHydrationSession(() => {
|
|
13
|
+
if (!isInit) setInit(true);
|
|
14
|
+
});
|
|
17
15
|
|
|
18
16
|
return isInit;
|
|
19
17
|
};
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
interface UrlSearchHelper {
|
|
2
|
-
getUrlSearch: () => string;
|
|
3
|
-
setUrlSearch: (params: URLSearchParams) => void;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export const createUrlSearch = (mode: 'search' | 'hash' = 'hash'): UrlSearchHelper => {
|
|
7
|
-
if (mode === 'hash')
|
|
8
|
-
return {
|
|
9
|
-
getUrlSearch: () => location.hash.slice(1),
|
|
10
|
-
setUrlSearch: (params: URLSearchParams) => (location.hash = params.toString()),
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
return {
|
|
14
|
-
getUrlSearch: () => location.search.slice(1),
|
|
15
|
-
setUrlSearch: (params: URLSearchParams) => (location.search = params.toString()),
|
|
16
|
-
};
|
|
17
|
-
};
|