前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >封装一个管理 url 状态的 hook

封装一个管理 url 状态的 hook

作者头像
GopalFeng
发布2022-08-01 20:12:50
1.1K0
发布2022-08-01 20:12:50
举报
文章被收录于专栏:前端杂货铺-Gopal

本文是深入浅出 ahooks 源码系列文章的第十一篇,这个系列的目标主要有以下几点:

  • 加深对 React hooks 的理解。
  • 学习如何抽象自定义 hooks。构建属于自己的 React hooks 工具库。
  • 培养阅读学习源码的习惯,工具库是一个对源码阅读不错的选择。

本文来讲下 ahooks 中的 useUrlState。

通过 url query 来管理 state 的 Hook。

useUrlState 的特殊

在之前的架构篇中我们就提到,ahooks 这个项目是一个 monoRepo。它的项目管理是通过 lerna[1] 进行管理的。可以从官网以及源码中看到 useUrlState 是独立一个仓库进行管理的。

也就是你必须单独安装:

代码语言:javascript
复制
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])

代码语言:javascript
复制
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])

代码语言:javascript
复制
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 之后的结果。
代码语言:javascript
复制
// ...
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() 更新状态。
代码语言:javascript
复制
// 设置 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/

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-06-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端杂货铺 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • useUrlState 的特殊
  • useUrlState 源码解析
  • 思考与总结
    • 参考资料
    相关产品与服务
    项目管理
    CODING 项目管理(CODING Project Management,CODING-PM)工具包含迭代管理、需求管理、任务管理、缺陷管理、文件/wiki 等功能,适用于研发团队进行项目管理或敏捷开发实践。结合敏捷研发理念,帮助您对产品进行迭代规划,让每个迭代中的需求、任务、缺陷无障碍沟通流转, 让项目开发过程风险可控,达到可持续性快速迭代。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档