当前位置:网站首页>封装一个管理 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] Ankerui Food and Beverage Fume Monitoring Cloud Platform Helps Fight Air Pollution
- MySQL 中出现的字符编码错误 Incorrect string value: ‘\x\x\x\x‘ for column ‘x‘
- Excel advanced drawing techniques, 100 (22) - how to respectively the irregular data
- 网络安全与基础设施安全局(CISA):两国将在网络安全方面扩大合作
- Digital twin Beijing the imperial palace, yuan universe is the process of tourism
- Different operating with different locks, rounding
- 如何用Chrome编辑以及调试代码
- STAHL触摸屏维修一体机显示屏ET-316-TX-TFT常见故障
- Go 语言中常见的坑
- 【节能学院】数据机房中智能小母线与列头柜方案的对比分析
猜你喜欢

Internet使用的网络协议是什么

Remove 360's detection and modification of the default browser

数据库内核面试中我不会的问题(1)

Digital twin Beijing the imperial palace, yuan universe is the process of tourism

Pytorch框架学习记录8——最大池化的使用
Godaddy domain name resolution is slow and how to use DNSPod resolution to solve it

【Dart】dart之mixin探究

扣减库存方案
![[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

Acrel-5010重点用能单位能耗在线监测系统在湖南三立集团的应用
随机推荐
latex paper artifact -- server deployment overleaf
MongoDB快速上手
Go Atomic
myid file is missing
Redis does check-in statistics
通俗解释:什么是临床预测模型
Godaddy域名解析速度慢问题以及如何使用DNSPod解析解决
使用百度EasyDL实现厂区工人抽烟行为识别
【节能学院】数据机房中智能小母线与列头柜方案的对比分析
Pytorch框架学习记录8——最大池化的使用
Custom command to get focus
Hiking, cured my mental internal friction
外骨骼机器人(七):标准步态数据库
徒步,治好了我的精神内耗
Convolutional Neural Network (CNN) mnist Digit Recognition - Tensorflow
Postman 批量测试接口详细教程
【Dart】dart构造函数学习记录(含dart单例模式写法)
Qt设置应用程序开机自启 解决设置失败原因
What is the difference between a utility model patent and an invention patent?Understand in seconds!
Buttons with good user experience should not have hover state on mobile phones