本文是深入浅出 ahooks 源码系列文章的第十一篇,该系列已整顿成文档-地址。感觉还不错,给个 star 反对一下哈,Thanks。
本文来讲下 ahooks 中的 useUrlState。
通过 url query 来治理 state 的 Hook。
useUrlState 的非凡
在之前的架构篇中咱们就提到,ahooks 这个我的项目是一个 monoRepo
。它的项目管理是通过 lerna 进行治理的。能够从官网以及源码中看到 useUrlState 是独立一个仓库进行治理的。
也就是你必须独自装置:
npm i @ahooksjs/use-url-state -S
我认为官网这么做的理由是 useUrlState 基于 react-router 的 useLocation & useHistory & useNavigate 进行 query 治理。所以你必须要装置 react-router 的 5.x 或者 6.x 版本。但其实很多 React 我的项目都不肯定应用 react-router。如果将这个 hook 内置到 ahooks 中的话,可能会导致包体积变大。
另外,该 hook 是依赖于 query-string 这个 npm 包的。应用这个包,我认为理由有以下几点:
- 一来是其功能强大,反对很多的 options 选项,满足咱们各类业务需要。
- 二来它业内也比拟成熟,防止反复造轮子。
- 三来它的包体积也很小,没什么累赘。咱们次要用到它的 parse 和 stringify 办法,压缩后只有 2.4 k。
通过示例简略介绍下,这两个办法:
qs.parse(string, [options])
qs.parse('?name=jim') // {name: 'jim'}qs.parse('#token=123') // {token: '123'}qs.parse('name=jim&name=lily&age=22') // {name: ['jim', 'lily'], age: 22}qs.parse('foo[]=1&foo[]=2&foo[]=3', {arrayFormat: 'bracket'});//=> {foo: ['1', '2', '3']}
qs.stringify(object, [options])
qs.stringify({name: 'jim', age: 22}); // 'age=22&name=jim'qs.stringify({name: ['jim', 'lily'], age: 22}); // 'age=22&name=jim&name=lily'qs.stringify({foo: [1, 2, 3]}, {arrayFormat: 'bracket'});//=> 'foo[]=1&foo[]=2&foo[]=3'
useUrlState 源码解析
间接看代码,显示初始值局部。
- 第一个参数为初始状态,第二个参数为 url 的配置,包含状态变更时切换 history 的形式、query-string parse 和 stringify 的配置。
- 通过 react-router 的 useLocation 获取到 URL 的 location 对象。
- react-router 兼容 5.x 和 6.x,其中 5.x 应用 useHistory,6.x 应用 useNavigate。
- queryFromUrl 是调用 query-string 的 parse 办法,将 location 对象的 search 解决成对象值。
- targetQuery 就是解决之后的最终值-将 queryFromUrl 和初始值进行 merge 之后的后果。
// ...import * as tmp from 'react-router';// ...const useUrlState = <S extends UrlState = UrlState>( // 初始状态 initialState?: S | (() => S), // url 配置 options?: Options,) => { type State = Partial<{ [key in keyof S]: any }>; const { // 状态变更时切换 history 的形式 navigateMode = 'push', // query-string parse 的配置 parseOptions, // query-string stringify 的配置 stringifyOptions, } = options || {}; const mergedParseOptions = { ...baseParseConfig, ...parseOptions }; const mergedStringifyOptions = { ...baseStringifyConfig, ...stringifyOptions }; // useLocation钩子返回示意以后URL的location对象。您能够将它设想成一个useState,它在URL更改时返回一个新值。 const location = rc.useLocation(); // https://v5.reactrouter.com/web/api/Hooks/usehistory // useHistory 钩子能够拜访用来导航的历史实例。 // 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 || {}, ); // 依据 url query const queryFromUrl = useMemo(() => { return parse(location.search, mergedParseOptions); }, [location.search]); const targetQuery: State = useMemo( () => ({ ...initialStateRef.current, ...queryFromUrl, }), [queryFromUrl], ); // 省略局部代码}
接下来看 url 的状态设置:
- 首先是依据传入的 s 值,获取到新的状态 newQuery,反对 function 形式。
依据不同的 react-router 的版本调用不同的办法进行更新。
- 如果是 5.x 版本,调用 useHistory 办法,更新对应的状态。
- 如果是 6.x 版本,调用 useNavigate 办法,更新对应的状态。
通过 update() -
useUpdate()
更新状态。// 设置 url 状态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) || '?', });}if (navigate) { navigate( { hash: location.hash, search: stringify({ ...queryFromUrl, ...newQuery }, mergedStringifyOptions) || '?', }, { replace: navigateMode === 'replace', }, );}};
思考与总结
工具库中如果某个工具函数/hook 依赖于一个开发者可能并不会应用的包,而且这个包的体积还比拟大的时候,能够将这个工具函数/hook 独立成一个 npm 包,开发者应用的时候才进行装置。另外这种能够思考应用 monoRepo 的包治理办法,不便进行文档治理以及一些公共包治理等。
本文已收录到集体博客中,欢送关注~