首页
/ React Router 的 createSearchParams 指南:让 URL 查询参数构造告别重复手写

React Router 的 createSearchParams 指南:让 URL 查询参数构造告别重复手写

2026-09-07 19:28:43作者:宣海椒Queenly

createSearchParams 是 React Router 提供的一个轻量级工具函数,它用于创建一个 URLSearchParams 对象。本文围绕 docs/api/utils/createSearchParams.md 展开,说明它的设计动机、类型签名与底层实现,并演示它如何同 useSearchParamshref 等 React Router API 配合,快速构造可导航的查询字符串。读完本文,你将掌握用「对象字面量 + 数组值」描述多重查询参数的方法,并理解 React Router 内部是如何统一各种查询参数输入形态的。

适用模式:framework / data / declarative(框架模式、数据模式、声明式模式均可使用)。本文对应的官方 API 参考入口在 docs/api/utils/index.md 的 utils 分类中。

一、为什么需要 createSearchParams

浏览器原生提供的 new URLSearchParams(init) 已经足够强大,但当你想用「对象」形式一次性表达一个键对应多个取值时,它并不支持对象值;标准的做法是把每对键值写成二元组放进数组:

let searchParams = new URLSearchParams([
  ["sort", "name"],
  ["sort", "price"],
]);

这里同一键 sort 出现了两次,代码既重复又易错。createSearchParams 的职责就是消除这种重复:它在行为上与 new URLSearchParams(init) 完全一致,唯一的区别是——当传入对象字面量时,它额外允许用数组作为某个键的值,从而表达多重取值。

import { createSearchParams } from "react-router";

let searchParams = createSearchParams({
  sort: ["name", "price"],
});

searchParams.toString(); // "sort=name&sort=price"
searchParams.getAll("sort"); // ["name", "price"]

这段示例正是 React Router 源码 JSDoc 中的原始用例(见 packages/react-router/lib/dom/dom.ts),它直观地说明了本函数存在的全部价值:多值查询参数的可读写法。

二、函数签名与参数说明

createSearchParams 的完整签名如下:

function createSearchParams(init: URLSearchParamsInit = ""): URLSearchParams

参数:init

init 是用于初始化 URL 查询参数的值,它并非原生 string 那么简单,而是 React Router 专门导出的一个联合类型 URLSearchParamsInit。从 packages/react-router/lib/dom/dom.ts 可以看到它的精确定义:

export type ParamKeyValuePair = [string, string];

export type URLSearchParamsInit =
  | string                       // "sort=name&brand=nike"
  | ParamKeyValuePair[]          // [["sort", "name"], ["brand", "nike"]]
  | Record<string, string | string[]>  // { sort: ["name","price"] }
  | URLSearchParams;             // 另一个 URLSearchParams 实例

因此 init 实际上支持四种输入形态:

输入形态 示例 说明
string createSearchParams("tab=1") 与原生字符串初始化行为一致,字符串内部的 ? 不会被特殊处理
键值对数组 createSearchParams([["tab", "1"], ["brand", "nike"]]) 完全等同原生 new URLSearchParams([...])
对象字面量 createSearchParams({ brand: ["nike", "reebok"] }) 字符串或字符串数组作为值,数组即表示多值,这是本函数相对原生的增强点
URLSearchParams createSearchParams(new URLSearchParams("?tab=1")) 拷贝另一个实例

返回值:URLSearchParams

函数始终返回一个包含初始化后查询参数的标准 URLSearchParams 对象,可以继续调用 getgetAllsetappendtoStringforEach 等原生方法。

默认值

当不传 init(或显式传 undefined)时,参数默认值为空字符串 "",即返回一个空的查询参数对象:

createSearchParams().toString(); // ""

三、底层实现:对象如何被摊平成键值对数组

要理解 createSearchParams 的行为边界,值得看一眼它的完整实现(packages/react-router/lib/dom/dom.ts):

export function createSearchParams(
  init: URLSearchParamsInit = "",
): URLSearchParams {
  return new URLSearchParams(
    typeof init === "string" ||
      Array.isArray(init) ||
      init instanceof URLSearchParams
      ? init
      : Object.keys(init).reduce((memo, key) => {
          let value = init[key];
          return memo.concat(
            Array.isArray(value) ? value.map((v) => [key, v]) : [[key, value]],
          );
        }, [] as ParamKeyValuePair[]),
  );
}

