Skip to content

useUrlState

作用

通过 url query 来管理 state 的 Hook

基础用法与原理

需要独立安装

bash
  npm install --save @umijs/use-url-state

该 Hooks 基于 react-routeruseLocation & useHistory & useNavigate 进行 query 管理,所以使用该 Hooks 之前,你需要保证:

  1. 你项目正在使用 react-router 5.x 或 6.x 版本来管理路由
  2. 独立安装了 @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

如有转载或 CV 的请标注本站原文地址