背景 在react-router
里如果需要获取和修改query-string
在class
组件里都是通过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