-
Notifications
You must be signed in to change notification settings - Fork 2.7k
/
Copy pathindex.ts
96 lines (79 loc) · 2.67 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import { useMemoizedFn, useUpdate } from 'ahooks';
import qs from 'query-string';
import type { ParseOptions, StringifyOptions } from 'query-string';
import { useMemo, useRef } from 'react';
import type * as React from 'react';
import * as tmp from 'react-router';
// ignore waring `"export 'useNavigate' (imported as 'rc') was not found in 'react-router'`
const rc = tmp as any;
export interface Options {
navigateMode?: 'push' | 'replace';
parseOptions?: ParseOptions;
stringifyOptions?: StringifyOptions;
}
const baseParseConfig: ParseOptions = {
parseNumbers: false,
parseBooleans: false,
};
const baseStringifyConfig: StringifyOptions = {
skipNull: false,
skipEmptyString: false,
};
type UrlState = Record<string, any>;
const useUrlState = <S extends UrlState = UrlState>(
initialState?: S | (() => S),
options?: Options,
) => {
type State = Partial<{ [key in keyof S]: any }>;
const { navigateMode = 'push', parseOptions, stringifyOptions } = options || {};
const mergedParseOptions = { ...baseParseConfig, ...parseOptions };
const mergedStringifyOptions = { ...baseStringifyConfig, ...stringifyOptions };
const location = rc.useLocation();
// react-router v5
const history = rc.useHistory?.();
// react-router v6
const navigate = rc.useNavigate?.();
const update = useUpdate();
const initialStateRef = useRef(
typeof initialState === 'function' ? (initialState as () => S)() : initialState || {},
);
const queryFromUrl = useMemo(() => {
return qs.parse(location.search, mergedParseOptions);
}, [location.search]);
const targetQuery: State = useMemo(
() => ({
...initialStateRef.current,
...queryFromUrl,
}),
[queryFromUrl],
);
const setState = (s: React.SetStateAction<State>) => {
const newQuery = typeof s === 'function' ? s(targetQuery) : s;
// 1. 如果 setState 后,search 没变化,就需要 update 来触发一次更新。比如 demo1 直接点击 clear,就需要 update 来触发更新。
// 2. update 和 history 的更新会合并,不会造成多次更新
update();
if (history) {
history[navigateMode](
{
hash: location.hash,
search: qs.stringify({ ...queryFromUrl, ...newQuery }, mergedStringifyOptions) || '?',
},
location.state,
);
}
if (navigate) {
navigate(
{
hash: location.hash,
search: qs.stringify({ ...queryFromUrl, ...newQuery }, mergedStringifyOptions) || '?',
},
{
replace: navigateMode === 'replace',
state: location.state,
},
);
}
};
return [targetQuery, useMemoizedFn(setState)] as const;
};
export default useUrlState;