mcpField
P
path-params

path-params

github.com/tanstack开发15.3k197 次安装4 天前更新
代码仓库

关于 path-params

面向 TanStack Router 开发者的技能,介绍动态、可选、通配及模式化 URL 参数,适用于定义类型化路由并安全地读取和传递参数。

主要功能

  1. 01动态及多个路径参数
  2. 02通配与可选参数
  3. 03前缀和后缀参数模式
  4. 04类型化参数访问与推断
  5. 05安全导航与编码

适用场景

  1. 01根据资源 ID 构建详情路由
  2. 02创建通配文件路由
  3. 03支持可选语言或分类路径段
  4. 04导航时保留类型化参数

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/tanstack/router --skill path-params

安装后在 Claude Code 中输入/path-params确认已加载。

详细介绍

路径参数

路径参数会将动态 URL 片段捕获到命名变量中。在路由路径中,它们以 $ 为前缀定义。

CRITICAL:绝不要将参数插入 to 字符串中。始终使用 params 属性。这是 Agent 处理路径参数时最常见的错误。

CRITICAL:类型会完全自动推断。绝不要为 useParams() 的返回值添加类型注解。

动态片段

以 $ 为前缀的片段会捕获文本,直到遇到下一个 /。

// src/routes/posts.$postId.tsx
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/posts/$postId')({
  loader: async ({ params }) => {
    // params.postId is string — fully inferred, do not annotate
    return fetchPost(params.postId)
  },
  component: PostComponent,
})

function PostComponent() {
  const { postId } = Route.useParams()
  const data = Route.useLoaderData()
  return (
    <h1>
      Post {postId}: {data.title}
    </h1>
  )
}

多个动态片段可以跨路径层级使用:

// src/routes/teams.$teamId.members.$memberId.tsx
export const Route = createFileRoute('/teams/$teamId/members/$memberId')({
  component: MemberComponent,
})

function MemberComponent() {
  const { teamId, memberId } = Route.useParams()
  return (
    <div>
      Team {teamId}, Member {memberId}
    </div>
  )
}

Splat / 捕获全部路由

路径以 $(单独的美元符号)结尾的路由会捕获其后的所有内容。该值可通过 _splat 键获取。

// src/routes/files.$.tsx
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/files/$')({
  component: FileViewer,
})

function FileViewer() {
  const { _splat } = Route.useParams()
  // URL: /files/documents/report.pdf → _splat = "documents/report.pdf"
  return <div>File path: {_splat}</div>
}

可选参数

可选参数使用 {-$paramName} 语法。该片段可以出现,也可以不出现。如果不存在,该值为 undefined。

// src/routes/posts.{-$category}.tsx
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/posts/{-$category}')({
  component: PostsComponent,
})

function PostsComponent() {
  const { category } = Route.useParams()
  // URL: /posts → category is undefined
  // URL: /posts/tech → category is "tech"
  return <div>{category ? `Posts in ${category}` : 'All Posts'}</div>
}

多个可选参数:

// Matches: /posts, /posts/tech, /posts/tech/hello-world
export const Route = createFileRoute('/posts/{-$category}/{-$slug}')({
  component: PostComponent,
})
使用可选语言环境的 i18n
// src/routes/{-$locale}/about.tsx
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/{-$locale}/about')({
  component: AboutComponent,
})

function AboutComponent() {
  const { locale } = Route.useParams()
  const currentLocale = locale || 'en'
  return <h1>{currentLocale === 'fr' ? 'À Propos' : 'About Us'}</h1>
}
// Matches: /about, /en/about, /fr/about

前缀和后缀模式

在 $paramName 两侧使用花括号 {},即可在单个片段中让动态部分前后带有文本。

前缀
// src/routes/posts/post-{$postId}.tsx
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/posts/post-{$postId}')({
  component: PostComponent,
})

function PostComponent() {
  const { postId } = Route.useParams()
  // URL: /posts/post-123 → postId = "123"
  return <div>Post ID: {postId}</div>
}
后缀
// src/routes/files/{$fileName}[.]txt.tsx
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/files/{$fileName}.txt')({
  component: FileComponent,
})

function FileComponent() {
  const { fileName } = Route.useParams()
  // URL: /files/readme.txt → fileName = "readme"
  return <div>File: {fileName}.txt</div>
}
前缀 + 后缀组合
// URL: /users/user-456.json → userId = "456"
export const Route = createFileRoute('/users/user-{$userId}.json')({
  component: UserComponent,
})

