当前位置: 首页 > news >正文

【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 虽好,但请慎用!

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【Java技术专题】「入门到精通系列」深入探索Java技术中常用到的六种加密技术和代码
  • 计算机网络(1)
  • 你好2024!
  • Css基础内容
  • 迈向通用异常检测和理解:大规模视觉语言模型(GPT-4V)率先推出
  • 马尔科夫假设
  • WPF 漂亮长方体、正文体简单实现方法 Path实现长方体 正方体方案 WPF快速实现长方体、正方体的方法源代码
  • 使用JavaScript实现图片轮播效果
  • 致最后【个人】
  • 第三章 Linux 用户与用户
  • JAVA Web 期末复习
  • 使用electron属性实现保存图片并获取图片的磁盘路径
  • 循环冗余效验码的计算方法
  • 【仅供测试】
  • 力扣133. 克隆图
  • 8年软件测试工程师感悟——写给还在迷茫中的朋友
  • Bytom交易说明(账户管理模式)
  • Javascripit类型转换比较那点事儿,双等号(==)
  • Node 版本管理
  • PHP变量
  • 包装类对象
  • 成为一名优秀的Developer的书单
  • 从setTimeout-setInterval看JS线程
  • 翻译--Thinking in React
  • 深度学习中的信息论知识详解
  • 时间复杂度与空间复杂度分析
  • 微服务入门【系列视频课程】
  • 一加3T解锁OEM、刷入TWRP、第三方ROM以及ROOT
  • 在Mac OS X上安装 Ruby运行环境
  • ​中南建设2022年半年报“韧”字当头,经营性现金流持续为正​
  • # 飞书APP集成平台-数字化落地
  • (DFS + 剪枝)【洛谷P1731】 [NOI1999] 生日蛋糕
  • (ISPRS,2023)深度语义-视觉对齐用于zero-shot遥感图像场景分类
  • (TipsTricks)用客户端模板精简JavaScript代码
  • (附源码)小程序儿童艺术培训机构教育管理小程序 毕业设计 201740
  • (免费领源码)Python#MySQL图书馆管理系统071718-计算机毕业设计项目选题推荐
  • (四)进入MySQL 【事务】
  • (一)搭建springboot+vue前后端分离项目--前端vue搭建
  • (转) 深度模型优化性能 调参
  • (自用)网络编程
  • .net core 外观者设计模式 实现,多种支付选择
  • .Net mvc总结
  • .net 逐行读取大文本文件_如何使用 Java 灵活读取 Excel 内容 ?
  • .net反编译工具
  • .NET基础篇——反射的奥妙
  • .Net实现SCrypt Hash加密
  • //usr/lib/libgdal.so.20:对‘sqlite3_column_table_name’未定义的引用
  • @GlobalLock注解作用与原理解析
  • @kafkalistener消费不到消息_消息队列对战之RabbitMq 大战 kafka
  • [ 蓝桥杯Web真题 ]-Markdown 文档解析
  • [20180129]bash显示path环境变量.txt
  • [Algorithm][综合训练][kotori和气球][体操队形][二叉树中的最大路径和]详细讲解
  • [C/C++]_[初级]_[关于编译时出现有符号-无符号不匹配的警告-sizeof使用注意事项]
  • [C++][STL源码剖析] 详解AVL树的实现
  • [CF543A]/[CF544C]Writing Code