作为 Vue 开发者,在迁移到 React 开发时,路由系统的差异是需要重点关注的部分。本文将从 Vue Router 的使用经验出发,详细介绍 React Router 的使用方式和最佳实践。
基础路由配置
Vue Router 配置
在 Vue 中,我们通常这样配置路由:
// router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
import About from '../views/About.vue';
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
component: About
},
{
path: '/user/:id',
name: 'user',
component: () => import('../views/User.vue')
}
]
});
export default router;
// main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
const app = createApp(App);
app.use(router);
app.mount('#app');
React Router 配置
在 React 中,我们使用 React Router:
// App.jsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import User from './pages/User';
import Layout from './components/Layout';
function App() {
return (
}>
} />
} />
} />
} />
);
}
// Layout.jsx
import { Outlet } from 'react-router-dom';
function Layout() {
return (
);
}
主要区别:
路由配置方式
- Vue Router 使用配置对象
- React Router 使用 JSX 声明式配置
路由嵌套方式
- Vue Router 使用 children 配置
- React Router 使用 Route 组件嵌套
布局组件实现
- Vue Router 使用嵌套路由
- React Router 使用 Outlet 组件
导航与参数获取
Vue Router 导航
首页
用户
React Router 导航
import { Link, useNavigate } from 'react-router-dom';
function Navigation() {
const navigate = useNavigate();
const handleNavigate = () => {
navigate('/about');
// 或者使用相对路径
navigate('../about');
// 带状态的导航
navigate('/user/123', { state: { from: 'home' } });
};
return (
);
}
参数获取对比
Vue Router:
React Router:
import { useParams, useSearchParams, useLocation } from 'react-router-dom';
function UserPage() {
const { id } = useParams();
const [searchParams] = useSearchParams();
const location = useLocation();
useEffect(() => {
// 参数变化时的处理
fetchUserData(id);
}, [id]);
return (
用户 ID: {id}
搜索词: {searchParams.get('search')}
来源: {location.state?.from}
);
}
路由守卫
Vue Router 的导航守卫
// 全局前置守卫
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !isAuthenticated()) {
next('/login');
} else {
next();
}
});
// 路由独享守卫
{
path: '/admin',
component: Admin,
beforeEnter: (to, from, next) => {
if (isAdmin()) {
next();
} else {
next('/403');
}
}
}
// 组件内守卫
export default {
beforeRouteEnter(to, from, next) {
next(vm => {
// 通过 vm 访问组件实例
});
}
}
React Router 的路由保护
// 路由保护组件
function PrivateRoute({ children }) {
const auth = useAuth(); // 自定义 hook 获取认证状态
const location = useLocation();
if (!auth.isAuthenticated) {
return ;
}
return children;
}
// 使用路由保护
function App() {
return (
}>
} />
}
/>
);
}
// 自定义 Hook 实现路由守卫逻辑
function useRouteGuard(checkFn) {
const location = useLocation();
const navigate = useNavigate();
useEffect(() => {
if (!checkFn()) {
navigate('/login', {
state: { from: location },
replace: true
});
}
}, [location, navigate, checkFn]);
}
// 在组件中使用
function AdminPage() {
useRouteGuard(() => isAdmin());
return 管理员页面;
}
实战示例:后台管理系统路由
让我们通过一个后台管理系统的路由配置来实践这些概念:
// types.ts
interface RouteConfig {
path: string;
element: React.ReactNode;
children?: RouteConfig[];
meta?: {
title: string;
icon?: string;
requiresAuth?: boolean;
permissions?: string[];
};
}
// routes/index.tsx
import { lazy } from 'react';
import { Navigate } from 'react-router-dom';
import Layout from '@/components/Layout';
const Dashboard = lazy(() => import('@/pages/Dashboard'));
const UserList = lazy(() => import('@/pages/UserList'));
const UserDetail = lazy(() => import('@/pages/UserDetail'));
const Settings = lazy(() => import('@/pages/Settings'));
export const routes: RouteConfig[] = [
{
path: '/',
element: ,
children: [
{
path: '',
element:
},
{
path: 'dashboard',
element: ,
meta: {
title: '仪表盘',
icon: 'dashboard',
requiresAuth: true
}
},
{
path: 'users',
meta: {
title: '用户管理',
icon: 'users',
requiresAuth: true,
permissions: ['user:read']
},
children: [
{
path: '',
element: ,
meta: {
title: '用户列表'
}
},
{
path: ':id',
element: ,
meta: {
title: '用户详情',
permissions: ['user:read']
}
}
]
},
{
path: 'settings',
element: ,
meta: {
title: '系统设置',
icon: 'settings',
requiresAuth: true,
permissions: ['settings:manage']
}
}
]
},
{
path: '/login',
element: ,
meta: {
title: '登录'
}
},
{
path: '*',
element: ,
meta: {
title: '404'
}
}
];
// components/AuthRoute.tsx
function AuthRoute({ meta, children }: { meta?: RouteMeta; children: React.ReactNode }) {
const { isAuthenticated, hasPermission } = useAuth();
const location = useLocation();
if (meta?.requiresAuth && !isAuthenticated) {
return ;
}
if (meta?.permissions && !meta.permissions.every(hasPermission)) {
return ;
}
return <>{children}>;
}
// App.tsx
function App() {
return (
}>
{renderRoutes(routes)}
);
}
// utils/route-helpers.tsx
function renderRoutes(routes: RouteConfig[]): React.ReactNode {
return routes.map(route => (
{route.element}
}
>
{route.children && renderRoutes(route.children)}
));
}
// hooks/useTitle.ts
function useTitle(title?: string) {
useEffect(() => {
if (title) {
document.title = `${title} - 管理系统`;
}
}, [title]);
}
// components/Layout.tsx
function Layout() {
const location = useLocation();
const { title } = useRouteMatch(routes)?.meta || {};
useTitle(title);
return (
);
}
// components/Sidebar.tsx
function Sidebar({ routes }: { routes: RouteConfig[] }) {
const location = useLocation();
const renderMenuItem = (route: RouteConfig) => {
if (route.meta?.icon) {
return (
}>
{route.meta.title}
);
}
return null;
};
const renderSubMenu = (route: RouteConfig) => {
if (route.children?.length) {
return (
}
title={route.meta?.title}
>
{route.children.map(child => (
route.children?.length ? renderSubMenu(child) : renderMenuItem(child)
))}
);
}
return renderMenuItem(route);
};
return (
);
}
性能优化
路由懒加载
const UserList = lazy(() => import('./pages/UserList')); function App() { return (
}> } /> 预加载路由
const UserList = lazy(() => import('./pages/UserList')); // 在合适的时机预加载 const prefetchUserList = () => { const component = import('./pages/UserList'); }; // 例如在鼠标悬停在链接上时 用户列表
路由缓存
function CacheRoute({ cacheKey, element }) { const [cached, setCached] = useState(null); const location = useLocation(); useEffect(() => { if (location.pathname === cacheKey) { setCached(element); } }, [location, cacheKey, element]); return cached || element; }
最佳实践
路由组织
- 按功能模块组织路由
- 使用懒加载优化性能
- 合理使用路由元信息
- 统一的路由配置管理
权限控制
- 路由级别的权限控制
- 细粒度的操作权限控制
- 动态路由生成
- 权限缓存优化
用户体验
- 合理的加载状态
- 平滑的过渡动画
- 直观的导航提示
- 友好的错误处理
小结
React Router 的特点:
- 声明式路由配置
- 组件化的路由管理
- 强大的 Hooks API
- 灵活的路由保护
从 Vue Router 到 React Router 的转变:
- 配置方式的转变
- 导航守卫的实现
- 参数获取的方式
- 布局组织的差异
开发建议:
- 深入理解路由原理
- 掌握最佳实践
- 注重性能优化
- 关注用户体验
下一篇文章,我们将深入探讨 React 的表单处理和验证,帮助你构建更好的用户交互体验。
如果觉得这篇文章对你有帮助,别忘了点个赞