就像 TanStack Query 让在 React 和 Solid 应用程序中处理服务器状态变得轻而易举一样,TanStack Router 旨在释放应用程序中 URL 搜索参数的强大功能。
🧠 如果您使用的是非常旧的浏览器,如 IE11,您可能需要为 URLSearchParams 使用 polyfill。
我们理解,您最近听到了很多"使用平台"的说法,在大多数情况下,我们同意这一点。但是,我们也认为重要的是要认识到平台在更高级用例中的不足之处,我们认为 URLSearchParams 就是这些情况之一。
传统的搜索参数 API 通常假设几件事:
然而,现实与这些假设非常不同。
您可能在 URL 中看过像 ?page=3 或 ?filter-name=tanner 这样的搜索参数。毫无疑问,这确实是生活在 URL 内部的一种全局状态形式。在 URL 中存储特定状态片段是有价值的,因为:
为了实现上述目标,TanStack Router 内置的第一步是一个强大的搜索参数解析器,它自动将 URL 的搜索字符串转换为结构化的 JSON。这意味着您可以在搜索参数中存储任何 JSON 可序列化的数据结构,它将被解析和序列化为 JSON。这是对 URLSearchParams 的巨大改进,后者对类数组结构和嵌套数据的支持有限。
例如,导航到以下路由:
const link = (
<Link
to="/shop"
search={{
pageIndex: 3,
includeCategories: ['electronics', 'gifts'],
sortBy: 'price',
desc: true,
}}
/>
)
const link = (
<Link
to="/shop"
search={{
pageIndex: 3,
includeCategories: ['electronics', 'gifts'],
sortBy: 'price',
desc: true,
}}
/>
)
将产生以下 URL:
/shop?pageIndex=3&includeCategories=%5B%22electronics%22%2C%22gifts%22%5D&sortBy=price&desc=true
/shop?pageIndex=3&includeCategories=%5B%22electronics%22%2C%22gifts%22%5D&sortBy=price&desc=true
当解析此 URL 时,搜索参数将被准确地转换回以下 JSON:
{
"pageIndex": 3,
"includeCategories": ["electronics", "gifts"],
"sortBy": "price",
"desc": true
}
{
"pageIndex": 3,
"includeCategories": ["electronics", "gifts"],
"sortBy": "price",
"desc": true
}
如果您注意到,这里发生了几件事:
🧠 其他工具通常假设搜索参数总是扁平的和基于字符串的,这就是为什么我们选择在第一级保持 URLSearchParam 兼容的原因。这最终意味着即使 TanStack Router 将您的嵌套搜索参数作为 JSON 管理,其他���具仍然能够正常写入 URL 和读取第一级参数。
尽管 TanStack Router 能够将搜索参数解析为可靠的 JSON,但它们最终仍然来自面向用户的原始文本输入。与其他序列化边界类似,这意味着在您使用搜索参数之前,应该将它们验证为您的应用程序可以信任和依赖的格式。
TanStack Router 提供了用于验证和类型化搜索参数的便捷 API。这一切都从 Route 的 validateSearch 选项开始:
// /routes/shop.products.tsx
type ProductSearchSortOptions = 'newest' | 'oldest' | 'price'
type ProductSearch = {
page: number
filter: string
sort: ProductSearchSortOptions
}
export const Route = createFileRoute('/shop/products')({
validateSearch: (search: Record<string, unknown>): ProductSearch => {
// validate and parse the search params into a typed state
return {
page: Number(search?.page ?? 1),
filter: (search.filter as string) || '',
sort: (search.sort as ProductSearchSortOptions) || 'newest',
}
},
})
// /routes/shop.products.tsx
type ProductSearchSortOptions = 'newest' | 'oldest' | 'price'
type ProductSearch = {
page: number
filter: string
sort: ProductSearchSortOptions
}
export const Route = createFileRoute('/shop/products')({
validateSearch: (search: Record<string, unknown>): ProductSearch => {
// validate and parse the search params into a typed state
return {
page: Number(search?.page ?? 1),
filter: (search.filter as string) || '',
sort: (search.sort as ProductSearchSortOptions) || 'newest',
}
},
})
在上面的示例中,我们正在验证 Route 的搜索参数并返回一个类型化的 ProductSearch 对象。然后,这个类型化对象可供此路由的其���选项以及任何子路由使用!
validateSearch 选项是一个函数,它接收 JSON 解析的(但未验证的)搜索参数作为 Record<string, unknown>,并返回您选择的类型化对象。通常最好为格式错误或意外的搜索参数提供合理的回退,以便您的用户体验保持不中断。
这是一个示例:
// /routes/shop.products.tsx
type ProductSearchSortOptions = 'newest' | 'oldest' | 'price'
type ProductSearch = {
page: number
filter: string
sort: ProductSearchSortOptions
}
export const Route = createFileRoute('/shop/products')({
validateSearch: (search: Record<string, unknown>): ProductSearch => {
// validate and parse the search params into a typed state
return {
page: Number(search?.page ?? 1),
filter: (search.filter as string) || '',
sort: (search.sort as ProductSearchSortOptions) || 'newest',
}
},
})
// /routes/shop.products.tsx
type ProductSearchSortOptions = 'newest' | 'oldest' | 'price'
type ProductSearch = {
page: number
filter: string
sort: ProductSearchSortOptions
}
export const Route = createFileRoute('/shop/products')({
validateSearch: (search: Record<string, unknown>): ProductSearch => {
// validate and parse the search params into a typed state
return {
page: Number(search?.page ?? 1),
filter: (search.filter as string) || '',
sort: (search.sort as ProductSearchSortOptions) || 'newest',
}
},
})
这是一个使用 Zod 库(但您可以随意使用任何您想要的验证库)在单个步骤中验证和类型化搜索参数的示例:
// /routes/shop.products.tsx
import { z } from 'zod'
const productSearchSchema = z.object({
page: z.number().catch(1),
filter: z.string().catch(''),
sort: z.enum(['newest', 'oldest', 'price']).catch('newest'),
})
type ProductSearch = z.infer<typeof productSearchSchema>
export const Route = createFileRoute('/shop/products')({
validateSearch: (search) => productSearchSchema.parse(search),
})
// /routes/shop.products.tsx
import { z } from 'zod'
const productSearchSchema = z.object({
page: z.number().catch(1),
filter: z.string().catch(''),
sort: z.enum(['newest', 'oldest', 'price']).catch('newest'),
})
type ProductSearch = z.infer<typeof productSearchSchema>
export const Route = createFileRoute('/shop/products')({
validateSearch: (search) => productSearchSchema.parse(search),
})
因为 validateSearch 也接受具有 parse 属性的对象,这可以简化为:
validateSearch: productSearchSchema
validateSearch: productSearchSchema
在上面的示例中,我们使用了 Zod 的 .catch() 修饰符而不是 .default() 来避免向用户显示错误,因为我们坚信如果搜索参数格式错误,您可能不希望停止用户在应用程序中的体验来显示一个大的错误消息。也就是说,有时您确实想要显示错误消息。在这种情况下,您可以使用 .default() 而不是 .catch()。
这种工作原理的底层机制依赖于 validateSearch 函数抛出错误。如果抛出错误,路由的 onError 选项将被触发(并且 error.routerCode 将被设置为 VALIDATE_SEARCH,errorComponent 将被渲染而不是路由的 component,您可以在其中以任何您喜欢的方式处理搜索参数错误。
当使用像 Zod 这样的库来验证搜索参数时,您可能希望在将搜索参数提交到 URL 之前 transform 搜索参数。例如,常见的 zod transform 是 default。
import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'
const productSearchSchema = z.object({
page: z.number().default(1),
filter: z.string().default(''),
sort: z.enum(['newest', 'oldest', 'price']).default('newest'),
})
export const Route = createFileRoute('/shop/products/')({
validateSearch: productSearchSchema,
})
import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'
const productSearchSchema = z.object({
page: z.number().default(1),
filter: z.string().default(''),
sort: z.enum(['newest', 'oldest', 'price']).default('newest'),
})
export const Route = createFileRoute('/shop/products/')({
validateSearch: productSearchSchema,
})
当您尝试导航到此路由时,search 是必需的,这可能令人惊讶。以下 Link 将出现类型错误,因为缺少 search。
<Link to="/shop/products" />
<Link to="/shop/products" />
对于验证库,我们建议使用适配器,它们推断正确的 input 和 output 类型。
为 Zod 提供了一个适配器,它将传递正确的 input 类型和 output 类型
import { createFileRoute } from '@tanstack/react-router'
import { zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'
const productSearchSchema = z.object({
page: z.number().default(1),
filter: z.string().default(''),
sort: z.enum(['newest', 'oldest', 'price']).default('newest'),
})
export const Route = createFileRoute('/shop/products/')({
validateSearch: zodValidator(productSearchSchema),
})
import { createFileRoute } from '@tanstack/react-router'
import { zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'
const productSearchSchema = z.object({
page: z.number().default(1),
filter: z.string().default(''),
sort: z.enum(['newest', 'oldest', 'price']).default('newest'),
})
export const Route = createFileRoute('/shop/products/')({
validateSearch: zodValidator(productSearchSchema),
})
这里的重要部分是以下 Link 的使用不再需要 search 参数
<Link to="/shop/products" />
<Link to="/shop/products" />
然而,这里使用 catch 会覆盖类型并使 page、filter 和 sort 变为 unknown,导致类型丢失。我们通过提供一个 fallback 泛型函数来处理这种情况,它保留类型但在验证失败时提供 fallback 值
import { createFileRoute } from '@tanstack/react-router'
import { fallback, zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'
const productSearchSchema = z.object({
page: fallback(z.number(), 1).default(1),
filter: fallback(z.string(), '').default(''),
sort: fallback(z.enum(['newest', 'oldest', 'price']), 'newest').default(
'newest',
),
})
export const Route = createFileRoute('/shop/products/')({
validateSearch: zodValidator(productSearchSchema),
})
import { createFileRoute } from '@tanstack/react-router'
import { fallback, zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'
const productSearchSchema = z.object({
page: fallback(z.number(), 1).default(1),
filter: fallback(z.string(), '').default(''),
sort: fallback(z.enum(['newest', 'oldest', 'price']), 'newest').default(
'newest',
),
})
export const Route = createFileRoute('/shop/products/')({
validateSearch: zodValidator(productSearchSchema),
})
因此,当导航到此路由时,search 是可选的并保留正确的类型。
虽然不推荐,但也可以配置 input 和 output 类型,以防 output 类型比 input 类型更准确
const productSearchSchema = z.object({
page: fallback(z.number(), 1).default(1),
filter: fallback(z.string(), '').default(''),
sort: fallback(z.enum(['newest', 'oldest', 'price']), 'newest').default(
'newest',
),
})
export const Route = createFileRoute('/shop/products/')({
validateSearch: zodValidator({
schema: productSearchSchema,
input: 'output',
output: 'input',
}),
})
const productSearchSchema = z.object({
page: fallback(z.number(), 1).default(1),
filter: fallback(z.string(), '').default(''),
sort: fallback(z.enum(['newest', 'oldest', 'price']), 'newest').default(
'newest',
),
})
export const Route = createFileRoute('/shop/products/')({
validateSearch: zodValidator({
schema: productSearchSchema,
input: 'output',
output: 'input',
}),
})
这提供了您想要为导航推断哪种类型以及您想要为读取搜索参数推断哪些类型的灵活性。
Warning
Router 期望安装 valibot 1.0 包。
当使用 Valibot 时,不需要适配器来确保为导航和读取搜索参数使用正确的 input 和 output 类型。这是因为 valibot 实现了 Standard Schema
import { createFileRoute } from '@tanstack/react-router'
import * as v from 'valibot'
const productSearchSchema = v.object({
page: v.optional(v.fallback(v.number(), 1), 1),
filter: v.optional(v.fallback(v.string(), ''), ''),
sort: v.optional(
v.fallback(v.picklist(['newest', 'oldest', 'price']), 'newest'),
'newest',
),
})
export const Route = createFileRoute('/shop/products/')({
validateSearch: productSearchSchema,
})
import { createFileRoute } from '@tanstack/react-router'
import * as v from 'valibot'
const productSearchSchema = v.object({
page: v.optional(v.fallback(v.number(), 1), 1),
filter: v.optional(v.fallback(v.string(), ''), ''),
sort: v.optional(
v.fallback(v.picklist(['newest', 'oldest', 'price']), 'newest'),
'newest',
),
})
export const Route = createFileRoute('/shop/products/')({
validateSearch: productSearchSchema,
})
Warning
Router 期望安装 arktype 2.0-rc 包。
当使用 ArkType 时,不需要适配器来确保为导航和读取搜索参数使用正确的 input 和 output 类型。这是因为 ArkType 实现了 Standard Schema
import { createFileRoute } from '@tanstack/react-router'
import { type } from 'arktype'
const productSearchSchema = type({
page: 'number = 1',
filter: 'string = ""',
sort: '"newest" | "oldest" | "price" = "newest"',
})
export const Route = createFileRoute('/shop/products/')({
validateSearch: productSearchSchema,
})
import { createFileRoute } from '@tanstack/react-router'
import { type } from 'arktype'
const productSearchSchema = type({
page: 'number = 1',
filter: 'string = ""',
sort: '"newest" | "oldest" | "price" = "newest"',
})
export const Route = createFileRoute('/shop/products/')({
validateSearch: productSearchSchema,
})
当使用 Effect/Schema 时,不需要适配器来确保为导航和读取搜索参数使用正确的 input 和 output 类型。这是因为 Effect/Schema 实现了 Standard Schema
import { createFileRoute } from '@tanstack/react-router'
import { Schema as S } from 'effect'
const productSearchSchema = S.standardSchemaV1(
S.Struct({
page: S.NumberFromString.pipe(
S.optional,
S.withDefaults({
constructor: () => 1,
decoding: () => 1,
}),
),
filter: S.String.pipe(
S.optional,
S.withDefaults({
constructor: () => '',
decoding: () => '',
}),
),
sort: S.Literal('newest', 'oldest', 'price').pipe(
S.optional,
S.withDefaults({
constructor: () => 'newest' as const,
decoding: () => 'newest' as const,
}),
),
}),
)
export const Route = createFileRoute('/shop/products/')({
validateSearch: productSearchSchema,
})
import { createFileRoute } from '@tanstack/react-router'
import { Schema as S } from 'effect'
const productSearchSchema = S.standardSchemaV1(
S.Struct({
page: S.NumberFromString.pipe(
S.optional,
S.withDefaults({
constructor: () => 1,
decoding: () => 1,
}),
),
filter: S.String.pipe(
S.optional,
S.withDefaults({
constructor: () => '',
decoding: () => '',
}),
),
sort: S.Literal('newest', 'oldest', 'price').pipe(
S.optional,
S.withDefaults({
constructor: () => 'newest' as const,
decoding: () => 'newest' as const,
}),
),
}),
)
export const Route = createFileRoute('/shop/products/')({
validateSearch: productSearchSchema,
})
一旦您的搜索参数已经被验证和类型化,您就可以开始读取和写入它们了。在 TanStack Router 中有几种方法可以做到这一点,让我们来看看它们。
请阅读加载器中的搜索参数部分,了解如何使用 loaderDeps 选项在加载器中读取搜索参数的更多信息。
当您沿着路由树向下时,父级的搜索参数和类型会被合并,因此子路由也可以访问其父级的搜索参数:
const productSearchSchema = z.object({
page: z.number().catch(1),
filter: z.string().catch(''),
sort: z.enum(['newest', 'oldest', 'price']).catch('newest'),
})
type ProductSearch = z.infer<typeof productSearchSchema>
export const Route = createFileRoute('/shop/products')({
validateSearch: productSearchSchema,
})
const productSearchSchema = z.object({
page: z.number().catch(1),
filter: z.string().catch(''),
sort: z.enum(['newest', 'oldest', 'price']).catch('newest'),
})
type ProductSearch = z.infer<typeof productSearchSchema>
export const Route = createFileRoute('/shop/products')({
validateSearch: productSearchSchema,
})
export const Route = createFileRoute('/shop/products/$productId')({
beforeLoad: ({ search }) => {
search
// ^? ProductSearch ✅
},
})
export const Route = createFileRoute('/shop/products/$productId')({
beforeLoad: ({ search }) => {
search
// ^? ProductSearch ✅
},
})
您可以通过 useSearch 钩子在路由的 component 中访问路由的已验证搜索参数。
// /routes/shop.products.tsx
export const Route = createFileRoute('/shop/products')({
validateSearch: productSearchSchema,
})
const ProductList = () => {
const { page, filter, sort } = Route.useSearch()
return <div>...</div>
}
// /routes/shop.products.tsx
export const Route = createFileRoute('/shop/products')({
validateSearch: productSearchSchema,
})
const ProductList = () => {
const { page, filter, sort } = Route.useSearch()
return <div>...</div>
}
Tip
如果您的组件是代码分割的,您可以使用 getRouteApi 函数 来避免必须导入 Route 配置以获得对类型化 useSearch() 钩子的访问。
您可以使用 useSearch 钩子在应用程序的任何地方访问路由的已验证搜索参数。通过传递原始路由的 from id/path,您将获得更好的类型安全性:
// /routes/shop.products.tsx
export const Route = createFileRoute('/shop/products')({
validateSearch: productSearchSchema,
// ...
})
// Somewhere else...
// /components/product-list-sidebar.tsx
const routeApi = getRouteApi('/shop/products')
const ProductList = () => {
const routeSearch = routeApi.useSearch()
// OR
const { page, filter, sort } = useSearch({
from: Route.fullPath,
})
return <div>...</div>
}
// /routes/shop.products.tsx
export const Route = createFileRoute('/shop/products')({
validateSearch: productSearchSchema,
// ...
})
// Somewhere else...
// /components/product-list-sidebar.tsx
const routeApi = getRouteApi('/shop/products')
const ProductList = () => {
const routeSearch = routeApi.useSearch()
// OR
const { page, filter, sort } = useSearch({
from: Route.fullPath,
})
return <div>...</div>
}
或者,您可以通过传递 strict: false 来放宽类型安全性并获得可选的 search 对象:
function ProductList() {
const search = useSearch({
strict: false,
})
// {
// page: number | undefined
// filter: string | undefined
// sort: 'newest' | 'oldest' | 'price' | undefined
// }
return <div>...</div>
}
function ProductList() {
const search = useSearch({
strict: false,
})
// {
// page: number | undefined
// filter: string | undefined
// sort: 'newest' | 'oldest' | 'price' | undefined
// }
return <div>...</div>
}
现在您已经学会了如何读取路由的搜索参数,您会很高兴知道您已经看到了修改和更新它们的主要 API。让我们稍微回顾一下
更新搜索参数的最佳方法是在 <Link /> 组件上使用 search 属性。
如果要更新当前页面的搜索并指定了 from 属性,则可以省略 to 属性。
这是一个示例:
// /routes/shop.products.tsx
export const Route = createFileRoute('/shop/products')({
validateSearch: productSearchSchema,
})
const ProductList = () => {
return (
<div>
<Link from={Route.fullPath} search={(prev) => ({ page: prev.page + 1 })}>
Next Page
</Link>
</div>
)
}
// /routes/shop.products.tsx
export const Route = createFileRoute('/shop/products')({
validateSearch: productSearchSchema,
})
const ProductList = () => {
return (
<div>
<Link from={Route.fullPath} search={(prev) => ({ page: prev.page + 1 })}>
Next Page
</Link>
</div>
)
}
如果您想在多个路由上渲染的通用组件中更新搜索参数,指定 from 可能具有挑战性。
在这种情况下,您可以设置 to=".",这将为您提供对松散类型搜索参数的访问。
这是一个说明这一点的示例:
// `page` 是在 __root 路由中定义的搜索参数,因此在所有路由上都可用。
const PageSelector = () => {
return (
<div>
<Link to="." search={(prev) => ({ ...prev, page: prev.page + 1 })}>
Next Page
</Link>
</div>
)
}
// `page` 是在 __root 路由中定义的搜索参数,因此在所有路由上都可用。
const PageSelector = () => {
return (
<div>
<Link to="." search={(prev) => ({ ...prev, page: prev.page + 1 })}>
Next Page
</Link>
</div>
)
}
如果通用组件仅在路由树的特定子树中渲染,您可以使用 from 指定该子树。如果您愿意,可以省略 to='.'。
// `page` 是在 /posts 路由中定义的搜索参数,因此在其所有子路由上都可用。
const PageSelector = () => {
return (
<div>
<Link
from="/posts"
to="."
search={(prev) => ({ ...prev, page: prev.page + 1 })}
>
Next Page
</Link>
</div>
)
// `page` 是在 /posts 路由中定义的搜索参数,因此在其所有子路由上都可用。
const PageSelector = () => {
return (
<div>
<Link
from="/posts"
to="."
search={(prev) => ({ ...prev, page: prev.page + 1 })}
>
Next Page
</Link>
</div>
)
navigate 函数也接受一个 search 选项,其工作方式与 <Link /> 上的 search 属性相同:
// /routes/shop.products.tsx
export const Route = createFileRoute('/shop/products/$productId')({
validateSearch: productSearchSchema,
})
const ProductList = () => {
const navigate = useNavigate({ from: Route.fullPath })
return (
<div>
<button
onClick={() => {
navigate({
search: (prev) => ({ page: prev.page + 1 }),
})
}}
>
Next Page
</button>
</div>
)
}
// /routes/shop.products.tsx
export const Route = createFileRoute('/shop/products/$productId')({
validateSearch: productSearchSchema,
})
const ProductList = () => {
const navigate = useNavigate({ from: Route.fullPath })
return (
<div>
<button
onClick={() => {
navigate({
search: (prev) => ({ page: prev.page + 1 }),
})
}}
>
Next Page
</button>
</div>
)
}
router.navigate 函数的工作方式与上面的 useNavigate/navigate 钩子/函数完全相同。
<Navigate search /> 组件的工作方式与上面的 useNavigate/navigate 钩子/函数完全相同,但接受其选项作为属性而不是函数参数。
当构建链接 href 时,默认情况下,查询字符串部分唯一重要的是 <Link> 的 search 属性。
TanStack Router 提供了一种通过搜索中间件在生成 href 之前操作搜索参数的方法。 搜索中间件是在为路由或其后代生成新链接时转换搜索参数的函数。 它们也在搜索验证后的导航时执行,以允许操作查询字符串。
以下示例显示了如何确保对于正在构建的每个链接,如果 rootValue 搜索参数是当前搜索参数的一部分,则添加它。如果链接在 search 内指定了 rootValue,则该值用于构建链接。
import { z } from 'zod'
import { createFileRoute } from '@tanstack/react-router'
import { zodValidator } from '@tanstack/zod-adapter'
const searchSchema = z.object({
rootValue: z.string().optional(),
})
export const Route = createRootRoute({
validateSearch: zodValidator(searchSchema),
search: {
middlewares: [
({ search, next }) => {
const result = next(search)
return {
rootValue: search.rootValue,
...result,
}
},
],
},
})
import { z } from 'zod'
import { createFileRoute } from '@tanstack/react-router'
import { zodValidator } from '@tanstack/zod-adapter'
const searchSchema = z.object({
rootValue: z.string().optional(),
})
export const Route = createRootRoute({
validateSearch: zodValidator(searchSchema),
search: {
middlewares: [
({ search, next }) => {
const result = next(search)
return {
rootValue: search.rootValue,
...result,
}
},
],
},
})
由于这个特定用例非常常见,TanStack Router 提供了一个通过 retainSearchParams 保留搜索参数的通用实现:
import { z } from 'zod'
import { createFileRoute, retainSearchParams } from '@tanstack/react-router'
import { zodValidator } from '@tanstack/zod-adapter'
const searchSchema = z.object({
rootValue: z.string().optional(),
})
export const Route = createRootRoute({
validateSearch: zodValidator(searchSchema),
search: {
middlewares: [retainSearchParams(['rootValue'])],
},
})
import { z } from 'zod'
import { createFileRoute, retainSearchParams } from '@tanstack/react-router'
import { zodValidator } from '@tanstack/zod-adapter'
const searchSchema = z.object({
rootValue: z.string().optional(),
})
export const Route = createRootRoute({
validateSearch: zodValidator(searchSchema),
search: {
middlewares: [retainSearchParams(['rootValue'])],
},
})
另一个常见用例是如果设置了默认值,则从链接中剥离搜索参数。TanStack Router 通过 stripSearchParams 为此用例提供了通用实现:
import { z } from 'zod'
import { createFileRoute, stripSearchParams } from '@tanstack/react-router'
import { zodValidator } from '@tanstack/zod-adapter'
const defaultValues = {
one: 'abc',
two: 'xyz',
}
const searchSchema = z.object({
one: z.string().default(defaultValues.one),
two: z.string().default(defaultValues.two),
})
export const Route = createFileRoute('/hello')({
validateSearch: zodValidator(searchSchema),
search: {
// 剥离默认值
middlewares: [stripSearchParams(defaultValues)],
},
})
import { z } from 'zod'
import { createFileRoute, stripSearchParams } from '@tanstack/react-router'
import { zodValidator } from '@tanstack/zod-adapter'
const defaultValues = {
one: 'abc',
two: 'xyz',
}
const searchSchema = z.object({
one: z.string().default(defaultValues.one),
two: z.string().default(defaultValues.two),
})
export const Route = createFileRoute('/hello')({
validateSearch: zodValidator(searchSchema),
search: {
// 剥离默认值
middlewares: [stripSearchParams(defaultValues)],
},
})
可以链接多个中间件。以下示例显示了如何组合 retainSearchParams 和 stripSearchParams。
import {
Link,
createFileRoute,
retainSearchParams,
stripSearchParams,
} from '@tanstack/react-router'
import { z } from 'zod'
import { zodValidator } from '@tanstack/zod-adapter'
const defaultValues = ['foo', 'bar']
export const Route = createFileRoute('/search')({
validateSearch: zodValidator(
z.object({
retainMe: z.string().optional(),
arrayWithDefaults: z.string().array().default(defaultValues),
required: z.string(),
}),
),
search: {
middlewares: [
retainSearchParams(['retainMe']),
stripSearchParams({ arrayWithDefaults: defaultValues }),
],
},
})
import {
Link,
createFileRoute,
retainSearchParams,
stripSearchParams,
} from '@tanstack/react-router'
import { z } from 'zod'
import { zodValidator } from '@tanstack/zod-adapter'
const defaultValues = ['foo', 'bar']
export const Route = createFileRoute('/search')({
validateSearch: zodValidator(
z.object({
retainMe: z.string().optional(),
arrayWithDefaults: z.string().array().default(defaultValues),
required: z.string(),
}),
),
search: {
middlewares: [
retainSearchParams(['retainMe']),
stripSearchParams({ arrayWithDefaults: defaultValues }),
],
},
})