在开始从 React Location 迁移之前,理解 TanStack Router 采用的路由概念和设计决策非常重要。
React Location 和 TanStack Router 在设计理念上有许多共同点,但也存在一些关键差异需要注意:
本指南将以React Location 基础示例为例,演示如何通过基于文件的路由迁移至 TanStack Router,最终实现与原示例相同的功能(样式等非路由相关代码将省略)。
Tip
若需使用代码方式定义路由,请参阅代码路由指南。
首先安装 TanStack Router 的依赖:
npm install @tanstack/react-router @tanstack/router-devtools
npm install @tanstack/react-router @tanstack/router-devtools
同时移除 React Location 的依赖:
npm uninstall @tanstack/react-location @tanstack/react-location-devtools
npm uninstall @tanstack/react-location @tanstack/react-location-devtools
如果项目使用 Vite(或其他支持的打包工具),可通过 TanStack Router 插件自动监听路由文件变化并更新配置。
安装 Vite 插件:
npm install -D @tanstack/router-plugin
npm install -D @tanstack/router-plugin
在 vite.config.js 中添加配置:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { TanStackRouterVite } from '@tanstack/router-plugin/vite'
export default defineConfig({
// ...
plugins: [TanStackRouterVite(), react()],
})
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { TanStackRouterVite } from '@tanstack/router-plugin/vite'
export default defineConfig({
// ...
plugins: [TanStackRouterVite(), react()],
})
若未使用 Vite,可选用其他支持的打包工具,或通过 @tanstack/router-cli 包实现路由文件监听。
在项目根目录创建 tsr.config.json 文件:
{
"routesDirectory": "./src/routes",
"generatedRouteTree": "./src/routeTree.gen.ts"
}
{
"routesDirectory": "./src/routes",
"generatedRouteTree": "./src/routeTree.gen.ts"
}
完整配置选项详见文档。
在 src 目录下创建路由文件夹:
mkdir src/routes
mkdir src/routes
// src/routes/__root.tsx
import { createRootRoute, Outlet, Link } from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/router-devtools'
export const Route = createRootRoute({
component: () => {
return (
<>
<div>
<Link to="/" activeOptions={{ exact: true }}>
Home
</Link>
<Link to="/posts">Posts</Link>
</div>
<hr />
<Outlet />
<TanStackRouterDevtools />
</>
)
},
})
// src/routes/__root.tsx
import { createRootRoute, Outlet, Link } from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/router-devtools'
export const Route = createRootRoute({
component: () => {
return (
<>
<div>
<Link to="/" activeOptions={{ exact: true }}>
Home
</Link>
<Link to="/posts">Posts</Link>
</div>
<hr />
<Outlet />
<TanStackRouterDevtools />
</>
)
},
})
// src/routes/index.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/')({
component: Index,
})
// src/routes/index.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/')({
component: Index,
})
需将原 src/index.tsx 中与首页相关的组件和逻辑迁移至此文件。
// src/routes/posts.tsx
import { createFileRoute, Link, Outlet } from '@tanstack/react-router'
export const Route = createFileRoute('/posts')({
component: Posts,
loader: async () => {
const posts = await fetchPosts()
return {
posts,
}
},
})
function Posts() {
const { posts } = Route.useLoaderData()
return (
<div>
<nav>
{posts.map((post) => (
<Link
key={post.id}
to={`/posts/$postId`}
params={{ postId: post.id }}
>
{post.title}
</Link>
))}
</nav>
<Outlet />
</div>
)
}
// src/routes/posts.tsx
import { createFileRoute, Link, Outlet } from '@tanstack/react-router'
export const Route = createFileRoute('/posts')({
component: Posts,
loader: async () => {
const posts = await fetchPosts()
return {
posts,
}
},
})
function Posts() {
const { posts } = Route.useLoaderData()
return (
<div>
<nav>
{posts.map((post) => (
<Link
key={post.id}
to={`/posts/$postId`}
params={{ postId: post.id }}
>
{post.title}
</Link>
))}
</nav>
<Outlet />
</div>
)
}
需迁移原 src/index.tsx 中与文章列表相关的组件和逻辑。
// src/routes/posts.index.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/')({
component: PostsIndex,
})
// src/routes/posts.index.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/')({
component: PostsIndex,
})
需迁移原 src/index.tsx 中与文章列表索引相关的组件和逻辑。
// src/routes/posts.$postId.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/$postId')({
component: PostsId,
loader: async ({ params: { postId } }) => {
const post = await fetchPost(postId)
return {
post,
}
},
})
function PostsId() {
const { post } = Route.useLoaderData()
// ...
}
// src/routes/posts.$postId.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/$postId')({
component: PostsId,
loader: async ({ params: { postId } }) => {
const post = await fetchPost(postId)
return {
post,
}
},
})
function PostsId() {
const { post } = Route.useLoaderData()
// ...
}
需迁移原 src/index.tsx 中与文章详情相关的组件和逻辑。
使用支持的打包工具时,路由树会在开发脚本运行时自动生成。
若未使用支持的工具,可运行以下命令生成:
npx tsr generate
npx tsr generate
生成路由树后,更新 src/index.tsx 创建路由实例并渲染:
// src/index.tsx
import React from 'react'
import ReactDOM from 'react-dom'
import { createRouter, RouterProvider } from '@tanstack/react-router'
// 导入生成的路由树
import { routeTree } from './routeTree.gen'
// 创建路由实例
const router = createRouter({ routeTree })
// 注册路由实例以获得类型安全
declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}
const domElementId = 'root' // 假设存在 id 为 'root' 的根元素
// 渲染应用
const rootElement = document.getElementById(domElementId)
if (!rootElement) {
throw new Error(`Element with id ${domElementId} not found`)
}
ReactDOM.createRoot(rootElement).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>,
)
// src/index.tsx
import React from 'react'
import ReactDOM from 'react-dom'
import { createRouter, RouterProvider } from '@tanstack/react-router'
// 导入生成的路由树
import { routeTree } from './routeTree.gen'
// 创建路由实例
const router = createRouter({ routeTree })
// 注册路由实例以获得类型安全
declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}
const domElementId = 'root' // 假设存在 id 为 'root' 的根元素
// 渲染应用
const rootElement = document.getElementById(domElementId)
if (!rootElement) {
throw new Error(`Element with id ${domElementId} not found`)
}
ReactDOM.createRoot(rootElement).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>,
)
至此已成功通过基于文件的路由将应用从 React Location 迁移至 TanStack Router。
若原应用使用了 React Location 的其他功能,可参考以下迁移指南:
TanStack Router 还提供更多功能可供探索:
如遇问题或有疑问,欢迎在 TanStack Discord 社区寻求帮助。
Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.