TanStack Router
TanStack Router(原 React Location)是一个现代的、功能强大的路由库,专注于类型安全和开发体验。
特点优势
-
完全类型安全
- 路由定义时的类型检查
- 参数和查询字符串的类型推断
- 导航和链接的类型安全
-
文件系统路由
- 基于文件系统的路由组织
- 自动路由生成
- 动态路由支持
-
优秀的开发体验
- 自动代码分割
- 开发时的热重载
- 详细的开发时错误提示
基础使用
1. 安装
npm install @tanstack/router
2. 基本路由配置
import {
Router,
Route,
RootRoute,
RouterProvider
} from '@tanstack/router';
// 定义根路由
const rootRoute = new RootRoute({
component: () => (
<div>
<h1>Root Layout</h1>
<Outlet />
</div>
),
});
// 定义索引路由
const indexRoute = new Route({
getParentRoute: () => rootRoute,
path: '/',
component: () => <div>首页</div>,
});
// 定义用户路由
const usersRoute = new Route({
getParentRoute: () => rootRoute,
path: 'users',
component: () => <div>用户列表</div>,
});
// 创建路由器
const router = new Router({
routeTree: rootRoute.addChildren([
indexRoute,
usersRoute,
]),
});
// 应用路由器
function App() {
return <RouterProvider router={router} />;
}
3. 类型安全的路由参数
interface UserRouteParams {
userId: string;
}
const userRoute = new Route({
getParentRoute: () => usersRoute,
path: '$userId',
component: () => {
const { userId } = useParams();
return <div>用户 ID: {userId}</div>;
},
validateParams: (params): params is UserRouteParams => {
return !!params.userId;
},
});
高级特性
1. 路由加载器(Loaders)
const userRoute = new Route({
path: 'users/$userId',
loader: async ({ params: { userId } }) => {
const user = await fetchUser(userId);
return { user };
},
component: () => {
const { user } = useLoaderData();
return <div>{user.name}</div>;
},
});
2. 路由搜索参数
const searchRoute = new Route({
path: 'search',
validateSearch: (search): search is { q: string } => {
return typeof search.q === 'string';
},
component: () => {
const { search } = useSearch();
return <div>搜索: {search.q}</div>;
},
});
3. 延迟加载和预加载
const dashboardRoute = new Route({
path: 'dashboard',
loader: async () => {
const [stats, notifications] = await Promise.all([
fetchStats(),
fetchNotifications(),
]);
return { stats, notifications };
},
component: lazy(() => import('./Dashboard')),
preload: () => import('./Dashboard'),
});
4. 路由守卫
const protectedRoute = new Route({
path: 'admin',
beforeLoad: async () => {
const isAuthenticated = await checkAuth();
if (!isAuthenticated) {
throw redirect({ to: '/login' });
}
},
component: AdminPanel,
});
实践模式
1. 组织路由文件
// routes/root.tsx
export const rootRoute = new RootRoute({
component: RootComponent,
});
// routes/index.tsx
export const indexRoute = new Route({
getParentRoute: () => rootRoute,
path: '/',
component: IndexComponent,
});
// routes/users.tsx
export const usersRoute = new Route({
getParentRoute: () => rootRoute,
path: 'users',
component: UsersComponent,
});
// routes/index.ts
export const routeTree = rootRoute.addChildren([
indexRoute,
usersRoute,
]);
2. 类型安全的链接
import { Link } from '@tanstack/router';
function Navigation() {
return (
<nav>
<Link
to="/users/$userId"
params={{ userId: '123' }}
search={{ tab: 'profile' }}
>
用户资料
</Link>
</nav>
);
}
3. 错误处理
const userRoute = new Route({
path: 'users/$userId',
errorComponent: ({ error }) => {
return (
<div className="error">
<h2>错误</h2>
<p>{error.message}</p>
</div>
);
},
});
性能优化
1. 路由预加载
const router = new Router({
routeTree,
defaultPreload: 'intent', // 鼠标悬停时预加载
});
// 手动预加载
router.preloadRoute({
to: '/dashboard',
search: { view: 'analytics' },
});
2. 并行数据加载
const dashboardRoute = new Route({
path: 'dashboard',
loader: async () => {
const promises = [
fetchUserData(),
fetchAnalytics(),
fetchNotifications(),
];
const [userData, analytics, notifications] = await Promise.all(promises);
return {
userData,
analytics,
notifications,
};
},
});
最佳实践
-
路由组织
- 使用文件系统路由
- 按功能模块组织路由文件
- 利用类型系统保证路由安全
-
数据加载
- 使用 loader 函数预加载数据
- 实现并行数据加载
- 适当使用预加载策略
-
错误处理
- 为每个路由定义错误边界
- 实现优雅的错误展示
- 提供清晰的错误信息
-
性能优化
- 实现路由级别的代码分割
- 使用预加载提升用户体验
- 优化数据加载策略
常见问题
1. 类型错误处理
// 确保参数类型正确
interface PostParams {
postId: string;
}
const postRoute = new Route({
path: 'posts/$postId',
validateParams: (params): params is PostParams => {
return typeof params.postId === 'string';
},
});
2. 动态导入优化
// 使用动态导入和预加载
const LazyComponent = lazy(() => import('./HeavyComponent'));
const route = new Route({
component: LazyComponent,
preload: () => import('./HeavyComponent'),
});