React QueryString Hooks

背景

react-router里如果需要获取和修改query-stringclass组件里都是通过route组件的props传入的方法实现的, 但是在函数式组件里缺少比较好的方法, 再加上项目中有些复杂的格式需要转成query-string, 于是需要封装一个全局Hooks提供优雅的读写方式

开发

这个相比起form-hooks来说就简单很多, 原理就是通过react-router提供的__RouterContext结合useContext来提供全局读写router的能力

useRouter

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
import { useContext } from 'react';
import { __RouterContext as RouterContext } from 'react-router';
import { LocationDescriptorObject, LocationState } from 'history';

export default function useRouter() {
return useContext(RouterContext);
}

export function useMatch() {
const { match } = useRouter();
return match;
}

export function useParams() {
const { params } = useMatch();
return params;
}

export function useLocation() {
const { location, history } = useRouter();

function navigate(to: string | LocationDescriptorObject<LocationState>, { replace = false }: { replace?: boolean } = {}) {
if (replace) {
history.replace(to as LocationDescriptorObject<LocationState>);
} else {
history.push(to as LocationDescriptorObject<LocationState>);
}
}

return {
location,
navigate,
};
}

useQueryString

可以在组件中直接获取router后, 再封装一个方便读写使用router组件的hooks即可

这里有个重要的点是setQueryStringState会返回一个设置是否成功的选项, 用于某些同步设置的地方做下一步操作, 如果url本来就没有变化, 可以不进行下一步操作

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
import _ from 'lodash';
import { useMemo, useRef, useCallback } from 'react';
import queryString from 'query-string';

type QueryResultData<K extends keyof { [key: string]: any }> = {
[P in K]: string | string[] | null | undefined;
};

const useQueryString = <T extends readonly string[], U extends T[number]>(
search: string,
keys: T,
): [QueryResultData<U>, (param: QueryResultData<U>, replace?: boolean) => boolean] => {
const { history } = useRouter();

const searchQuery = queryString.parse(search, { arrayFormat: 'index' });
const currentSearch = queryString.stringify(searchQuery, { arrayFormat: 'index' });

const searchQueryValidateValueRef = useRef<QueryResultData<string>>(_.pick(searchQuery, keys));
const searchQueryValidateValue = _.pick(searchQuery, keys);

const ref = useRef<{ [key: string]: string | string[] }>({});
_.map(keys, (key) => {
if (_.isArray(searchQueryValidateValue[key])) {
if (!_.isEqual(searchQueryValidateValue[key], ref.current[key])) {
ref.current[key] = searchQueryValidateValue[key];
} else {
searchQueryValidateValue[key] = ref.current[key];
}
}
});
if (!_.isEqual(searchQueryValidateValueRef.current, searchQueryValidateValue)) {
searchQueryValidateValueRef.current = searchQueryValidateValue;
}

const notEmptySearch = queryString.stringify(searchQueryValidateValueRef.current, { arrayFormat: 'index' });

useMemo(() => {
if (notEmptySearch !== currentSearch) {
history.replace({
search: notEmptySearch,
});
}
}, [notEmptySearch, currentSearch, history]);
const setQueryStringState = useCallback(
(param: QueryResultData<U>, _replace: boolean = false) => {
const validateParam = queryString.stringify(_.pick(param, keys), { arrayFormat: 'index' });
if (!_.isEqual(searchQueryValidateValueRef.current, queryString.parse(validateParam, { arrayFormat: 'index' }))) {
history[_replace ? 'replace' : 'push']({
search: validateParam,
});
return true;
}
return false;
},
[history, keys],
);
return [searchQueryValidateValueRef.current, setQueryStringState];
};

export default useQueryString;

query-string

在开发了比较多的系统功能后, 对怎么合理的使用query-string有一个比较固定的流程, 这里记录下来以供参考

sequenceDiagram participant U as URL-Search participant QSS as QueryStringState participant LS as LocalState participant I as Input participant D as Data alt 组件加载 U ->> QSS: 1. 从URL-Search中获取需要的数据, 存到QueryStringState中 opt 数据验证 QSS -->> U: 2. 验证非法值, 将非法值修复后同步回URL U ->> QSS: 3. 从URL-Search中获取最新合法数据 end QSS ->> LS: 4. 从QueryStringState转化为LocalState, 添加默认值 QSS ->> D: 5. 将转化后的值用于数据请求 LS ->> I: 6. 并将这些数据初始化到页面输入组件(input/select)中 end alt 更新参数 I -->> I: 7. 用户输入数据 I -->> LS: 8. 数据同步到LocalState LS -->> LS: 9. debounce/enter等事件触发数据实际生效(如确认按钮) LS -->> U: 10. 格式化为合法的search后写入URL U ->> QSS: 11. URL-Search更新, 同步到QueryStringState QSS -->> LS: 12. QueryStringState更新, 同步到LocalState(因为第10步应该不会实际写入数据) QSS ->> D: 13. 将最新数据用于数据请求 LS -->> I: 14. LocalState数据更新到页面输入组件(同样不会造成实际变化) end
作者

Mosby

发布于

2019-04-03

许可协议

评论