路径参数用于匹配单个段(直到下一个 / 的文本),并将其值作为**命名**变量返回给您。它们通过在路径中使用 $ 字符前缀,后跟要分配的键变量来定义。以下是有效的路径参数路径
因为路径参数路由只匹配到下一个 /,所以可以创建子路由来继续表达层次结构
让我们创建一个使用路径参数来匹配帖子 ID 的帖子路由文件
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
return fetchPost(params.postId)
},
})
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
return fetchPost(params.postId)
},
})
一旦路径参数被解析,它就可用于所有子路由。这意味着如果我们为 postRoute 定义一个子路由,我们就可以在子路由的路径中使用 URL 中的 postId 变量!
路径参数作为 params 对象传递给加载器。此对象的键是路径参数的名称,值是从实际 URL 路径中解析出来的值。例如,如果我们要访问 /blog/123 URL,params 对象将是 { postId: '123' }
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
return fetchPost(params.postId)
},
})
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
return fetchPost(params.postId)
},
})
params 对象也会传递给 beforeLoad 选项
export const Route = createFileRoute('/posts/$postId')({
beforeLoad: async ({ params }) => {
// do something with params.postId
},
})
export const Route = createFileRoute('/posts/$postId')({
beforeLoad: async ({ params }) => {
// do something with params.postId
},
})
如果我们将一个组件添加到 postRoute,我们可以通过使用路由的 useParams hook 从 URL 访问 postId 变量
export const Route = createFileRoute('/posts/$postId')({
component: PostComponent,
})
function PostComponent() {
const { postId } = Route.useParams()
return <div>Post {postId}</div>
}
export const Route = createFileRoute('/posts/$postId')({
component: PostComponent,
})
function PostComponent() {
const { postId } = Route.useParams()
return <div>Post {postId}</div>
}
🧠 快速提示:如果您的组件是代码分割的,您可以使用 getRouteApi 函数 来避免导入 Route 配置以获取对类型化 useParams() hook 的访问。
您还可以使用全局导出的 useParams hook 从应用程序中的任何组件访问任何已解析的路径参数。您需要将 strict: false 选项传递给 useParams,表示您希望从模糊位置访问参数
function PostComponent() {
const { postId } = useParams({ strict: false })
return <div>Post {postId}</div>
}
function PostComponent() {
const { postId } = useParams({ strict: false })
return <div>Post {postId}</div>
}
导航到带有路径参数的路由时,TypeScript 将要求您以对象或返回参数对象的函数的形式传递参数。
让我们看看对象样式是什么样子的
function Component() {
return (
<Link to="/blog/$postId" params={{ postId: '123' }}>
Post 123
</Link>
)
}
function Component() {
return (
<Link to="/blog/$postId" params={{ postId: '123' }}>
Post 123
</Link>
)
}
函数样式是这样的
function Component() {
return (
<Link to="/blog/$postId" params={(prev) => ({ ...prev, postId: '123' })}>
Post 123
</Link>
)
}
function Component() {
return (
<Link to="/blog/$postId" params={(prev) => ({ ...prev, postId: '123' })}>
Post 123
</Link>
)
}
请注意,当您需要为其他路由保留 URL 中已有的参数时,函数样式很有用。这是因为函数样式将接收当前参数作为参数,允许您根据需要修改它们并返回最终的参数对象。
您还可以将**前缀**和**后缀**与路径参数一起使用,以创建更复杂的路由模式。这允许您匹配特定的 URL 结构,同时仍然捕获动态段。
当使用前缀或后缀时,您可以通过将路径参数括在大括号 {} 中,并将前缀或后缀放在变量名之前或之后来定义它们。
前缀是通过将前缀文本放在变量名之前的大括号外面来定义的。例如,如果您想匹配以 post- 开头后跟帖子 ID 的 URL,您可以这样定义它
// src/routes/posts/post-{$postId}.tsx
export const Route = createFileRoute('/posts/post-{$postId}')({
component: PostComponent,
})
function PostComponent() {
const { postId } = Route.useParams()
// postId will be the value after 'post-'
return <div>Post ID: {postId}</div>
}
// src/routes/posts/post-{$postId}.tsx
export const Route = createFileRoute('/posts/post-{$postId}')({
component: PostComponent,
})
function PostComponent() {
const { postId } = Route.useParams()
// postId will be the value after 'post-'
return <div>Post ID: {postId}</div>
}
您甚至可以将前缀与通配符路由组合以创建更复杂的模式
// src/routes/on-disk/storage-{$}
export const Route = createFileRoute('/on-disk/storage-{$postId}/$')({
component: StorageComponent,
})
function StorageComponent() {
const { _splat } = Route.useParams()
// _splat, will be value after 'storage-'
// i.e. my-drive/documents/foo.txt
return <div>Storage Location: /{_splat}</div>
}
// src/routes/on-disk/storage-{$}
export const Route = createFileRoute('/on-disk/storage-{$postId}/$')({
component: StorageComponent,
})
function StorageComponent() {
const { _splat } = Route.useParams()
// _splat, will be value after 'storage-'
// i.e. my-drive/documents/foo.txt
return <div>Storage Location: /{_splat}</div>
}
后缀是通过将后缀文本放在变量名之后的大括号外面来定义的。例如,如果您想匹配以 txt 结尾的文件名 URL,您可以这样定义它
// src/routes/files/{$fileName}txt
export const Route = createFileRoute('/files/{$fileName}.txt')({
component: FileComponent,
})
function FileComponent() {
const { fileName } = Route.useParams()
// fileName will be the value before 'txt'
return <div>File Name: {fileName}</div>
}
// src/routes/files/{$fileName}txt
export const Route = createFileRoute('/files/{$fileName}.txt')({
component: FileComponent,
})
function FileComponent() {
const { fileName } = Route.useParams()
// fileName will be the value before 'txt'
return <div>File Name: {fileName}</div>
}
您还可以将后缀与通配符组合以实现更复杂的路由模式
// src/routes/files/{$}[.]txt
export const Route = createFileRoute('/files/{$fileName}[.]txt')({
component: FileComponent,
})
function FileComponent() {
const { _splat } = Route.useParams()
// _splat will be the value before '.txt'
return <div>File Splat: {_splat}</div>
}
// src/routes/files/{$}[.]txt
export const Route = createFileRoute('/files/{$fileName}[.]txt')({
component: FileComponent,
})
function FileComponent() {
const { _splat } = Route.useParams()
// _splat will be the value before '.txt'
return <div>File Splat: {_splat}</div>
}
您可以结合前缀和后缀来创建非常特定的路由模式。例如,如果您想匹配以 user- 开头并以 .json 结尾的 URL,您可以这样定义它
// src/routes/users/user-{$userId}person
export const Route = createFileRoute('/users/user-{$userId}person')({
component: UserComponent,
})
function UserComponent() {
const { userId } = Route.useParams()
// userId will be the value between 'user-' and 'person'
return <div>User ID: {userId}</div>
}
// src/routes/users/user-{$userId}person
export const Route = createFileRoute('/users/user-{$userId}person')({
component: UserComponent,
})
function UserComponent() {
const { userId } = Route.useParams()
// userId will be the value between 'user-' and 'person'
return <div>User ID: {userId}</div>
}
与前面的示例类似,您也可以将通配符与前缀和后缀一起使用。尽情发挥吧!
可选路径参数允许您定义 URL 中可能存在也可能不存在的路由段。它们使用 {-$paramName} 语法,并提供灵活的路由模式,其中某些参数是可选的。
可选路径参数使用带连字符前缀的大括号定义:{-$paramName}
// Single optional parameter
// src/routes/posts/{-$category}.tsx
export const Route = createFileRoute('/posts/{-$category}')({
component: PostsComponent,
})
// Multiple optional parameters
// src/routes/posts/{-$category}/{-$slug}.tsx
export const Route = createFileRoute('/posts/{-$category}/{-$slug}')({
component: PostComponent,
})
// Mixed required and optional parameters
// src/routes/users/$id/{-$tab}.tsx
export const Route = createFileRoute('/users/$id/{-$tab}')({
component: UserComponent,
})
// Single optional parameter
// src/routes/posts/{-$category}.tsx
export const Route = createFileRoute('/posts/{-$category}')({
component: PostsComponent,
})
// Multiple optional parameters
// src/routes/posts/{-$category}/{-$slug}.tsx
export const Route = createFileRoute('/posts/{-$category}/{-$slug}')({
component: PostComponent,
})
// Mixed required and optional parameters
// src/routes/users/$id/{-$tab}.tsx
export const Route = createFileRoute('/users/$id/{-$tab}')({
component: UserComponent,
})
可选参数创建灵活的 URL 模式
当 URL 中不存在可选参数时,其值在您的路由处理程序和组件中将为 undefined。
可选参数在您的组件中与常规参数完全相同,但它们的值可能为 undefined
function PostsComponent() {
const { category } = Route.useParams()
return <div>{category ? `Posts in ${category}` : 'All Posts'}</div>
}
function PostsComponent() {
const { category } = Route.useParams()
return <div>{category ? `Posts in ${category}` : 'All Posts'}</div>
}
可选参数在加载器中可用,并且可能为 undefined
export const Route = createFileRoute('/posts/{-$category}')({
loader: async ({ params }) => {
// params.category might be undefined
return fetchPosts({ category: params.category })
},
})
export const Route = createFileRoute('/posts/{-$category}')({
loader: async ({ params }) => {
// params.category might be undefined
return fetchPosts({ category: params.category })
},
})
可选参数在 beforeLoad 处理程序中也有效
export const Route = createFileRoute('/posts/{-$category}')({
beforeLoad: async ({ params }) => {
if (params.category) {
// Validate category exists
await validateCategory(params.category)
}
},
})
export const Route = createFileRoute('/posts/{-$category}')({
beforeLoad: async ({ params }) => {
if (params.category) {
// Validate category exists
await validateCategory(params.category)
}
},
})
可选参数支持前缀和后缀模式
// File route: /files/prefix{-$name}.txt
// Matches: /files/prefix.txt and /files/prefixdocument.txt
export const Route = createFileRoute('/files/prefix{-$name}.txt')({
component: FileComponent,
})
function FileComponent() {
const { name } = Route.useParams()
return <div>File: {name || 'default'}</div>
}
// File route: /files/prefix{-$name}.txt
// Matches: /files/prefix.txt and /files/prefixdocument.txt
export const Route = createFileRoute('/files/prefix{-$name}.txt')({
component: FileComponent,
})
function FileComponent() {
const { name } = Route.useParams()
return <div>File: {name || 'default'}</div>
}
您可以创建所有参数都是可选的路由
// Route: /{-$year}/{-$month}/{-$day}
// Matches: /, /2023, /2023/12, /2023/12/25
export const Route = createFileRoute('/{-$year}/{-$month}/{-$day}')({
component: DateComponent,
})
function DateComponent() {
const { year, month, day } = Route.useParams()
if (!year) return <div>Select a year</div>
if (!month) return <div>Year: {year}</div>
if (!day)
return (
<div>
Month: {year}/{month}
</div>
)
return (
<div>
Date: {year}/{month}/{day}
</div>
)
}
// Route: /{-$year}/{-$month}/{-$day}
// Matches: /, /2023, /2023/12, /2023/12/25
export const Route = createFileRoute('/{-$year}/{-$month}/{-$day}')({
component: DateComponent,
})
function DateComponent() {
const { year, month, day } = Route.useParams()
if (!year) return <div>Select a year</div>
if (!month) return <div>Year: {year}</div>
if (!day)
return (
<div>
Month: {year}/{month}
</div>
)
return (
<div>
Date: {year}/{month}/{day}
</div>
)
}
可选参数可以与通配符结合用于复杂的路由模式
// Route: /docs/{-$version}/$
// Matches: /docs/extra/path, /docs/v2/extra/path
export const Route = createFileRoute('/docs/{-$version}/$')({
component: DocsComponent,
})
function DocsComponent() {
const { version } = Route.useParams()
const { _splat } = Route.useParams()
return (
<div>
Version: {version || 'latest'}
Path: {_splat}
</div>
)
}
// Route: /docs/{-$version}/$
// Matches: /docs/extra/path, /docs/v2/extra/path
export const Route = createFileRoute('/docs/{-$version}/$')({
component: DocsComponent,
})
function DocsComponent() {
const { version } = Route.useParams()
const { _splat } = Route.useParams()
return (
<div>
Version: {version || 'latest'}
Path: {_splat}
</div>
)
}
导航到带有可选参数的路由时,您可以精细控制要包含哪些参数
function Navigation() {
return (
<div>
{/* Navigate with optional parameter */}
<Link to="/posts/{-$category}" params={{ category: 'tech' }}>
Tech Posts
</Link>
{/* Navigate without optional parameter */}
<Link to="/posts/{-$category}" params={{ category: undefined }}>
All Posts
</Link>
{/* Navigate with multiple optional parameters */}
<Link
to="/posts/{-$category}/{-$slug}"
params={{ category: 'tech', slug: 'react-tips' }}
>
Specific Post
</Link>
</div>
)
}
function Navigation() {
return (
<div>
{/* Navigate with optional parameter */}
<Link to="/posts/{-$category}" params={{ category: 'tech' }}>
Tech Posts
</Link>
{/* Navigate without optional parameter */}
<Link to="/posts/{-$category}" params={{ category: undefined }}>
All Posts
</Link>
{/* Navigate with multiple optional parameters */}
<Link
to="/posts/{-$category}/{-$slug}"
params={{ category: 'tech', slug: 'react-tips' }}
>
Specific Post
</Link>
</div>
)
}
TypeScript 为可选参数提供完整的类型安全
function PostsComponent() {
// TypeScript knows category might be undefined
const { category } = Route.useParams() // category: string | undefined
// Safe navigation
const categoryUpper = category?.toUpperCase()
return <div>{categoryUpper || 'All Categories'}</div>
}
// Navigation is type-safe and flexible
<Link
to="/posts/{-$category}"
params={{ category: 'tech' }} // ✅ Valid - string
>
Tech Posts
</Link>
<Link
to="/posts/{-$category}"
params={{ category: 123 }} // ✅ Valid - number (auto-stringified)
>
Category 123
</Link>
function PostsComponent() {
// TypeScript knows category might be undefined
const { category } = Route.useParams() // category: string | undefined
// Safe navigation
const categoryUpper = category?.toUpperCase()
return <div>{categoryUpper || 'All Categories'}</div>
}
// Navigation is type-safe and flexible
<Link
to="/posts/{-$category}"
params={{ category: 'tech' }} // ✅ Valid - string
>
Tech Posts
</Link>
<Link
to="/posts/{-$category}"
params={{ category: 123 }} // ✅ Valid - number (auto-stringified)
>
Category 123
</Link>
可选路径参数非常适合实现国际化 (i18n) 路由模式。您可以使用前缀模式来处理多种语言,同时保持清晰、对 SEO 友好的 URL。
使用可选语言前缀来支持 /en/about、/fr/about 或仅 /about(默认语言)等 URL
// Route: /{-$locale}/about
export const Route = createFileRoute('/{-$locale}/about')({
component: AboutComponent,
})
function AboutComponent() {
const { locale } = Route.useParams()
const currentLocale = locale || 'en' // Default to English
const content = {
en: { title: 'About Us', description: 'Learn more about our company.' },
fr: {
title: 'À Propos',
description: 'En savoir plus sur notre entreprise.',
},
es: {
title: 'Acerca de',
description: 'Conoce más sobre nuestra empresa.',
},
}
return (
<div>
<h1>{content[currentLocale]?.title}</h1>
<p>{content[currentLocale]?.description}</p>
</div>
)
}
// Route: /{-$locale}/about
export const Route = createFileRoute('/{-$locale}/about')({
component: AboutComponent,
})
function AboutComponent() {
const { locale } = Route.useParams()
const currentLocale = locale || 'en' // Default to English
const content = {
en: { title: 'About Us', description: 'Learn more about our company.' },
fr: {
title: 'À Propos',
description: 'En savoir plus sur notre entreprise.',
},
es: {
title: 'Acerca de',
description: 'Conoce más sobre nuestra empresa.',
},
}
return (
<div>
<h1>{content[currentLocale]?.title}</h1>
<p>{content[currentLocale]?.description}</p>
</div>
)
}
此模式匹配
结合可选参数以实现更复杂的 i18n 路由
// Route: /{-$locale}/blog/{-$category}/$slug
export const Route = createFileRoute('/{-$locale}/blog/{-$category}/$slug')({
beforeLoad: async ({ params }) => {
const locale = params.locale || 'en'
const category = params.category
// Validate locale and category
const validLocales = ['en', 'fr', 'es', 'de']
if (locale && !validLocales.includes(locale)) {
throw new Error('Invalid locale')
}
return { locale, category }
},
loader: async ({ params, context }) => {
const { locale } = context
const { slug, category } = params
return fetchBlogPost({ slug, category, locale })
},
component: BlogPostComponent,
})
function BlogPostComponent() {
const { locale, category, slug } = Route.useParams()
const data = Route.useLoaderData()
return (
<article>
<h1>{data.title}</h1>
<p>
Category: {category || 'All'} | Language: {locale || 'en'}
</p>
<div>{data.content}</div>
</article>
)
}
// Route: /{-$locale}/blog/{-$category}/$slug
export const Route = createFileRoute('/{-$locale}/blog/{-$category}/$slug')({
beforeLoad: async ({ params }) => {
const locale = params.locale || 'en'
const category = params.category
// Validate locale and category
const validLocales = ['en', 'fr', 'es', 'de']
if (locale && !validLocales.includes(locale)) {
throw new Error('Invalid locale')
}
return { locale, category }
},
loader: async ({ params, context }) => {
const { locale } = context
const { slug, category } = params
return fetchBlogPost({ slug, category, locale })
},
component: BlogPostComponent,
})
function BlogPostComponent() {
const { locale, category, slug } = Route.useParams()
const data = Route.useLoaderData()
return (
<article>
<h1>{data.title}</h1>
<p>
Category: {category || 'All'} | Language: {locale || 'en'}
</p>
<div>{data.content}</div>
</article>
)
}
这支持以下 URL
使用带有函数样式参数的可选 i18n 参数创建语言切换器
function LanguageSwitcher() {
const currentParams = useParams({ strict: false })
const languages = [
{ code: 'en', name: 'English' },
{ code: 'fr', name: 'Français' },
{ code: 'es', name: 'Español' },
]
return (
<div className="language-switcher">
{languages.map(({ code, name }) => (
<Link
key={code}
to="/{-$locale}/blog/{-$category}/$slug"
params={(prev) => ({
...prev,
locale: code === 'en' ? undefined : code, // Remove 'en' for clean URLs
})}
className={currentParams.locale === code ? 'active' : ''}
>
{name}
</Link>
))}
</div>
)
}
function LanguageSwitcher() {
const currentParams = useParams({ strict: false })
const languages = [
{ code: 'en', name: 'English' },
{ code: 'fr', name: 'Français' },
{ code: 'es', name: 'Español' },
]
return (
<div className="language-switcher">
{languages.map(({ code, name }) => (
<Link
key={code}
to="/{-$locale}/blog/{-$category}/$slug"
params={(prev) => ({
...prev,
locale: code === 'en' ? undefined : code, // Remove 'en' for clean URLs
})}
className={currentParams.locale === code ? 'active' : ''}
>
{name}
</Link>
))}
</div>
)
}
您还可以创建更复杂的语言切换逻辑
function AdvancedLanguageSwitcher() {
const currentParams = useParams({ strict: false })
const handleLanguageChange = (newLocale: string) => {
return (prev: any) => {
// Preserve all existing params but update locale
const updatedParams = { ...prev }
if (newLocale === 'en') {
// Remove locale for clean English URLs
delete updatedParams.locale
} else {
updatedParams.locale = newLocale
}
return updatedParams
}
}
return (
<div className="language-switcher">
<Link
to="/{-$locale}/blog/{-$category}/$slug"
params={handleLanguageChange('fr')}
>
Français
</Link>
<Link
to="/{-$locale}/blog/{-$category}/$slug"
params={handleLanguageChange('es')}
>
Español
</Link>
<Link
to="/{-$locale}/blog/{-$category}/$slug"
params={handleLanguageChange('en')}
>
English
</Link>
</div>
)
}
function AdvancedLanguageSwitcher() {
const currentParams = useParams({ strict: false })
const handleLanguageChange = (newLocale: string) => {
return (prev: any) => {
// Preserve all existing params but update locale
const updatedParams = { ...prev }
if (newLocale === 'en') {
// Remove locale for clean English URLs
delete updatedParams.locale
} else {
updatedParams.locale = newLocale
}
return updatedParams
}
}
return (
<div className="language-switcher">
<Link
to="/{-$locale}/blog/{-$category}/$slug"
params={handleLanguageChange('fr')}
>
Français
</Link>
<Link
to="/{-$locale}/blog/{-$category}/$slug"
params={handleLanguageChange('es')}
>
Español
</Link>
<Link
to="/{-$locale}/blog/{-$category}/$slug"
params={handleLanguageChange('en')}
>
English
</Link>
</div>
)
}
使用可选参数组织 i18n 路由以实现灵活的区域设置处理
// Route structure:
// routes/
// {-$locale}/
// index.tsx // /, /en, /fr
// about.tsx // /about, /en/about, /fr/about
// blog/
// index.tsx // /blog, /en/blog, /fr/blog
// $slug.tsx // /blog/post, /en/blog/post, /fr/blog/post
// routes/{-$locale}/index.tsx
export const Route = createFileRoute('/{-$locale}/')({
component: HomeComponent,
})
function HomeComponent() {
const { locale } = Route.useParams()
const isRTL = ['ar', 'he', 'fa'].includes(locale || '')
return (
<div dir={isRTL ? 'rtl' : 'ltr'}>
<h1>Welcome ({locale || 'en'})</h1>
{/* Localized content */}
</div>
)
}
// routes/{-$locale}/about.tsx
export const Route = createFileRoute('/{-$locale}/about')({
component: AboutComponent,
})
// Route structure:
// routes/
// {-$locale}/
// index.tsx // /, /en, /fr
// about.tsx // /about, /en/about, /fr/about
// blog/
// index.tsx // /blog, /en/blog, /fr/blog
// $slug.tsx // /blog/post, /en/blog/post, /fr/blog/post
// routes/{-$locale}/index.tsx
export const Route = createFileRoute('/{-$locale}/')({
component: HomeComponent,
})
function HomeComponent() {
const { locale } = Route.useParams()
const isRTL = ['ar', 'he', 'fa'].includes(locale || '')
return (
<div dir={isRTL ? 'rtl' : 'ltr'}>
<h1>Welcome ({locale || 'en'})</h1>
{/* Localized content */}
</div>
)
}
// routes/{-$locale}/about.tsx
export const Route = createFileRoute('/{-$locale}/about')({
component: AboutComponent,
})
正确处理 i18n 路由的 SEO
export const Route = createFileRoute('/{-$locale}/products/$id')({
component: ProductComponent,
head: ({ params, loaderData }) => {
const locale = params.locale || 'en'
const product = loaderData
return {
title: product.title[locale] || product.title.en,
meta: [
{
name: 'description',
content: product.description[locale] || product.description.en,
},
{
property: 'og:locale',
content: locale,
},
],
links: [
// Canonical URL (always use default locale format)
{
rel: 'canonical',
href: `https://example.com/products/${params.id}`,
},
// Alternate language versions
{
rel: 'alternate',
hreflang: 'en',
href: `https://example.com/products/${params.id}`,
},
{
rel: 'alternate',
hreflang: 'fr',
href: `https://example.com/fr/products/${params.id}`,
},
{
rel: 'alternate',
hreflang: 'es',
href: `https://example.com/es/products/${params.id}`,
},
],
}
},
})
export const Route = createFileRoute('/{-$locale}/products/$id')({
component: ProductComponent,
head: ({ params, loaderData }) => {
const locale = params.locale || 'en'
const product = loaderData
return {
title: product.title[locale] || product.title.en,
meta: [
{
name: 'description',
content: product.description[locale] || product.description.en,
},
{
property: 'og:locale',
content: locale,
},
],
links: [
// Canonical URL (always use default locale format)
{
rel: 'canonical',
href: `https://example.com/products/${params.id}`,
},
// Alternate language versions
{
rel: 'alternate',
hreflang: 'en',
href: `https://example.com/products/${params.id}`,
},
{
rel: 'alternate',
hreflang: 'fr',
href: `https://example.com/fr/products/${params.id}`,
},
{
rel: 'alternate',
hreflang: 'es',
href: `https://example.com/es/products/${params.id}`,
},
],
}
},
})
确保您的 i18n 实现的类型安全
// Define supported locales
type Locale = 'en' | 'fr' | 'es' | 'de'
// Type-safe locale validation
function validateLocale(locale: string | undefined): locale is Locale {
return ['en', 'fr', 'es', 'de'].includes(locale as Locale)
}
export const Route = createFileRoute('/{-$locale}/shop/{-$category}')({
beforeLoad: async ({ params }) => {
const { locale } = params
// Type-safe locale validation
if (locale && !validateLocale(locale)) {
throw redirect({
to: '/shop/{-$category}',
params: { category: params.category },
})
}
return {
locale: (locale as Locale) || 'en',
isDefaultLocale: !locale || locale === 'en',
}
},
component: ShopComponent,
})
function ShopComponent() {
const { locale, category } = Route.useParams()
const { isDefaultLocale } = Route.useRouteContext()
// TypeScript knows locale is Locale | undefined
// and we have validated it in beforeLoad
return (
<div>
<h1>Shop {category ? `- ${category}` : ''}</h1>
<p>Language: {locale || 'en'}</p>
{!isDefaultLocale && (
<Link to="/shop/{-$category}" params={{ category }}>
View in English
</Link>
)}
</div>
)
}
// Define supported locales
type Locale = 'en' | 'fr' | 'es' | 'de'
// Type-safe locale validation
function validateLocale(locale: string | undefined): locale is Locale {
return ['en', 'fr', 'es', 'de'].includes(locale as Locale)
}
export const Route = createFileRoute('/{-$locale}/shop/{-$category}')({
beforeLoad: async ({ params }) => {
const { locale } = params
// Type-safe locale validation
if (locale && !validateLocale(locale)) {
throw redirect({
to: '/shop/{-$category}',
params: { category: params.category },
})
}
return {
locale: (locale as Locale) || 'en',
isDefaultLocale: !locale || locale === 'en',
}
},
component: ShopComponent,
})
function ShopComponent() {
const { locale, category } = Route.useParams()
const { isDefaultLocale } = Route.useRouteContext()
// TypeScript knows locale is Locale | undefined
// and we have validated it in beforeLoad
return (
<div>
<h1>Shop {category ? `- ${category}` : ''}</h1>
<p>Language: {locale || 'en'}</p>
{!isDefaultLocale && (
<Link to="/shop/{-$category}" params={{ category }}>
View in English
</Link>
)}
</div>
)
}
可选路径参数为在 TanStack Router 应用程序中实现国际化提供了强大而灵活的基础。无论您喜欢基于前缀的方法还是组合方法,您都可以创建清晰、对 SEO 友好的 URL,同时保持出色的开发人员体验和类型安全。
默认情况下,路径参数使用 encodeURIComponent 进行转义。如果您想允许其他有效的 URI 字符(例如 @ 或 +),您可以在 RouterOptions 中指定。
用法示例
const router = createRouter({
// ...
pathParamsAllowedCharacters: ['@'],
})
const router = createRouter({
// ...
pathParamsAllowedCharacters: ['@'],
})
以下是允许的字符列表
您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。