选项
配置 nuqs
默认情况下,nuqs 将更新搜索参数:
- 仅在客户端(不向服务器发送请求),
- 通过替换当前历史记录条目,
- 不滚动到页面顶部。
- 使用适应您浏览器的节流速率
这些默认行为可以配置, 以及一些额外的选项。
传递选项
选项可以通过构建器模式在钩子级别传递:
const [state, setState] = useQueryState(
'foo',
parseAsString.withOptions({ history: 'push' })
)或者在调用状态更新函数时,作为第二个参数传递:
setState('foo', { scroll: true })调用级别的选项将覆盖钩子级别的选项。
History
默认情况下,状态更新通过替换当前历史记录条目来完成,使用更新后的查询参数当状态发生变化时。
您可以将此视为一种 git squash,其中所有状态更改操作都会合并到一个单一的浏览历史记录条目中。
您也可以选择为每个状态更改推送一个新的历史记录条目,按键分组,这将允许您使用后退按钮来导航状态更新:
// 将状态更改追加到历史记录:
useQueryState('foo', { history: 'push' })注意!
破坏后退按钮可能会导致糟糕的用户体验。请确保仅在要更新的搜索参数有助于导航式体验(例如:选项卡、模态框)时使用此选项。覆盖后退行为必须是 UX 增强,而不是干扰。
— “能力越大,责任越大。”
Shallow
默认情况下,查询状态更新以 client-first 方式进行:没有向服务器发起网络调用。
这相当于 Next.js 路由器的 shallow 选项设置为 true。
要选择通知服务器查询更新,您可以将 shallow 设置为 false:
useQueryState('foo', { shallow: false })请注意,shallow 选项只有在您的页面可以进行服务器端渲染时才有意义。 因此,在 React SPA 中它没有效果。
对于支持服务器端渲染的框架,您可以将 shallow: false 与以下内容配对:
- 在 Next.js app router 中:使用
searchParams页面 prop 来基于更新后的查询状态渲染 RSC 树。 - 在 Next.js pages router 中:
getServerSideProps函数 - 在 Remix 和 React Router 中:
loader函数
在基于 React Router 的框架中
虽然 shallow: true 默认行为在 Remix 和 React Router 中不常见,在这些框架中加载器总是应该在 URL 更改时运行,但 nuqs 让您控制此行为,通过选择仅在需要访问相关搜索参数时运行加载器。
一个注意事项是,这些框架的标准 useSearchParams 钩子不会反映浅更新后的搜索参数,因此我们为您提供了一个会反映的钩子:
import { useOptimisticSearchParams } from 'nuqs/adapters/remix' // 或 '…/react-router/v6' 或 '…/react-router/v7'
function Component() {
// 注意:这是只读的,但对所有 URL 更改有反应
const searchParams = useOptimisticSearchParams()
return <div>{searchParams.get('foo')}</div>
}这种 “浅路由” 的概念是通过更新浏览器的 History API 来实现的。
为什么不使用 shouldRevalidate?
shouldRevalidate
是选择不运行加载器进行导航的惯用方式,但 nuqs 使用相反的方法:仅在需要时选择运行加载器。
为了避免为每个路由指定 shouldRevalidate,nuqs 选择修补历史方法,以在基于 React Router 的框架中默认启用浅路由(在其自身更新上)。
Scroll
Next.js 路由器在导航更新时会滚动到页面顶部,这在用本地状态更新查询字符串时可能不是理想的。
查询状态更新默认不会滚动到页面顶部,但您可以选择启用此行为:
useQueryState('foo', { scroll: true })Rate-limiting URL updates
由于浏览器对 History API 进行速率限制,对 URL 的更新会被排队并节流到默认 50ms,这似乎能满足大多数浏览器,即使发送高频查询更新,比如绑定到文本输入或滑块。
Safari 的速率限制更严格,使用默认节流 120ms (旧版 Safari 为 320ms)。
注意
钩子返回的状态总是即时更新,以保持 UI 响应性。
只有对 URL 的更改,以及在使用 shallow: false 时发送到服务器的请求,才会被节流或防抖。
这个 throttle 时间是可配置的,并且还允许您 debounce 更新 而不是节流。
我应该使用哪一个?
节流会立即发出第一个更新,然后以较慢的节奏定期批量更新。这适用于大多数低频更新,推荐使用。
防抖会推迟 URL 更新时刻,当您设置状态时,使其最终一致。这适用于高频更新,最后的值比中间值更有趣,比如搜索输入或移动滑块。
阅读更多关于 debounce vs throttle 的内容。
Throttle
如果您想增加节流时间——例如,与 shallow: false 配对时减少发送到服务器的请求数量——您可以在 limitUrlUpdates 选项下指定它:
useQueryState('foo', {
// 每秒最多向服务器发送一次更新
shallow: false,
limitUrlUpdates: {
method: 'throttle',
timeMs: 1000
}
})
// 或者使用简写:
import { throttle } from 'nuqs'
useQueryState('foo', {
shallow: false,
limitUrlUpdates: throttle(1000)
})如果多个钩子在同一事件循环滴答上设置不同的节流值, 将使用最高的值。而且,小于 50ms 的值将被忽略, 以避免速率限制问题。 阅读更多。
为节流时间指定 +Infinity 值将禁用对
URL 或服务器的更新,但所有 useQueryState(s) 钩子仍将更新其内部状态并相互保持同步。
弃用通知
throttleMs 选项已在 nuqs@2.5.0 中弃用,并将在以后的主要升级中移除。
迁移步骤:
import { throttle } from 'nuqs'- 将
{ throttleMs: 100 }替换为{ limitUrlUpdates: throttle(100) }在您的选项中。
Debounce
我需要防抖吗?
防抖仅适用于 服务器端数据获取 (RSC 和加载器,当与 shallow: false 结合时),
以控制何时向服务器发送请求。例如:它可以让您避免在搜索输入中输入第一个字符时单独发送,通过等待用户输入完成。
如果您是 客户端获取 (例如:使用 TanStack Query),您将想要防抖钩子返回的状态(使用第三方 useDebounce 实用钩子)。
除了节流,您还可以将防抖机制应用于 URL 更新, 以延迟 URL 使用最新值更新的时刻。
返回的状态总是立即更新:只有发送到服务器的网络请求会被防抖。
这对于服务器仅关心最终值而非输入搜索或移动滑块期间所有中间值的高频状态更新很有用。
我们推荐您选择在特定状态更新上启用防抖,而不是 为整个搜索参数定义它。
让我们以搜索输入为例。您将想要更新它:
- 当用户输入文本时,使用防抖
- 当用户清空输入时,通过发送即时更新
- 当用户按 Enter 时,通过发送即时更新
您可以看到防抖情况是这里的异常,并且实际上取决于 设置的值,因此我们可以使用状态更新函数来指定它:
import { useQueryState, parseAsString, debounce } from 'nuqs';
function Search() {
const [search, setSearch] = useQueryState(
'q',
parseAsString
.withDefault('')
.withOptions({ shallow: false })
)
return (
<input
value={search}
onChange={(e) =>
setSearch(e.target.value, {
// 如果重置则发送即时更新,否则 500ms 防抖
limitUrlUpdates: e.target.value === '' ? undefined : debounce(500)
})
}
onKeyPress={(e) => {
if (e.key === 'Enter') {
// 发送即时更新
setSearch(e.target.value)
}
}}
/>
)
}Resetting
您可以使用 defaultRateLimit 导入来重置防抖或节流到
默认值:
import { debounce, defaultRateLimit } from 'nuqs'
const [, setState] = useQueryState('foo', {
limitUrlUpdates: debounce(1000)
})
// 此状态更新未被防抖
setState('bar', { limitUrlUpdates: defaultRateLimit })Transitions
当与 shallow: false 结合时,您可以使用 React 的 useTransition 钩子
来获取服务器使用更新后的 URL 重新渲染服务器组件时的加载状态。
将来自 useTransition 的 startTransition 函数传递到选项
中以启用此行为:
'use client'
import React from 'react'
import { useQueryState, parseAsString } from 'nuqs'
function ClientComponent({ data }) {
// 1. 提供您自己的 useTransition 钩子:
const [isLoading, startTransition] = React.useTransition()
const [query, setQuery] = useQueryState(
'query',
// 2. 将 `startTransition` 作为选项传递:
parseAsString.withOptions({ startTransition, shallow: false })
)
// 3. 当通过 `setQuery` 更新查询时,`isLoading` 将在服务器重新渲染
// 和流式传输 RSC 负载时为 true。
// 表示加载状态
if (isLoading) return <div>加载中...</div>
// 使用数据进行正常渲染
return <div>...</div>
}在 nuqs@1.x.x 中,将 startTransition 作为选项传递会自动设置
shallow: false。
在 nuqs@>=2.0.0 中不再是这样:您需要显式设置它。
Clear on default
当状态设置为默认值时,搜索参数会 从 URL 中移除,而不是显式反映。
然而,有时您可能希望保留 URL 中的搜索参数, 因为 默认值 可能 会更改,从而改变 URL 的含义。
默认值更改的示例
在 nuqs@1.x.x 中,clearOnDefault 默认值为 false。
在 nuqs@2.0.0 中,clearOnDefault 现在默认值为 true,以响应
用户反馈。
如果您希望在设置为默认值时保留 URL 中的搜索参数,您可以将 clearOnDefault 设置为 false:
useQueryState('search', {
defaultValue: '',
clearOnDefault: false
})提示
从查询字符串中清除键值对始终可以通过将状态设置为 null 来完成。
此选项使用 ===
引用相等来比较设置的状态与默认值,因此如果您使用 自定义解析器
对于引用相等无效的状态类型,您应该为您的解析器提供
eq 函数(内置解析器已为您完成):
const dateParser = createParser({
parse: (value: string) => new Date(value.slice(0, 10)),
serialize: (date: Date) => date.toISOString().slice(0, 10),
eq: (a: Date, b: Date) => a.getTime() === b.getTime()
})Adapter props
以下选项是全局的,可以直接
传递到 <NuqsAdapter> 作为 prop,并应用于其整个
子树。
Global defaults override
某些选项的默认值可以通过 defaultOptions
适配器 prop 进行全局配置:
<NuqsAdapter
defaultOptions={{
shallow: false,
scroll: true,
clearOnDefault: false,
limitUrlUpdates: throttle(250),
}}
>
{children}
</NuqsAdapter>Processing URLSearchParams
您可以传递一个 processUrlSearchParams 回调到适配器,
它将在执行状态更新时 URLSearchParams 合并后调用,并且 在 它们发送到适配器
用于更新 URL 之前。
将其视为一种处理搜索参数的 中间件。
Alphabetical Sort
按字母顺序排序搜索参数:
<NuqsAdapter
processUrlSearchParams={(search) => {
// 注意:您可以就地修改 search,
// 或返回一个副本,随您喜欢。
search.sort()
return search
}}
>
{children}
</NuqsAdapter>尝试切换 c,然后 b,然后 a,并注意 URL 如何保持有序:
Timestamp
向搜索参数添加时间戳:
<NuqsAdapter
processUrlSearchParams={(search) => {
search.set('ts', Date.now().toString())
return search
}}
>
{children}
</NuqsAdapter>