Vue 开发者的 React 实战指南:路由和导航篇

原创
01/11 20:08
阅读数 64

作为 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" />}&gt;
          <route index element="{<Home" />} /&gt;
          <route path="about" element="{<About" />} /&gt;
          <route path="user/:id" element="{<User" />} /&gt;
          <route path="*" element="{<NotFound" />} /&gt;
        
      </routes>
    </browserrouter>
  );
}

// Layout.jsx
import { Outlet } from 'react-router-dom';

function Layout() {
  return (
    <div>
      <nav>{/* 导航栏 */}</nav>
      <main>
        <outlet />
      </main>
      <footer>{/* 页脚 */}</footer>
    </div>
  );
}

主要区别:

  1. 路由配置方式
    • Vue Router 使用配置对象
    • React Router 使用 JSX 声明式配置
  2. 路由嵌套方式
    • Vue Router 使用 children 配置
    • React Router 使用 Route 组件嵌套
  3. 布局组件实现
    • 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 = () =&gt; {
    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(() =&gt; {
    // 参数变化时的处理
    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) =&gt; {
  if (to.meta.requiresAuth &amp;&amp; !isAuthenticated()) {
    next('/login');
  } else {
    next();
  }
});

// 路由独享守卫
{
  path: '/admin',
  component: Admin,
  beforeEnter: (to, from, next) =&gt; {
    if (isAdmin()) {
      next();
    } else {
      next('/403');
    }
  }
}

// 组件内守卫
export default {
  beforeRouteEnter(to, from, next) {
    next(vm =&gt; {
      // 通过 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" />}&gt;
          <route index element="{<Home" />} /&gt;
          <route path="admin/*" element="{" <privateroute>
                <adminroutes />
              
            }
          /&gt;
        </route>
      </routes>
    </browserrouter>
  );
}

// 自定义 Hook 实现路由守卫逻辑
function useRouteGuard(checkFn) {
  const location = useLocation();
  const navigate = useNavigate();
  
  useEffect(() =&gt; {
    if (!checkFn()) {
      navigate('/login', {
        state: { from: location },
        replace: true
      });
    }
  }, [location, navigate, checkFn]);
}

// 在组件中使用
function AdminPage() {
  useRouteGuard(() =&gt; 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(() =&gt; import('@/pages/Dashboard'));
const UserList = lazy(() =&gt; import('@/pages/UserList'));
const UserDetail = lazy(() =&gt; import('@/pages/UserDetail'));
const Settings = lazy(() =&gt; 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 &amp;&amp; !isAuthenticated) {
    return <navigate to="/login" state="{{" from: location }} replace />;
  }
  
  if (meta?.permissions &amp;&amp; !meta.permissions.every(hasPermission)) {
    return <navigate to="/403" replace />;
  }
  
  return &lt;&gt;{children};
}

// App.tsx
function App() {
  return (
    <browserrouter>
      <suspense fallback="{<Loading" />}&gt;
        <routes>
          {renderRoutes(routes)}
        </routes>
      
    </browserrouter>
  );
}

// utils/route-helpers.tsx
function renderRoutes(routes: RouteConfig[]): React.ReactNode {
  return routes.map(route =&gt; (
    <route key="{route.path}" path="{route.path}" element="{" <authroute meta="{route.meta}">
          {route.element}
        
      }
    &gt;
      {route.children &amp;&amp; renderRoutes(route.children)}
    </route>
  ));
}

// hooks/useTitle.ts
function useTitle(title?: string) {
  useEffect(() =&gt; {
    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) =&gt; {
    if (route.meta?.icon) {
      return (
        <menu.item key="{route.path}" icon="{<Icon" type="{route.meta.icon}" />}&gt;
          <link to="{route.path}">{route.meta.title}
        
      );
    }
    return null;
  };
  
  const renderSubMenu = (route: RouteConfig) =&gt; {
    if (route.children?.length) {
      return (
        <menu.submenu key="{route.path}" icon="{<Icon" type="{route.meta?.icon}" />}
          title={route.meta?.title}
        &gt;
          {route.children.map(child =&gt; (
            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 =&gt; renderSubMenu(route))}
    </menu>
  );
}

性能优化

  1. 路由懒加载
const UserList = lazy(() =&gt; import('./pages/UserList'));

function App() {
  return (
    <suspense fallback="{<Loading" />}&gt;
      <routes>
        <route path="/users" element="{<UserList" />} /&gt;
      </routes>
    
  );
}
  1. 预加载路由
const UserList = lazy(() =&gt; import('./pages/UserList'));

// 在合适的时机预加载
const prefetchUserList = () =&gt; {
  const component = import('./pages/UserList');
};

// 例如在鼠标悬停在链接上时
<link to="/users" onMouseEnter="{prefetchUserList}">
  用户列表

  1. 路由缓存
function CacheRoute({ cacheKey, element }) {
  const [cached, setCached] = useState(null);
  const location = useLocation();
  
  useEffect(() =&gt; {
    if (location.pathname === cacheKey) {
      setCached(element);
    }
  }, [location, cacheKey, element]);
  
  return cached || element;
}

最佳实践

  1. 路由组织

    • 按功能模块组织路由
    • 使用懒加载优化性能
    • 合理使用路由元信息
    • 统一的路由配置管理
  2. 权限控制

    • 路由级别的权限控制
    • 细粒度的操作权限控制
    • 动态路由生成
    • 权限缓存优化
  3. 用户体验

    • 合理的加载状态
    • 平滑的过渡动画
    • 直观的导航提示
    • 友好的错误处理

小结

  1. React Router 的特点:

    • 声明式路由配置
    • 组件化的路由管理
    • 强大的 Hooks API
    • 灵活的路由保护
  2. 从 Vue Router 到 React Router 的转变:

    • 配置方式的转变
    • 导航守卫的实现
    • 参数获取的方式
    • 布局组织的差异
  3. 开发建议:

    • 深入理解路由原理
    • 掌握最佳实践
    • 注重性能优化
    • 关注用户体验

下一篇文章,我们将深入探讨 React 的表单处理和验证,帮助你构建更好的用户交互体验。

如果觉得这篇文章对你有帮助,别忘了点个赞 👍

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
0 评论
0 收藏
0
分享
返回顶部
顶部