useUrlState
作用
通过 url query 来管理 state 的 Hook
基础用法与原理
需要独立安装
bash
npm install --save @umijs/use-url-state
该 Hooks 基于
react-router
的useLocation & useHistory & useNavigate
进行query
管理,所以使用该 Hooks 之前,你需要保证:
- 你项目正在使用
react-router
5.x 或 6.x 版本来管理路由- 独立安装了
@ahooksjs/use-url-state
该 hook 还依赖
query-string
这个 npm 包
源码
ts
import { useMemoizedFn, useUpdate } from 'ahooks'
import { parse, stringify } 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 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: stringify({ ...queryFromUrl, ...newQuery }, mergedStringifyOptions) || '?',
},
location.state,
)
}
if (navigate) {
navigate(
{
hash: location.hash,
search: stringify({ ...queryFromUrl, ...newQuery }, mergedStringifyOptions) || '?',
},
{
replace: navigateMode === 'replace',
state: location.state,
},
)
}
}
return [targetQuery, useMemoizedFn(setState)] as const
}
export default useUrlState