当前位置:网站首页>封装一个管理 url 状态的 hook
封装一个管理 url 状态的 hook
2022-08-01 20:58:00 【GopalFeng】
本文是深入浅出 ahooks 源码系列文章的第十一篇,这个系列的目标主要有以下几点:
- 加深对 React hooks 的理解。
- 学习如何抽象自定义 hooks。构建属于自己的 React hooks 工具库。
- 培养阅读学习源码的习惯,工具库是一个对源码阅读不错的选择。
本文来讲下 ahooks 中的 useUrlState。
通过 url query 来管理 state 的 Hook。
useUrlState 的特殊
在之前的架构篇中我们就提到,ahooks 这个项目是一个 monoRepo。它的项目管理是通过 lerna[1] 进行管理的。可以从官网以及源码中看到 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 的包管理方法,方便进行文档管理以及一些公共包管理等。
参考资料
[1]
lerna: https://www.lernajs.cn/
边栏推荐
猜你喜欢
![[Energy Conservation Institute] Application of Intelligent Control Device in High Voltage Switchgear](/img/6d/05233ce5c91a612b6247ea07d7982e.jpg)
[Energy Conservation Institute] Application of Intelligent Control Device in High Voltage Switchgear

OSG Notes: Set DO_NOT_COMPUTE_NEAR_FAR to manually calculate far and near planes
![[Multi-task optimization] DWA, DTP, Gradnorm (CVPR 2019, ECCV 2018, ICML 2018)](/img/a1/ec038eeb6c98c871eb31d92569533d.png)
[Multi-task optimization] DWA, DTP, Gradnorm (CVPR 2019, ECCV 2018, ICML 2018)

数字孪生北京故宫,元宇宙推进旅游业进程

Hiking, cured my mental internal friction

使用百度EasyDL实现厂区工人抽烟行为识别

响应式织梦模板美容整形类网站

MySQL 中出现的字符编码错误 Incorrect string value: ‘\x\x\x\x‘ for column ‘x‘

4.1 配置Mysql与注册登录模块
![[Multi-task learning] Modeling Task Relationships in Multi-task Learning with Multi-gate Mixture-of-Experts KDD18](/img/f3/a8813759e5b4dd4b4132e65672fc3f.png)
[Multi-task learning] Modeling Task Relationships in Multi-task Learning with Multi-gate Mixture-of-Experts KDD18
随机推荐
Excel advanced drawing techniques, 100 (22) - how to respectively the irregular data
写给刚进互联网圈子的人,不管你是开发,测试,产品,运维都适用
Software you should know as a programmer
职场如象棋,测试/开发程序员如何突破成长瓶颈期?
Hangao data import
STAHL触摸屏维修一体机显示屏ET-316-TX-TFT常见故障
Multithreaded producers and consumers
idea插件generateAllSetMethod一键生成set/get方法以及bean对象转换
乐观锁批量跟新 纯SQL
Godaddy domain name resolution is slow and how to use DNSPod resolution to solve it
【无标题】
win10版本1803无法升级1903系统如何解决
string
myid file is missing
宝塔搭建PESCMS-Ticket开源客服工单系统源码实测
【个人作品】无线网络图传模块
关于Request复用的那点破事儿。研究明白了,给你汇报一下。
响应式织梦模板清洁服务类网站
算法---解码方法(Kotlin)
[Multi-task model] Progressive Layered Extraction: A Novel Multi-Task Learning Model for Personalized (RecSys'20)