本文将讲讲ahooks中的useUrlState。Hook通过url查询来管理状态。特殊的useUrlState在之前的架构章节有提到,ahooks项目是一个monoRepo。它的项目管理是通过lerna[1]进行管理的。从官网和源码可以看出,useUrlState是由一个独立的仓库管理的。也就是必须单独安装:npmi@ahooksjs/use-url-state-S我认为官方的原因是useUrlState是基于react-router的useLocation&useHistory&useNavigate进行查询管理的。所以你必须安装react-router版本5.x或6.x。但实际上很多React项目并不一定要用到react-router。如果将此钩子内置到ahooks中,可能会导致包体积增大。此外,这个钩子依赖于查询字符串npm包。我觉得使用这个包的原因有以下几点:第一,它功能强大,支持的选项很多,可以满足我们各种业务需求。二是在行业内也比较成熟,避免了重新发明轮子。第三,它的包包体积也很小,没有负担。我们主要使用它的parse和stringify方法,压缩后只有2.4k。下面通过例子简单介绍一下这两个方法: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的配置,包括状态改变时切换历史的方式,query-stringparse和stringify的配置。URL的location对象是通过react-router的useLocation获取的。react-router兼容5.x和6.x,其中5.x使用useHistory,6.x使用useNavigate。queryFromUrl调用query-string的parse方法来处理将位置对象搜索为对象值。targetQuery是处理后的最终值——将queryFromUrl与初始值合并的结果。//...import*astmpfrom'react-router';//...constuseUrlState=(//初始状态initialState?:S|(()=>S),//urlconfigurationoptions?:Options,)=>{typeState=Partial<{[keyofS]:any}>;const{//状态改变时切换历史的方式navigateMode='push',//query-String解析配置parseOptions,//query-stringstringify配置stringifyOptions,}=options||{};constmergedParseOptions={...baseParseConfig,...parseOptions};constmergedStringifyOptions={...baseStringifyConfig,...stringifyOptions};//useLocation钩子返回一个代表当前URL的位置对象。您可以将其视为useState,它会在URL更改时返回新值。constlocation=rc.useLocation();//https://v5.reactrouter.com/web/api/Hooks/usehistory//useHistory钩子可以访问用于导航的历史实例。//react-routerv5consthistory=rc.useHistory?.();//react-routerv6constnavigate=rc.useNavigate?.();constupdate=useUpdate();constinitialStateRef=useRef(typeofinitialState==='function'?(initialStateas()=>S)():initialState||{},);//根据url查询constqueryFromUrl=useMemo(()=>{returnparse(location.search,mergedParseOptions);},[location.search]);consttargetQuery:State=useMemo(()=>({...initialStateRef.current,...queryFromUrl,}),[queryFromUrl],);//省略部分代码}connect先看url的state设置:首先根据传入的s的值,得到新的statenewQuery,支持函数模式。根据不同的react-router版本调用不同的方法进行更新。如果是5.x版本,调用useHistory方法更新对应状态。如果是6.x版本,调用useNavigate方法更新对应状态。通过update()-useUpdate()更新状态。//设置url状态constsetState=(s:React.SetStateAction)=>{constnewQuery=typeofs==='function'?s(目标查询):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,方便文档管理和一些公共包管理。