【react.js + hooks】useUrl 监听路由参数

【react.js + hooks】useUrl 监听路由参数

本节我们来实现一个监听并解析 URL 参数的 hook:useUrl。而且这个 hook 的返回类型是可推断的。

实现思路

  • 监听 URL 变化 - 事件监听
  • 根据 URL 地址获取参数并返回 - 依赖工具方法
  • 推断参数结构 - 泛型参数(对象式 & 模板式)
  • 返回参数 - 返回解析好的参数,并合并 location 和 history 以提供更多功能

监听 URL

监听 popstate 事件即可,注意因为是全局监听,创建一个总事件。

// 全局的事件监听器
const listeners = new Set<Function>();window.addEventListener("popstate", () => {// do somethinglisteners.forEach((listener) => listener());
});

解析参数

使用内置的 decodeURIComponent 解析参数即可:

  • 后面几个参数是对解析的细节配置
    • mode - 分了两种解析模式
      • string - 全解析为字符串
      • auto - 智能解析
    • autoParams - 指定被智能解析的字段
    • stringifyParams - 指定被解析为字符串的字段
    • custom - 自定义参数解析的映射配置
function getParams<T>(url: string,mode: "string" | "auto" = "auto",autoParams: (keyof T | (string & {}))[] = [],stringifyParams: (keyof T | (string & {}))[] = [],custom: { [K in keyof T]?: (value: string | undefined) => any } = {}
) {const params: {[key: string]: string | number | boolean | null | undefined;} = {};// 先处理 custom 对象for (const key in custom) {const value = new URLSearchParams(url).get(key);params[key] = custom[key as keyof T]?.(value ?? undefined);}const questionMarkIndex = url.indexOf("?");if (questionMarkIndex !== -1) {const queryString = url.substring(questionMarkIndex + 1);const pairs = queryString.split("&");for (const pair of pairs) {const [key, value] = pair.split("=");try {const decodedKey = decodeURIComponent(key);const decodedValue = decodeURIComponent(value);if (custom[decodedKey as keyof T]) {continue; // 如果这个键在 custom 对象中,我们已经处理过它了}if (stringifyParams.includes(decodedKey)) {params[decodedKey] = decodedValue;} else if (autoParams.includes(decodedKey) || mode === "auto") {if (decodedValue === "true") {params[decodedKey] = true;} else if (decodedValue === "false") {params[decodedKey] = false;} else if (decodedValue === "null") {params[decodedKey] = null;} else if (decodedValue === "undefined") {params[decodedKey] = undefined;} else if (!isNaN(Number(decodedValue))) {params[decodedKey] = Number(decodedValue);} else {params[decodedKey] = decodedValue;}} else {params[decodedKey] = decodedValue;}} catch (error) {console.error("Failed to decode URL parameter:", error);}}}return params as T;
}

类型推断

繁琐的类型体操,Github ts 练习中的 ParseQueryString 魔改升级版,加入了一些解析配置的泛型参数,以支持尽可能细致的类型推断(看看就好,工作中不建议写,费时间且头大,虽然写完后用着很舒服…),代码见完整实现。

完整实现