function UserComponent() {
  const { userId } = Route.useParams()
  return <div>User: {userId}</div>
}

使用路径参数导航

对象形式
import { Link } from '@tanstack/react-router'

function PostLink({ postId }: { postId: string }) {
  return (
    <Link to="/posts/$postId" params={{ postId }}>
      View Post
    </Link>
  )
}
函数形式(保留其他参数)
function PostLink({ postId }: { postId: string }) {
  return (
    <Link to="/posts/$postId" params={(prev) => ({ ...prev, postId })}>
      View Post
    </Link>
  )
}
编程式导航
import { useNavigate } from '@tanstack/react-router'

function GoToPost({ postId }: { postId: string }) {
  const navigate = useNavigate()

  return (
    <button
      onClick={() => {
        navigate({ to: '/posts/$postId', params: { postId } })
      }}
    >
      Go to Post
    </button>
  )
}
使用可选参数导航
// Include the optional param
<Link to="/posts/{-$category}" params={{ category: 'tech' }}>
  Tech Posts
</Link>

// Omit the optional param (renders /posts)
<Link to="/posts/{-$category}" params={{ category: undefined }}>
  All Posts
</Link>

在路由组件外读取参数

使用带 from 的 useParams
import { useParams } from '@tanstack/react-router'

function PostHeader() {
  const { postId } = useParams({ from: '/posts/$postId' })
  return <h2>Post {postId}</h2>
}
使用带 strict: false 的 useParams
function GenericBreadcrumb() {
  const params = useParams({ strict: false })
  // params is a union of all possible route params
  return <span>{params.postId ?? 'Home'}</span>
}

Loader 和 beforeLoad 中的参数

export const Route = createFileRoute('/posts/$postId')({
  beforeLoad: async ({ params }) => {
    // params.postId available here
    const canView = await checkPermission(params.postId)
    if (!canView) throw redirect({ to: '/unauthorized' })
  },
  loader: async ({ params }) => {
    return fetchPost(params.postId)
  },
})

允许的字符

默认情况下,参数使用 encodeURIComponent 编码。通过路由器配置允许额外字符:

import { createRouter } from '@tanstack/react-router'

const router = createRouter({
  routeTree,
  pathParamsAllowedCharacters: ['@', '+'],
})

允许的字符:;、:、@、&、=、+、$、,。

常见错误

1. CRITICAL(跨技能):将路径参数插入 to 字符串
// WRONG — breaks type safety and param encoding
<Link to={`/posts/${postId}`}>Post</Link>

// CORRECT — use params prop
<Link to="/posts/$postId" params={{ postId }}>Post</Link>
2. MEDIUM:使用 * 而不是 $ 表示 splat 路由

TanStack Router 使用 $ 表示 splat 路由。捕获的值位于 _splat 下,而不是 *。

// WRONG (React Router / other frameworks)
// <Route path="/files/*" />

// CORRECT (TanStack Router)
// File: src/routes/files.$.tsx
export const Route = createFileRoute('/files/$')({
  component: () => {
    const { _splat } = Route.useParams()
    return <div>{_splat}</div>
  },
})

注意:为保持向后兼容,v1 中仍支持 *,但 v2 中将移除。始终使用 _splat。

3. MEDIUM:对基本动态片段使用花括号

花括号仅用于前缀/后缀模式和可选参数。基本动态片段使用不带花括号的 $。

// WRONG — braces not needed for basic params
createFileRoute('/posts/{$postId}')

// CORRECT — bare $ for basic dynamic segments
createFileRoute('/posts/$postId')

// CORRECT — braces for prefix pattern
createFileRoute('/posts/post-{$postId}')

// CORRECT — braces for optional param
createFileRoute('/posts/{-$category}')
4. 参数始终是字符串

路径参数始终解析为字符串。如果需要数字,请在 loader 或组件中进行解析:

export const Route = createFileRoute('/posts/$postId')({
  loader: async ({ params }) => {
    const id = parseInt(params.postId, 10)
    if (isNaN(id)) throw notFound()
    return fetchPost(id)
  },
})

你也可以在路由上使用 params.parse 和 params.stringify 进行双向转换:

export const Route = createFileRoute('/posts/$postId')({
  params: {
    parse: (raw) => ({ postId: parseInt(raw.postId, 10) }),
    stringify: (parsed) => ({ postId: String(parsed.postId) }),
  },
  loader: async ({ params }) => {
    // params.postId is now number
    return fetchPost(params.postId)
  },
})

评论

支持 Markdown