作为 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 (
<browserrouter>
<routes>
<route path="/" element="{<Layout" />}>
<route index element="{<Home" />} />
<route path="about" element="{<About" />} />
<route path="user/:id" element="{<User" />} />
<route path="*" element="{<NotFound" />} />
</routes>
</browserrouter>
);
}
// Layout.jsx
import { Outlet } from 'react-router-dom';
function Layout() {
return (
<div>
<nav>{/* 导航栏 */}</nav>
<main>
<outlet />
</main>
<footer>{/* 页脚 */}</footer>
</div>
);
}
主要区别:
- 路由配置方式
- Vue Router 使用配置对象
- React Router 使用 JSX 声明式配置
- 路由嵌套方式
- Vue Router 使用 children 配置
- React Router 使用 Route 组件嵌套
- 布局组件实现
- Vue Router 使用嵌套路由
- React Router 使用 Outlet 组件
导航与参数获取
Vue Router 导航
<template>
<router-link to="/">首页</router-link>
<router-link :to="{ name: 'user', params: { id: 123 }}">用户</router-link>
<button @click="handleNavigate">编程式导航</button>
</template>
<script>
export default {
methods: {
handleNavigate() {
this.$router.push('/about');
// 或者使用命名路由
this.$router.push({ name: 'about' });
}
}
}
</script>
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 (
<nav>
<link to="/">首页
<link to="/user/123">用户
<button onclick="{handleNavigate}">编程式导航</button>
</nav>
);
}
参数获取对比
Vue Router:
<script>
export default {
created() {
// 路由参数
console.log(this.$route.params.id);
// 查询参数
console.log(this.$route.query.search);
},
watch: {
'$route.params.id'(newId) {
// 参数变化时的处理
this.fetchUserData(newId);
}
}
}
</script>
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 (
<div>
<h1>用户 ID: {id}</h1>
<p>搜索词: {searchParams.get('search')}</p>
<p>来源: {location.state?.from}</p>
</div>
);
}
路由守卫
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 <navigate to="/login" state="{{" from: location }} replace />;
}
return children;
}
// 使用路由保护
function App() {
return (
<browserrouter>
<routes>
<route path="/" element="{<Layout" />}>
<route index element="{<Home" />} />
<route path="admin/*" element="{" <privateroute>
<adminroutes />
}
/>
</route>
</routes>
</browserrouter>
);
}
// 自定义 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 <div>管理员页面</div>;
}
实战示例:后台管理系统路由
让我们通过一个后台管理系统的路由配置来实践这些概念:
// 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: <layout />,
children: [
{
path: '',
element: <navigate to="/dashboard" replace />
},
{
path: 'dashboard',
element: <dashboard />,
meta: {
title: '仪表盘',
icon: 'dashboard',
requiresAuth: true
}
},
{
path: 'users',
meta: {
title: '用户管理',
icon: 'users',
requiresAuth: true,
permissions: ['user:read']
},
children: [
{
path: '',
element: <userlist />,
meta: {
title: '用户列表'
}
},
{
path: ':id',
element: <userdetail />,
meta: {
title: '用户详情',
permissions: ['user:read']
}
}
]
},
{
path: 'settings',
element: <settings />,
meta: {
title: '系统设置',
icon: 'settings',
requiresAuth: true,
permissions: ['settings:manage']
}
}
]
},
{
path: '/login',
element: <login />,
meta: {
title: '登录'
}
},
{
path: '*',
element: <notfound />,
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 <navigate to="/login" state="{{" from: location }} replace />;
}
if (meta?.permissions && !meta.permissions.every(hasPermission)) {
return <navigate to="/403" replace />;
}
return <>{children};
}
// App.tsx
function App() {
return (
<browserrouter>
<suspense fallback="{<Loading" />}>
<routes>
{renderRoutes(routes)}
</routes>
</browserrouter>
);
}
// utils/route-helpers.tsx
function renderRoutes(routes: RouteConfig[]): React.ReactNode {
return routes.map(route => (
<route key="{route.path}" path="{route.path}" element="{" <authroute meta="{route.meta}">
{route.element}
}
>
{route.children && renderRoutes(route.children)}
</route>
));
}
// 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 (
<div classname="layout">
<sidebar routes="{routes}" />
<div classname="content">
<header></header>
<breadcrumb />
<main>
<outlet />
</main>
</div>
</div>
);
}
// components/Sidebar.tsx
function Sidebar({ routes }: { routes: RouteConfig[] }) {
const location = useLocation();
const renderMenuItem = (route: RouteConfig) => {
if (route.meta?.icon) {
return (
<menu.item key="{route.path}" icon="{<Icon" type="{route.meta.icon}" />}>
<link to="{route.path}">{route.meta.title}
);
}
return null;
};
const renderSubMenu = (route: RouteConfig) => {
if (route.children?.length) {
return (
<menu.submenu key="{route.path}" icon="{<Icon" type="{route.meta?.icon}" />}
title={route.meta?.title}
>
{route.children.map(child => (
route.children?.length ? renderSubMenu(child) : renderMenuItem(child)
))}
);
}
return renderMenuItem(route);
};
return (
<menu mode="inline" selectedkeys="{[location.pathname]}" defaultopenkeys="{[location.pathname.split('/')[1]]}">
{routes[0].children?.map(route => renderSubMenu(route))}
</menu>
);
}
性能优化
- 路由懒加载
const UserList = lazy(() => import('./pages/UserList'));
function App() {
return (
<suspense fallback="{<Loading" />}>
<routes>
<route path="/users" element="{<UserList" />} />
</routes>
);
}
- 预加载路由
const UserList = lazy(() => import('./pages/UserList'));
// 在合适的时机预加载
const prefetchUserList = () => {
const component = import('./pages/UserList');
};
// 例如在鼠标悬停在链接上时
<link to="/users" onMouseEnter="{prefetchUserList}">
用户列表
- 路由缓存
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 的表单处理和验证,帮助你构建更好的用户交互体验。
如果觉得这篇文章对你有帮助,别忘了点个赞 👍