import { useState, useEffect, useMemo } from "react";
import { ApplyMode, ParseQueryString, Prettify } from "./types";type UrlInfo<T extends Record<string, any>> = {readonly params: Prettify<Readonly<T>>;readonly name?: string;
} & Location &History;type UrlChangeCallback<T extends Record<string, any>> = (urlInfo: UrlInfo<T>
) => void;function getParams<T>(url: string,mode: "string" | "auto" = "auto",autoParams: (keyof T | (string & {}))[] = [],stringifyParams: (keyof T | (string & {}))[] = [],custom: { [K in keyof T]?: (value: string | undefined) => any } = {}
) {const params: {[key: string]: string | number | boolean | null | undefined;} = {};// 先处理 custom 对象for (const key in custom) {const value = new URLSearchParams(url).get(key);params[key] = custom[key as keyof T]?.(value ?? undefined);}const questionMarkIndex = url.indexOf("?");if (questionMarkIndex !== -1) {const queryString = url.substring(questionMarkIndex + 1);const pairs = queryString.split("&");for (const pair of pairs) {const [key, value] = pair.split("=");try {const decodedKey = decodeURIComponent(key);const decodedValue = decodeURIComponent(value);if (custom[decodedKey as keyof T]) {continue; // 如果这个键在 custom 对象中,我们已经处理过它了}if (stringifyParams.includes(decodedKey)) {params[decodedKey] = decodedValue;} else if (autoParams.includes(decodedKey) || mode === "auto") {if (decodedValue === "true") {params[decodedKey] = true;} else if (decodedValue === "false") {params[decodedKey] = false;} else if (decodedValue === "null") {params[decodedKey] = null;} else if (decodedValue === "undefined") {params[decodedKey] = undefined;} else if (!isNaN(Number(decodedValue))) {params[decodedKey] = Number(decodedValue);} else {params[decodedKey] = decodedValue;}} else {params[decodedKey] = decodedValue;}} catch (error) {console.error("Failed to decode URL parameter:", error);}}}return params as T;
}// 全局的事件监听器
const listeners = new Set<Function>();window.addEventListener("popstate", () => {listeners.forEach((listener) => listener());
});/*** ## useUrl hook* Converts a string to a query parameter object. Return an object merged with location, history, params and name.** ### Parameters* - callback (?) - The **callback** to call when the url changes.* - name (?) - The name of the listener* - immediate (`false`) - Whether to call the callback immediately.* - config (?) - The configuration of the params parser.*   + mode (`"auto"`) - The mode of the params parser: `"string"` | `"auto"` = `"auto"`.*   + autoParams (?) - The parameters to treat as auto.*   + stringifyParams (?) - The parameters to treat as string.*   + custom (?) - The custom parser of certain query parameters.** ### Type Parameters* - T - `string` or `object`.*   + The string to convert, like `"http://localhost?id=1&name=evan"`*   + object: object to inferred as, like `{ id: 1, name: "evan" }`* - Mode - The mode to use when converting: `"string"` | `"fuzzy"` | `"auto"` | `"strict"` | `"any"` = `"auto"`.* - StrictParams - The parameters to treat as strict.* - FuzzyParams - The parameters to treat as fuzzy.** ### Notes* - Type infer mode is not associated with the mode parameter of parser.** @return location merged with history, params and name.*/
function useUrl<T extends Record<string, any> | string,Mode extends "any" | "fuzzy" | "auto" | "auto" | "strict" = "auto",StrictParams extends string[] = [],FuzzyParams extends string[] = []
>(callback?: UrlChangeCallback<Partial<T extends string? ParseQueryString<T, Mode, StrictParams, FuzzyParams>: ApplyMode<T, Mode, StrictParams, FuzzyParams>>>,name?: string,immediate?: boolean,config: {mode?: "string" | "auto";autoParams?: (| keyof (T extends string ? ParseQueryString<T> : ApplyMode<T>)| (string & {}))[];stringifyParams?: (| keyof (T extends string ? ParseQueryString<T> : ApplyMode<T>)| (string & {}))[];custom?: {[K in keyof (T extends string ? ParseQueryString<T> : ApplyMode<T>)]?: (value: string | undefined) => any;};} = {}
): UrlInfo<Partial<T extends string? ParseQueryString<T, Mode, StrictParams, FuzzyParams>: ApplyMode<T, Mode, StrictParams, FuzzyParams>>
> {function getUrlInfo() {return {params: getParams(window.location.href,config?.mode,config?.autoParams,config?.stringifyParams,config?.custom),name: name,...window.location,...window.history,};}const [urlInfo, setUrlInfo] = useState<UrlInfo<T extends string? ParseQueryString<T, Mode, StrictParams, FuzzyParams>: ApplyMode<T, Mode, StrictParams, FuzzyParams>>>(getUrlInfo() as any);const memoizedConfig = useMemo(() => config,[config.mode, config.autoParams, config.stringifyParams, config.custom]);useEffect(() => {if (immediate) {const urlInfo = getUrlInfo();callback?.(urlInfo as any);setUrlInfo(urlInfo as any);}}, [immediate, JSON.stringify(memoizedConfig), name]);useEffect(() => {const handlePopState = () => {const urlInfo = getUrlInfo();setUrlInfo(urlInfo as any);callback?.(urlInfo as any);};// 在组件挂载时注册回调函数listeners.add(handlePopState);return () => {// 在组件卸载时注销回调函数listeners.delete(handlePopState);};}, [callback]);return urlInfo as any;
}export default useUrl;

types:

/*** Converts a string to a query parameter object.* ### Parameters* - S - The string to convert, like `"http://localhost?id=1&name=evan"`.* - Mode - The mode to use when converting: `"string"` | `"fuzzy"` | `"auto"` | `"strict"` | `"any"` = `"auto"`.** - StrictParams - The parameters to treat as strict.** - FuzzyParams - The parameters to treat as fuzzy.** @return A query parameter object*/
export type ParseQueryString<S extends string,Mode extends "string" | "fuzzy" | "auto" | "strict" | "any" = "auto",StrictParams extends string[] = [],FuzzyParams extends string[] = []
> = Prettify<S extends `${infer _Prefix}?${infer Params}`? Params extends ""? {}: MergeParams<SplitParams<Params>, Mode, StrictParams, FuzzyParams>: MergeParams<SplitParams<S>, Mode, StrictParams, FuzzyParams>
>;type SplitParams<S extends string> = S extends `${infer E}&${infer Rest}`? [E, ...SplitParams<Rest>]: [S];type MergeParams<T extends string[],Mode extends "string" | "fuzzy" | "auto" | "strict" | "any" = "auto",StrictParams extends string[] = [],FuzzyParams extends string[] = [],M = {}
> = T extends [infer E, ...infer Rest extends string[]]? E extends `${infer K}=${infer V}`? MergeParams<Rest,Mode,StrictParams,FuzzyParams,SetProperty<M, K, V, Mode, StrictParams, FuzzyParams>>: E extends `${infer K}`? MergeParams<Rest,Mode,StrictParams,FuzzyParams,SetProperty<M, K, undefined, Mode, StrictParams, FuzzyParams>>: never: M;type SetProperty<T,K extends PropertyKey,V extends any = true,Mode extends "string" | "fuzzy" | "auto" | "strict" | "any" = "auto",StrictParams extends string[] = [],FuzzyParams extends string[] = []
> = {[P in keyof T | K]: P extends K? P extends keyof T? T[P] extends V? T[P]: T[P] extends any[]? V extends T[P][number]? T[P]: [...T[P], V]: [T[P], V]: P extends FuzzyParams[number]? string: P extends StrictParams[number]? V extends "true"? true: V extends "false"? false: V extends "null"? null: V extends `${number}`? number: V: Mode extends "string"? string: Mode extends "fuzzy"? string: Mode extends "auto"? V extends "true" | "false"? boolean: V extends "null"? null: V extends `${number}`? number: string: Mode extends "strict"? V extends "true"? true: V extends "false"? false: V extends "null"? null: V extends `${number}`? ToNumber<V>: V: Mode extends "any"? any: never: P extends keyof T? T[P]: never;
};export type ApplyMode<T,Mode extends "string" | "fuzzy" | "auto" | "strict" | "any" = "auto",StrictParams extends string[] = [],FuzzyParams extends string[] = []
> = Mode extends "auto"? T: {[P in keyof T]: P extends FuzzyParams[number]? string: P extends StrictParams[number]? T[P] extends "true"? true: T[P] extends "false"? false: T[P] extends "null"? null: T[P] extends `${number}`? ToNumber<T[P]>: T[P]: Mode extends "string"? string: Mode extends "fuzzy"? string: Mode extends "strict"? T[P] extends "true"? true: T[P] extends "false"? false: T[P] extends "null"? null: T[P] extends `${number}`? ToNumber<T[P]>: T[P]: Mode extends "any"? any: T[P];};export type Prettify<T> = {[K in keyof T]: T[K];
} & {};

使用示例

比如在地址栏中传 id 和 source 两个参数,并更改它们的值:

const { params } = useUrl<"?id=2&source=Hangzhou">((urlInfo) => {console.log(`id: ${urlInfo.params.id} source: ${urlInfo.params.source}`);},"ursUrl exmaple listener",true // call immediately
);

Bingo! 一个监听 URL 的 hook 就酱紫实现了!TS 虽好,但请慎用!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/590601.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Nginx 不同源Https请求Http 报strict-origin-when-cross-origin

原因&#xff1a; nginx代理配置url指向只开放了/* 而我/*/*多了一层路径 成功&#xff1a;

2024新版塔罗占卜网站源码风水起名附带搭建视频及文本教程

附带文本教学及视频教程安装方法以linux为例&#xff1a; 1、建议在服务器上面安装宝塔面板&#xff0c;以便操作&#xff0c;高逼格技术员可以忽略这步操作。 2、把安装包文件解压到根目录&#xff0c;同时建立数据库&#xff0c;把数据文件导入数据库 3、修改核心文件conf…

python多环境管理工具——pyenv-win安装与使用教程

目录 pyenv-win简介 pyenv-win安装 配置环境变量 pyenv的基本命令 pyenv安装py环境 pyenv安装遇到问题 pycharm测试 pyenv-win简介 什么是pyenv-win&#xff1a; 是一个在windows系统上管理python版本的工具。它是pyenv的windows版本&#xff0c;旨在提供类似于unix/li…

ASP.NET Core SignalR推送服务器日志

产线机器人项目,上位机以读写寄存器的方式控制机器人,服务器就是用 ASP.NET Core 写的 Web API。由于前一位开发者写的代码质量问题,导致上位机需要16秒才能启动。经过改造,除了保留业务逻辑代码,其他的基本重写。如今上位机的启动时间在网络状态良好的条件下可以秒启动。…

Avalonia 跨ViewModel访问数据或方法

在Avalonia应用程序中&#xff0c;跨ViewModel访问数据或方法通常是为了实现不同视图间的数据共享和通信。在MVVM设计模式下&#xff0c;这可以通过多种方式进行&#xff1a; 依赖注入&#xff08;DI&#xff09;&#xff1a; 通过IoC容器&#xff08;如Autofac、DryIoc等&…

工作八年经验总结

今年没怎么写博客了&#xff0c;2023年一共才发了5篇&#xff0c;在CSDN的排名也是名落孙山&#xff08;从最辉煌时的几百名落到了180w&#xff09;&#xff0c;在纠结要不要断更&#xff0c;算了&#xff0c;今年我还是在矫情下吧。。。。 【工作篇】 1、2023挑战与机遇并存…

cargo设置国内源 windows+linux

cargo默认的源比pip的源好多了&#xff0c;但是有时候速度还是很慢 一、部分国内源&#xff08;排名不分先后&#xff09; 这些源的格式用在具体的配置文件中 中国科学技术大学 [source.crates-io] replace-with ustc[source.ustc] registry "git://mirrors.ustc.ed…

Redis Cluster集群模式学习

Redis Cluster集群模式 Redis哨兵模式&#xff1a;https://blog.csdn.net/liwenyang1992/article/details/133956200 Redis Cluster集群模式示意图&#xff1a; Cluster模式是Redis3.0开始推出采用无中心结构&#xff0c;每个节点保存数据和整个集群状态&#xff0c;每个节点都…

[C#]OpenCvSharp结合yolov8-face实现L2CS-Net眼睛注视方向估计或者人脸朝向估计

源码地址&#xff1a; github地址&#xff1a;https://github.com/Ahmednull/L2CS-Net L2CS-Net介绍&#xff1a; 眼睛注视&#xff08;eye gaze&#xff09; 是在各种应用中使用的基本线索之一。 它表示用户在人机交互和开放对话系统中的参与程度。此外&#xff0c;它还被用…

C/C++面向对象(OOP)编程-回调函数详解(回调函数、C/C++异步回调、函数指针)

本文主要介绍回调函数的使用&#xff0c;包括函数指针、异步回调编程、主要通过详细的例子来指导在异步编程和事件编程中如何使用回调函数来实现。 &#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;C/C精进之路 &…

再见2023,你好2024!

大家好&#xff0c;我是老三&#xff0c;本来今天晚上打算出去转一转&#xff0c;陆家嘴打车实在太艰难了&#xff0c;一公里多的路&#xff0c;司机走了四十分钟&#xff0c;还没到&#xff0c;再加上身体不适&#xff0c;咳嗽地比较厉害&#xff0c;所以还是宅在酒店里&#…

不同开源协议之间的差异分析

在IT行业中&#xff0c;开源协议是用来定义如何使用、修改、分享和分发软件的法律条款。不同的开源协议在保留版权、允许的使用方式、对衍生作品的要求以及对分发的限制等方面有所不同。以下是一些常用的开源协议及其主要特点&#xff1a; 1. MIT License (MIT) 特点&#xf…

用通俗易懂的方式讲解大模型:使用 Docker 部署大模型的训练环境

之前给大家介绍了主机安装方式——如何在 Ubuntu 操作系统下安装部署 AI 环境&#xff0c;但随着容器化技术的普及&#xff0c;越来越多的程序以容器的形式进行部署&#xff0c;通过容器的方式不仅可以简化部署流程&#xff0c;还可以随时切换不同的环境。 实际上很多云服务厂…

Java ArrayList在遍历时删除元素

文章目录 1. Arrays.asList()获取到的ArrayList只能遍历&#xff0c;不能增加或删除元素2. java.util.ArrayList.SubList有实现add()、remove()方法3. 遍历集合时对元素重新赋值、对元素中的属性赋值、删除元素、新增元素3.1 普通for循环3.2 增强for循环3.3 forEach循环3.4 str…

SpringBoot之YAML文件的使用

系列文章目录 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 SpringBoot之YAML文件的使用 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 系列文章目录前言一、YAML配置…

vue的file-saver

Vue FileSaver 是一个用于在浏览器中保存文件的 Vue.js 插件。它提供了一种简单的方式来将数据以文件的形式下载到用户的计算机上。 使用 Vue FileSaver&#xff0c;你可以将数据保存为常见的文件格式&#xff0c;如文本文件&#xff08;.txt&#xff09;、CSV 文件&#xff0…

产业互联网,并不存在严格意义上的互联网

产业互联网里的「互联网」字眼&#xff0c;让人们想当然地认为&#xff0c;其与互联网之间有着很多的联系&#xff0c;甚至很多人干脆就将产业互联网当成了一个互联网的衍生品&#xff0c;最终&#xff0c;再度将产业互联网带入到了互联网的怪圈之中。 事实上&#xff0c;真正…

Java中的自定义异常处理:业务异常类的创建与使用

文章内容 引言 在Java编程中&#xff0c;异常处理是一项重要的技术&#xff0c;它允许程序在遇到错误或特殊情况时能够优雅地处理&#xff0c;而不是直接崩溃。Java提供了丰富的内置异常类&#xff0c;但在实际业务开发中&#xff0c;我们往往需要根据具体的业务需求定义自己的…

目标检测-Two Stage-Mask RCNN

文章目录 前言一、Mask RCNN的网络结构和流程二、Mask RCNN的创新点总结 前言 前文目标检测-Two Stage-Faster RCNN提到了Faster RCNN主要缺点是&#xff1a; ROI Pooling有两次量化操作&#xff0c;会引入误差影响精度 Mask RCNN针对这一缺点做了改进&#xff0c;此外Mask …

数据结构——顺序栈与链式栈的实现

目录 一、概念 1、栈的定义 2、栈顶 3、栈底 二、接口 1、可写接口 1&#xff09;数据入栈 2&#xff09;数据出栈 3&#xff09;清空栈 2、只读接口 1&#xff09;获取栈顶数据 2&#xff09;获取栈元素个数 3&#xff09;栈的判空 三、栈的基本运算 四、顺序栈&…