这段代码揭示了三条可验证的事实:

  1. 透传策略:当 init 是字符串、键值对数组或 URLSearchParams 实例时,函数直接把 init 原样交给原生构造器 new URLSearchParams,不做任何额外处理,保证与原生行为逐字节一致。
  2. 对象摊平策略:只有当 init 是对象时才会进入 Object.keys(init).reduce(...) 分支。每个键被读取出值后判断:若值是数组,则用 value.map((v) => [key, v]) 把它展开成多组 [key, v] 二元组(键保持不变);否则生成单个 [key, value]。最终把全部二元组合并成一个 ParamKeyValuePair[] 数组,再交给原生构造器。
  3. 顺序稳定性:由于对象使用 Object.keys 遍历、数组 .map 按索引展开,键的先后顺序和数组内取值顺序都会原样保留在最终序列化结果里,这对排序参数(如 sort)这类「顺序有意义」的场景很重要。

一个容易踩的坑是:对象形态下,值为数组的键会以重复出现的形式序列化,因此不要期待它变成 sort=name,price 这种逗号分隔的格式;需要那种格式时,你应该自行 join(",") 并赋一个字符串值。

四、四种 init 形态的完整实操对比

下面把同一组「两个 sort 值」用四种形态各写一遍,结果完全等价:

import { createSearchParams } from "react-router";

// 形态 1:字符串
createSearchParams("sort=name&sort=price");

// 形态 2:键值对数组(与原生一致)
createSearchParams([
  ["sort", "name"],
  ["sort", "price"],
]);

// 形态 3:对象字面量 + 数组值(推荐,可读性最好)
createSearchParams({ sort: ["name", "price"] });

// 形态 4:拷贝其它 URLSearchParams
createSearchParams(new URLSearchParams("sort=name&sort=price"));

四种写法最终都得到同样的对象:toString() 输出 sort=name&sort=pricegetAll("sort") 返回 ["name", "price"]

混合键也很自然——字符串键与多值数组键可以同处一个对象:

let searchParams = createSearchParams({
  brand: ["nike", "reebok"],
  tab: "1",          // 单值键也可以直接给字符串
  featured: "true",
});

searchParams.toString();
// "brand=nike&brand=reebok&tab=1&featured=true"

五、在 useSearchParams 与导航场景中配合使用

createSearchParams 最常见的用途并不是被单独调用,而是作为 React Router 查询参数体系的统一入口,内嵌在 useSearchParams 的默认值与更新逻辑中(实现见 packages/react-router/lib/dom/lib.tsx)。

useSearchParams 的签名是:

useSearchParams(defaultInit?: URLSearchParamsInit): [URLSearchParams, SetURLSearchParams]

可见其 defaultInit 参数的类型正是 URLSearchParamsInit——这意味着你在初始化默认值时,同样能享受对象数组值语法:

import { useSearchParams } from "react-router";

// 对象形式的多值默认参数
let [searchParams] = useSearchParams({
  sort: ["name", "price"],
});

// 其它合法写法,见源码 JSDoc 示例
// useSearchParams("?tab=1");
// useSearchParams({ tab: "1" });
// useSearchParams({ brand: ["nike", "reebok"] });
// useSearchParams([["tab", "1"]]);
// useSearchParams(new URLSearchParams("?tab=1"));

在源码层面,useSearchParams 是这样消费 createSearchParams 的(packages/react-router/lib/dom/lib.tsx):

let defaultSearchParamsRef = React.useRef(createSearchParams(defaultInit));
let hasSetSearchParamsRef = React.useRef(false);

let location = useLocation();
let searchParams = React.useMemo(
  () =>
    getSearchParamsForLocation(
      location.search,
      hasSetSearchParamsRef.current ? null : defaultSearchParamsRef.current,
    ),
  [location.search],
);

这里可以看到两条值得注意的工程细节:

  • 默认值在首次渲染被立即转换URLSearchParams 并存进 useRef,之后每次渲染都复用同一实例,避免重复构建。
  • 一旦调用过 setSearchParams,后续就会以 null 替代默认值参与合并,这正是为了让 setSearchParams({}) 能真正清空参数——否则默认值会不断被合并回来。相关的合并逻辑 getSearchParamsForLocation 也定义在 packages/react-router/lib/dom/dom.ts,它只会把「当前 URL 中不存在的键」用默认值补上,已存在的键以 URL 实际值为准。

setSearchParamscreateSearchParams 的关系同样直接。setSearchParams 的类型(packages/react-router/lib/dom/lib.tsx)是:

export type SetURLSearchParams = (
  nextInit?:
    | URLSearchParamsInit
    | ((prev: URLSearchParams) => URLSearchParamsInit),
  navigateOpts?: NavigateOptions,
) => void;

