迁移到 v2 的指南

如何更新您的代码以使用 nuqs@2.0.0

这是 nuqs@2.0.0 中的重大变更总结:

Adapters

最大的变化是 nuqs@2.0.0 现在支持其他 React 框架, 为所有框架提供类型安全的 URL 状态。

您需要用适用于您的框架或路由器的适当 adapter 包装您的应用,以让钩子知道如何与之交互。

当前可用的适配器包括:

  • Next.js(app 和 pages 路由器)
  • React SPA
  • Remix
  • React Router
  • 测试环境(Vitest、Jest 等)

如果您是从仅支持 Next.js 的 nuqs v1 迁移,您需要 用适当的 NuqsAdapter 包装您的应用:

Next.js

最低要求版本:next@>=14.2.0

Next.js 14 的早期版本在浅路由方面处于变动中。 支持那些早期版本需要大量黑客技巧、工作绕道和 性能惩罚,这些在 nuqs@2.0.0 中已被移除。

App 路由器

src/app/layout.tsx
import { NuqsAdapter } from 'nuqs/adapters/next/app'
import { type ReactNode } from 'react'

export default function RootLayout({
  children
}: {
  children: ReactNode
}) {
  return (
    <html>
      <body>
        <NuqsAdapter>{children}</NuqsAdapter>
      </body>
    </html>
  )
}

Pages 路由器

src/pages/_app.tsx
import type { AppProps } from 'next/app'
import { NuqsAdapter } from 'nuqs/adapters/next/pages'

export default function MyApp({ Component, pageProps }: AppProps) {
  return (
    <NuqsAdapter>
      <Component {...pageProps} />
    </NuqsAdapter>
  )
}

统一(路由无关)

如果您的 Next.js 应用同时使用 app 和 pages 路由器,并且适配器需要 在其中一个中挂载,您可以导入统一适配器,但会以 略微增加捆绑包大小(~100B)为代价。

import { NuqsAdapter } from 'nuqs/adapters/next'

其他适配器

虽然不是从 v1 迁移的一部分,但您现在可以通过各自的 adapters 在其他 React 框架中使用 nuqs。

然而,还有一个可能对您感兴趣的适配器,它解决了 使用 nuqs 钩子测试组件的长期问题:

测试适配器

单元测试使用 nuqs v1 的组件很麻烦,因为它需要模拟 Next.js 路由器内部,导致抽象泄漏。

在 v2 中,您现在可以用 NuqsTestingAdapter 包装您的组件进行测试, 它提供了方便的设置和断言 API,用于您的测试。

以下是使用 Vitest 和 Testing Library 的示例:

counter-button-example.test.tsx
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { NuqsTestingAdapter, type UrlUpdateEvent } from 'nuqs/adapters/testing'
import { describe, expect, it, vi } from 'vitest'
import { CounterButton } from './counter-button'

it('should increment the count when clicked', async () => {
  const user = userEvent.setup()
  const onUrlUpdate = vi.fn<[UrlUpdateEvent]>()
  render(<CounterButton />, {
    // Setup the test by passing initial search params / querystring:
    wrapper: ({ children }) => (
      <NuqsTestingAdapter searchParams="?count=1" onUrlUpdate={onUrlUpdate}>
        {children}
      </NuqsTestingAdapter>
    )
  })
  // Act
  const button = screen.getByRole('button')
  await user.click(button)
  // Assert changes in the state and in the (mocked) URL
  expect(button).toHaveTextContent('count is 2')
  expect(onUrlUpdate).toHaveBeenCalledOnce()
  expect(onUrlUpdate.mock.calls[0][0].queryString).toBe('?count=2')
  expect(onUrlUpdate.mock.calls[0][0].searchParams.get('count')).toBe('2')
  expect(onUrlUpdate.mock.calls[0][0].options.history).toBe('push')
})

Behaviour changes

设置 startTransition 选项不再自动设置 shallow: false。 这是为了与其他没有 浅/深路由概念的框架保持一致。

您需要同时设置两者,以继续向服务器发送更新并在 Next.js 中获取加载 状态:

useQueryState('q', {
  startTransition: true,
+ shallow: false
})

"use client" 指令未包含在客户端导入 (import {} from 'nuqs')中。现在已添加,这意味着服务器端代码 需要从 nuqs/server 导入,以避免如下错误:

Error: Attempted to call withDefault() from the server but withDefault is on
the client. It's not possible to invoke a client function from the server, it can
only be rendered as a Component or passed to props of a Client
Component.

ESM only

nuqs@2.0.0 现在是一个 ESM-only 包。这应该不是大问题,因为 Next.js 从版本 12 开始支持 ESM 在 应用代码中使用,但如果您将 nuqs 代码捆绑到一个 中间 CJS 库中以供 Next.js 使用,您会遇到导入问题:

[ERR_REQUIRE_ESM]: require() of ES Module not supported

如果将您的库转换为 ESM 不可行,您的主要选项是 动态导入 nuqs

const { useQueryState } = await import('nuqs')

Deprecated exports

v1 API 中的一些部分早在 2023 年 9 月就被标记为已弃用,并在 nuqs@2.0.0 中移除。

queryTypes parsers 对象

queryTypes 对象已被移除,转而使用单个解析器导出, 以实现更好的 tree-shaking。

parseAsXYZ 替换以匹配:

- import { queryTypes } from 'nuqs'
+ import { parseAsString, parseAsInteger, ... } from 'nuqs'

- useQueryState('q',    queryTypes.string.withOptions({ ... }))
- useQueryState('page', queryTypes.integer.withDefault(1))
+ useQueryState('q',    parseAsString.withOptions({ ... }))
+ useQueryState('page', parseAsInteger.withDefault(1))

subscribeToQueryUpdates

Next.js 14.1.0 使 useSearchParams 对浅搜索参数更新具有响应性, 这使得这个内部辅助函数变得多余。参见 #425 以获取上下文。

Renamed nuqs/parsers to nuqs/server

在引入服务器缓存 #397 时,专用的解析器导出被 重用,因为它不包含 "use client" 指令。由于它现在包含 不止解析器,并且将来可能会扩展为服务器端代码, 它已被重命名为更清晰的导出名称。

在您的代码中查找并替换所有 nuqs/parsers 的出现为 nuqs/server

- import { parseAsInteger, createSearchParamsCache } from 'nuqs/parsers'
+ import { parseAsInteger, createSearchParamsCache } from 'nuqs/server'

Debug printout detection

重命名为 nuqs 后,调试输出检测逻辑处理 localStorage.debug 变量中存在 next-usequerystatenuqsnuqs@2.0.0 仅检查 nuqs 子串的存在以启用日志。

通过在开发工具控制台中运行以下代码一次,来更新您的本地开发环境以匹配:

if (localStorage.debug) {
  localStorage.debug = localStorage.debug.replace('next-usequerystate', 'nuqs')
}

Dropping next-usequerystate

此包最初名为 next-usequerystate,并于 2024 年 1 月 重命名为 nuqs。旧包名作为 v1 发布线的别名保留。

nuqs 版本 2 及更高版本不再镜像到 next-usequerystate 包名。

Type changes

以下重大变更仅适用于导出的类型:

  • Options 类型不再是泛型的
  • UseQueryStatesOptions 现在是一个类型而不是接口,并且现在 是您传递给 useQueryStates 的对象的泛型。
  • parseAsJson 现在需要一个运行时 验证函数来推断解析的 JSON 数据类型。