React Router Dom v6:如何安全规范地在URL搜索参数中存储对象?
在React Router Dom v6中规范安全地存储对象到URL搜索参数
使用React Router Dom v6时,若需要将复杂对象(如下方示例的filters)存储到URL搜索参数中,直接通过JSON序列化/反序列化的实现方式存在不够严谨、可读性差等问题,以下是几种更规范安全的实现方案:
const filters = { name: "user1", region: { city: "Sydney", country: "Australia" } };
方案1:扁平化参数命名(推荐用于需URL可读的场景)
将嵌套对象拆解为扁平化的键值对,比如把region.city作为参数名,直接存储原始值,无需JSON转换。这种方式兼容性好,URL可读性高,避免JSON解析错误。
写入参数示例:
import { useSearchParams, createSearchParams } from "react-router-dom"; const [, setSearchParams] = useSearchParams(); const setFilters = (filters: typeof filters) => { const flatParams = { name: filters.name, "region.city": filters.region.city, "region.country": filters.region.country }; setSearchParams(createSearchParams(flatParams), { replace: true }); };
读取参数示例:
const [searchParams] = useSearchParams(); const getFilters = () => { return { name: searchParams.get("name") || "", region: { city: searchParams.get("region.city") || "", country: searchParams.get("region.country") || "" } }; };
方案2:封装严谨的JSON序列化工具函数
如果必须存储嵌套对象,可将序列化/反序列化逻辑封装为通用工具函数,补充URL编码、严格类型检查和错误处理,避免原始实现的粗糙问题。
工具函数封装:
// 序列化:将对象转换为可存入搜索参数的键值对 const serializeSearchParams = <T extends Record<string, any>>(obj: T): Record<string, string> => { return Object.entries(obj).reduce((acc, [key, value]) => { if (value === null || value === undefined) { acc[key] = ""; } else if (typeof value === "object") { // 先序列化再URL编码,避免特殊字符破坏参数结构 acc[key] = encodeURIComponent(JSON.stringify(value)); } else { acc[key] = String(value); } return acc; }, {} as Record<string, string>); }; // 反序列化:从搜索参数还原为原始对象 const deserializeSearchParams = <T extends Record<string, any>>(params: URLSearchParams): T => { const result = {} as T; for (const [key, value] of params.entries()) { if (!value) { result[key as keyof T] = null as unknown as T[keyof T]; continue; } try { // 先URL解码再解析JSON const parsed = JSON.parse(decodeURIComponent(value)); // 验证解析结果是否为对象/数组,避免误解析普通字符串 if (typeof parsed === "object" && parsed !== null) { result[key as keyof T] = parsed as T[keyof T]; } else { result[key as keyof T] = value as unknown as T[keyof T]; } } catch { // 解析失败直接返回原始值 result[key as keyof T] = value as unknown as T[keyof T]; } } return result; };
使用示例:
import { useSearchParams, createSearchParams } from "react-router-dom"; import { useMemo } from "react"; const [searchParams, setSearchParams] = useSearchParams(); // 读取参数 const allSearchParams = useMemo(() => { return deserializeSearchParams<typeof filters>(searchParams); }, [searchParams]); // 写入参数 const setFilters = (nextFilter: typeof filters, replaceSearch = true) => { const serializedParams = serializeSearchParams(nextFilter); setSearchParams(createSearchParams(serializedParams), { replace: replaceSearch }); };
方案3:Base64编码(适用于需隐藏参数结构的场景)
将整个对象序列化为JSON后再进行Base64编码(需使用URL安全的Base64格式),优点是URL更简洁,缺点是参数不可读,且Base64并非加密方式,请勿存储敏感信息。
工具函数示例:
// URL安全的Base64编码 const encodeObjToBase64 = <T>(obj: T): string => { return btoa(JSON.stringify(obj)) .replace(/\+/g, "-") .replace(/\//g, "_") .replace(/=+$/, ""); }; // URL安全的Base64解码 const decodeBase64ToObj = <T>(str: string): T | null => { try { const restored = str.replace(/-/g, "+").replace(/_/g, "/"); const padding = restored.length % 4; const padded = padding ? restored + "=".repeat(4 - padding) : restored; return JSON.parse(atob(padded)) as T; } catch { return null; } };
使用示例:
const setFilters = (filters: typeof filters) => { setSearchParams({ filters: encodeObjToBase64(filters) }, { replace: true }); }; const getFilters = () => { const encoded = searchParams.get("filters"); return encoded ? decodeBase64ToObj<typeof filters>(encoded) : null; };
内容的提问来源于stack exchange,提问作者Crocsx
相关产品推荐
相关产品推荐

