当前位置:网站首页>封装一个管理 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/
边栏推荐
- Convolutional Neural Network (CNN) mnist Digit Recognition - Tensorflow
- Interview assault 70: what is the glue bag and a bag?How to solve?
- 进行交互或动画时如何选择Visibility, Display, and Opacity
- tiup mirror merge
- 案例:MySQL主从复制与读写分离
- Telnet弱口令渗透测试
- 4.1 配置Mysql与注册登录模块
- 任务调度线程池-应用定时任务
- Common pits in the Go language
- idea插件generateAllSetMethod一键生成set/get方法以及bean对象转换
猜你喜欢
idea插件generateAllSetMethod一键生成set/get方法以及bean对象转换
进行交互或动画时如何选择Visibility, Display, and Opacity
WeChat applet cloud development | personal blog applet
Which websites are commonly used for patent searches?
Godaddy域名解析速度慢问题以及如何使用DNSPod解析解决
Nacos 配置中心
StringTable详解 串池 性能调优 字符串拼接
【Untitled】
Internet使用的网络协议是什么
【节能学院】智能操控装置在高压开关柜的应用
随机推荐
Use WeChat official account to send information to designated WeChat users
Determine a binary tree given inorder traversal and another traversal method
外骨骼机器人(七):标准步态数据库
Different operating with different locks, rounding
The Internet giant development process
扣减库存方案
【Untitled】
和我一起写一个音乐播放器,听一首最伟大的作品
[Multi-task model] Progressive Layered Extraction: A Novel Multi-Task Learning Model for Personalized (RecSys'20)
Based on FPGA in any number of bytes (single-byte or multibyte) serial port (UART) to send (including source engineering)
Acrel-5010重点用能单位能耗在线监测系统在湖南三立集团的应用
Godaddy domain name resolution is slow and how to use DNSPod resolution to solve it
WeChat applet cloud development | personal blog applet
人工智能可信安全与评测
数字孪生北京故宫,元宇宙推进旅游业进程
Internet使用的网络协议是什么
漏洞分析丨HEVD-0x6.UninitializedStackVariable[win7x86]
myid file is missing
用户身份标识与账号体系实践
[Energy Conservation Institute] Application of Intelligent Control Device in High Voltage Switchgear