而在它的实现里(packages/react-router/lib/dom/lib.tsx),createSearchParams 承担了把各种输入归一化的职责,随后调用 navigate("?" + newSearchParams, navigateOptions) 触发一次带查询字符串的导航:

let setSearchParams = React.useCallback<SetURLSearchParams>(
  (nextInit, navigateOptions) => {
    const newSearchParams = createSearchParams(
      typeof nextInit === "function"
        ? nextInit(new URLSearchParams(searchParams))
        : nextInit,
    );
    hasSetSearchParamsRef.current = true;
    navigate("?" + newSearchParams, navigateOptions);
  },
  [navigate, searchParams],
);

因此,只要牢记 createSearchParams 支持的四种输入形态,也就等于掌握了 setSearchParamsuseSearchParams(defaultInit) 的完整输入语法。一个将二者结合的分页筛选示例:

import { useSearchParams } from "react-router";

function ProductFilters() {
  const [searchParams, setSearchParams] = useSearchParams();

  // 读取多值:brand 可能同时命中多个
  const brands = searchParams.getAll("brand");

  function toggleBrand(brand: string) {
    // 以函数形式基于上一状态改写,避免并发更新下互相覆盖
    setSearchParams((prev) => {
      const next = new URLSearchParams(prev);
      const list = next.getAll("brand");
      if (list.includes(brand)) {
        next.delete("brand");
        list.filter((b) => b !== brand).forEach((b) => next.append("brand", b));
      } else {
        next.append("brand", brand);
      }
      return next;
    });
  }

  // 一键重置:传对象形态,允许 set 覆盖
  function reset() {
    setSearchParams({ tab: "1" }, { preventScrollReset: true });
  }

  return (
    <div>
      <p>当前选中:{brands.join(", ")}</p>
      <button onClick={() => toggleBrand("nike")}>切换 nike</button>
      <button onClick={reset}>重置</button>
    </div>
  );
}

六、结合 href / useHref 使用

createSearchParams 返回的 URLSearchParams 也可直接与 useHrefhref 这类链接构造工具配合,先生成带查询参数的完整 href,再传给 Link 等组件。完整的 hooks 与 utils 用法可参考 docs/api/hooks/useSearchParams.mddocs/api/hooks/useHref.mddocs/api/hooks/useLinkClickHandler.md

import { createSearchParams } from "react-router";
import type { LinkProps } from "react-router";

// 在组件外构造:多值查询参数的可读对象写法
const resultsHref = {
  pathname: "/products",
  search: createSearchParams({ sort: ["price", "name"] }).toString(),
};

function ProductsLink(props: Partial<LinkProps>) {
  return <Link to={resultsHref} {...props}>查看排序结果</Link>;
}

七、可用的导出入口与使用限制

createSearchParams 与类型 URLSearchParamsInit 从包的主入口统一导出。导出语句位于 packages/react-router/index.ts

export { createSearchParams } from "./lib/dom/dom";

使用时只需:

import { createSearchParams, type URLSearchParamsInit } from "react-router";

需要留意的限制有两点:

  1. 依赖浏览器 URLSearchParams:函数最终依赖原生 URLSearchParams。源码中 useSearchParams 对此有明确的告警提示(packages/react-router/lib/dom/lib.tsx):若运行环境(如 IE11)不支持该 API,需要引入 polyfill 才能正常使用。
  2. 对象值的可接受类型:对象形态只接受 stringstring[] 作为值,URLSearchParamsInit 的类型定义已经给出约束;传入非字符串内容(如数字)时,类型检查会直接报错。需要数字时请先显式转换:
createSearchParams({ page: String(2) }); // "page=2"

小结

  • createSearchParams 在行为上与 new URLSearchParams 保持一致,并额外支持「对象 + 数组值」这一多值查询参数的高可读写法。
  • 其入参 URLSearchParamsInit 覆盖字符串、键值对数组、对象、URLSearchParams 四种形态;实现层面只对对象形态做数组值摊平,其余原样透传。
  • 它同时是 useSearchParams(defaultInit)setSearchParams(nextInit) 的统一归一化入口,掌握它就等于掌握了 React Router 查询参数体系的输入语法,也可用于独立构造带查询串的 href。

想进一步了解 URL 与搜索参数的更多用法,可继续阅读 docs/api/utils/index.md 下的 parsePathresolvePathgeneratePathuseSearchParams 等相邻文档,或在 packages/react-router/lib/dom/dom.ts 中查看完整实现。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388