陌上人如玉
公子世无双

6.10 与 Vue Router 的对比

6.10 与 Vue Router 的对比

一句话:Vue Router 是「路由表 + 全局守卫(beforeEach)」的模型,React Router 是「声明式组件 + 渲染控制」的模型——React 没有全局守卫钩子,权限要靠「包装组件」或「loader」实现。

一、配置方式

// Vue Router:集中式路由表
const routes = [
  {
    path: '/users/:id',
    name: 'user-detail',
    component: () => import('./views/UserDetail.vue'),   // 懒加载
    meta: { requiresAuth: true },
    props: true,
  },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

router.beforeEach((to, from, next) => {                  // ⭐ 全局守卫
  if (to.meta.requiresAuth && !isLoggedIn()) {
    next({ name: 'login', query: { redirect: to.fullPath } });
  } else {
    next();
  }
});
// React Router:声明式(JSX)或对象数组
const router = createBrowserRouter([
  {
    path: '/users/:id',
    element: <UserDetail />,
    loader: ({ params }) => fetchUser(params.id!),
  },
]);

// 或者声明式
<Routes>
  <Route path="/users/:id" element={<UserDetail />} />
</Routes>
维度 Vue Router React Router
配置形态 集中式路由表(对象数组) JSX 组件 或 对象数组(数据路由)
视图出口 <router-view /> <Outlet />
导航组件 <router-link> <Link> / <NavLink>
激活类名 自动加 .router-link-active 自己用 NavLink 的 isActive
命名路由 ✅ { name: 'x' } ❌ 只能用路径
路由别名 ✅ alias ❌
meta 字段 ✅ ❌(用 loader 或组件内处理)
全局守卫 ✅ beforeEach / afterEach ❌(用包装组件 / loader)

二、守卫模型对比(核心差异)

// Vue:三层守卫
// ① 全局
router.beforeEach((to, from, next) => {});
router.beforeResolve((to, from, next) => {});
router.afterEach((to, from) => {});

// ② 路由独享
{ path: '/admin', component: Admin, beforeEnter: (to, from, next) => {} }

// ③ 组件内
beforeRouteEnter(to, from, next) {}
beforeRouteUpdate(to, from, next) {}
beforeRouteLeave(to, from, next) {}
// React:没有守卫钩子,用「渲染控制」实现
// ① 包装组件(渲染时判断)
function RequireAuth({ children }: { children: React.ReactNode }) {
  const { user, isLoading } = useAuth();
  const location = useLocation();

  if (isLoading) return <FullPageSkeleton />;
  if (!user) return <Navigate to="/login" state={{ from: location }} replace />;
  return <>{children}</>;
}

// ② 布局路由(保护一组)
{
  path: '/',
  element: <RequireAuth />,          // 内部渲染 <Outlet />
  children: [/* 受保护的路由 */],
}

// ③ loader 里校验(渲染前,无闪烁)
{
  path: '/admin',
  loader: () => {
    if (!isAdmin()) throw redirect('/403');
    return null;
  },
  element: <Admin />,
}
能力 Vue Router React Router
全局前置守卫 ✅ beforeEach ❌(用布局路由 / loader)
全局后置守卫 ✅ afterEach ❌(用 useLocation + useEffect)
路由独享守卫 ✅ beforeEnter ✅ loader
组件内「离开前」守卫 ✅ beforeRouteLeave ⚠️ useBlocker(仅数据路由)
异步守卫 ✅(next() 异步调用) ✅(loader 可以是 async)
「渲染前」校验 ⚠️(守卫 + 组件) ✅ loader(更彻底)
// React 里实现「afterEach」的等价物:监听 location
function RouteAnalytics() {
  const location = useLocation();

  useEffect(() => {
    trackPageView(location.pathname + location.search);
  }, [location]);

  return null;
}
// 放在根布局里即可(等价于全局 afterEach)

三、数据获取对比

<!-- Vue:组件内 + 组件守卫 或 onMounted -->
<script setup>
import { onMounted, ref } from 'vue';
import { onBeforeRouteUpdate } from 'vue-router';

const user = ref(null);
const route = useRoute();

// ① 组件挂载时获取
onMounted(async () => {
  user.value = await fetchUser(route.params.id);
});

// ② 参数变化时重新获取(需要显式处理)
onBeforeRouteUpdate(async (to) => {
  user.value = await fetchUser(to.params.id);
});
</script>
// React:三种方式
// ① 组件内 useEffect(需要自己处理参数变化与竞态)
function UserDetail() {
  const { id } = useParams();
  const [user, setUser] = useState<User | null>(null);

  useEffect(() => {
    let cancelled = false;
    fetchUser(id!).then((data) => { if (!cancelled) setUser(data); });
    return () => { cancelled = true; };
  }, [id]);

  return <div>{user?.name}</div>;
}

// ② 查询库(推荐)
function UserDetail2() {
  const { id } = useParams();
  const { data } = useQuery({ queryKey: ['user', id], queryFn: () => fetchUser(id!) });
  return <div>{data?.name}</div>;
}

// ③ loader(数据路由,渲染前取)
const router = createBrowserRouter([
  { path: 'users/:id', element: <UserDetail />, loader: ({ params }) => fetchUser(params.id!) },
]);
维度 Vue React
组件内获取 onMounted / created useEffect
参数变化时重新获取 onBeforeRouteUpdate / watch useEffect 依赖参数 / 查询库 key
「渲染前」预取 ⚠️ 需配合 Suspense 或 beforeRouteEnter ✅ loader
内置缓存 ❌(需自己实现或用 Pinia + 手写) ✅ TanStack Query

四、动态路由与参数

<!-- Vue:可以用 props 传参(推荐) -->
<script setup>
// 路由配置里写 props: true
const props = defineProps({ id: String });
</script>
// React:用 Hook 读取
function UserDetail() {
  const { id } = useParams();
  return <div>{id}</div>;
}
维度 Vue React
读取参数 useRoute().params.id useParams().id
传给组件的方式 props: true(推荐,解耦) Hook(组件自己取)
参数变化时组件是否重建 默认不重建(同 React) 默认不重建
强制重建 :key="$route.fullPath" <Comp key={id} />
<!-- Vue 里用 key 强制重建(与 React 的 key 思路一致) -->
<router-view :key="$route.fullPath" />

五、嵌套路由与布局

<!-- Vue:嵌套 router-view -->
<!-- layout/DefaultLayout.vue -->
<template>
  <div>
    <TopNav />
    <router-view />          <!-- ⭐ 子路由出口 -->
  </div>
</template>
// React:Outlet
function DefaultLayout() {
  return (
    <div>
      <TopNav />
      <Outlet />             {/* ⭐ 子路由出口 */}
    </div>
  );
}
维度 Vue React
子路由出口 <router-view /> <Outlet />
具名出口 ✅ <router-view name="sidebar" /> ❌(用 props 传元素)
子路由路径 相对父路径(同 React) 相对父路径
索引路由 path: '' { index: true }

六、编程式导航与查询参数

// Vue
const router = useRouter();
const route = useRoute();

router.push('/about');                                  // 字符串
router.push({ name: 'user', params: { id: '1' } });     // ⭐ 命名路由
router.push({ path: '/search', query: { q: 'react' } }); // ⭐ query 对象
router.replace('/login');
router.back();
router.go(-1);
// React
const navigate = useNavigate();

navigate('/about');
navigate(`/users/${id}`);                              // ⚠️ 没有命名路由,要拼路径
navigate('/search?q=react');
navigate('/search', { state: { q: 'react' } });         // state 不进 URL
navigate(-1);                                           // 后退
// Vue 的 query 对应 React 的 useSearchParams / URL 拼接
// Vue:
router.push({ query: { page: 2, q: 'react' } });

// React:
const [searchParams, setSearchParams] = useSearchParams();
setSearchParams({ page: '2', q: 'react' });
维度 Vue React
跳转 router.push / replace navigate
按名字跳转 ✅ { name } ❌ 只能拼路径
传查询参数 ✅ query: {} 对象 useSearchParams 或用模板串
传「不进 URL」的数据 state(history) navigate(..., { state })
后退 router.back() / go(-1) navigate(-1)

七、心智转换(Vue → React)

① 从「路由表 + 守卫」变成「组件树 + 渲染控制」
   Vue 的「拦在跳转前」→ React 的「拦在渲染时」(或 loader)

② 从「全局守卫」变成「布局路由」
   Vue: router.beforeEach → React: <RequireAuth><Outlet /></RequireAuth>

③ 从「meta 字段」变成「loader / 组件内判断」
   Vue: to.meta.requiresAuth → React: loader 里读上下文 / 包装组件里判断

④ 从「命名路由」变成「路径常量」
   Vue: router.push({ name: 'user', params: { id } })
   React: navigate(PATHS.userDetail(id))     ← 建议定义路径常量

⑤ 从「props: true 传参」变成「useParams」
   React 里组件自己用 Hook 取参数

⑥ 从「beforeRouteLeave」变成「useBlocker」
   只在数据路由下可用(且浏览器前进/后退需额外处理 beforeunload)
// ④ 的实践:定义路径常量(弥补「没有命名路由」的不足)
export const PATHS = {
  home: '/',
  login: '/login',
  userDetail: (id: string) => `/users/${id}`,
  settings: '/settings',
  admin: {
    root: '/admin',
    users: '/admin/users',
  },
} as const;

// 使用
navigate(PATHS.userDetail(id));
<Link to={PATHS.settings}>设置</Link>;

八、常见坑

// ① 在 React 里找「beforeEach」的等价物(没有,要用布局路由)
// ② 期待「命名路由」跳转(React 不支持,用路径常量替代)
// ③ 用 meta 存权限信息(React 里应该存在「路由配置对象」或「服务端返回」)
// ④ 从 Vue 的「props 传参」思维出发,试图给路由组件传 props
<Route path="/users/:id" element={<UserDetail id={id} />} />   // ❌ 拿不到 id(外层没有)
// ✅ 组件内 useParams

// ⑤ 用 watch 的思维写 useEffect(依赖数组写错的概率更高)
// ⑥ 以为「路由变化会自动重新请求数据」(Vue 也需显式处理,React 也一样)
// ⑦ 从 Vue 过来「忘了 useParams 返回的是字符串」
// ⑧ 混淆「Vue 的 query」与「React 的 state」
// ⑨ 在 React 里「用事件总线做全局守卫」(应该用布局路由)
// ⑩ 用「beforeRouteLeave」的思路做「未保存提示」但没有 useBlocker
// ⑪ 认为「Vue Router 的守卫更强大」(其实只是模型不同——React 的 loader 能「渲染前」校验)
// ⑫ 忽略「React Router 的 loader 会阻塞导航」(Vue 的守卫默认不阻塞)

九、面试延伸

  1. 「React Router 和 Vue Router 的主要区别?」
    四方面:① 配置形态——Vue 是「集中式路由表」(对象数组),React 可以是 JSX 组件或对象数组(数据路由);② 视图出口——Vue 用 <router-view />,React 用 <Outlet />;③ 守卫模型——Vue 有「全局 beforeEach / 路由独享 beforeEnter / 组件内 beforeRouteLeave」三层守卫,而 React 没有全局守卫钩子,权限通过「包装组件」「布局路由」或「loader 里校验」实现;④ 数据获取——Vue 主要在组件内(onMounted / beforeRouteUpdate),React 除了组件内(useEffect / 查询库)还能用 loader 在渲染前取数据。

  2. 「React 里怎么等价实现 beforeEach?」
    三种方式:① 布局路由——写一个 <RequireAuth><Outlet /></RequireAuth> 作为「需要保护的路由」的父级,RequireAuth 内部做校验(未登录就 <Navigate to="/login" />);② loader 里校验——在路由配置的 loader 中 throw redirect('/login')(渲染前完成跳转,没有闪烁,最接近 beforeEach 的语义);③ 包装组件——<Route element={<RequireAuth><Page /></RequireAuth>} />(粒度更细)。注意要处理「鉴权状态加载中」的三态,否则刷新页面会闪一下登录页。

  3. 「beforeRouteLeave(离开前守卫)在 React 里怎么做?」
    用 useBlocker(仅在数据路由下可用):const blocker = useBlocker(({ currentLocation, nextLocation }) => dirty && currentLocation.pathname !== nextLocation.pathname),然后根据 blocker.state === 'blocked' 显示确认弹窗,用户点「离开」调 blocker.proceed()、点「留下」调 blocker.reset()。注意:useBlocker 只拦「站内导航」,浏览器前进/后退与关闭标签页需要额外监听 beforeunload 事件。

  4. 「数据获取的方式有什么不同?」
    Vue 主要在「组件内」——onMounted(首次)与 onBeforeRouteUpdate / watch(参数变化时重新获取);React 有三种:① 组件内 useEffect(依赖参数,需自己处理竞态);② 查询库(TanStack Query)(把参数放进 queryKey,自动重新请求 + 缓存);③ loader(渲染前取数据,组件里用 useLoaderData 同步读取)。React 的 loader 是 Vue 相对少见的能力——它能做到「数据就绪才渲染」,避免骨架闪烁。

  5. 「Vue 的 meta 字段在 React 里怎么实现?」
    React Router 没有内置 meta。等价做法有三种:① 在自己的路由配置对象上加自定义字段(如 permission: 'user:list'),在 loader 或包装组件里读取;② 在包装组件里显式判断(<RequireRole role="admin">);③ 用 Context 提供「权限工具」(useAuth().hasPermission('x'))。此外「页面标题」这类 meta 信息,React 19 支持直接在组件里渲染 <title>(会自动提升到 <head>),或用 react-helmet / 框架的 metadata API。

  6. 「Vue 的命名路由在 React 里怎么替代?」
    React Router 不支持命名路由,必须用路径字符串。为了避免「路径散落在各处」,推荐定义路径常量字典:const PATHS = { userDetail: (id: string) => \/users/${id}` },然后统一用navigate(PATHS.userDetail(id))`。这样至少有「集中管理 + 改路径只需改一处」的好处(等价于命名路由的部分价值),而且能被 TypeScript 校验参数。

  7. 「Vue 的 props: true 在 React 里怎么实现?」
    React 里路由组件不能通过路由配置传 props(<Route path="/users/:id" element={<UserDetail id={id} />} /> 里的 id 在配置时并不存在)。等价做法是「组件自己用 useParams() 取参数」:function UserDetail() { const { id } = useParams(); }。如果确实想保持「组件只依赖 props」的可测试性,可以写一个「容器组件」负责取参数再传给展示组件:function UserDetailRoute() { const { id } = useParams(); return <UserDetail id={id} />; }。

  8. 「两边的守卫哪个更强大?」
    各有所长:Vue Router 的守卫更「丰富」——有全局前置/后置、路由独享、组件内的进入/更新/离开三类时机,且能「取消导航」(next(false));React 的 loader 更「彻底」——它把「校验 + 取数据」放到渲染之前,所以「未授权时根本不会渲染组件、也不会闪一下」;而 Vue 的 beforeEach 虽然也在导航前,但「数据获取」通常还在组件里(可能先渲染再取数)。模型不同:Vue 是「拦截导航」,React 是「控制渲染 + 渲染前准备数据」。

一句话速记

Vue Router 是「集中式路由表 + 三层守卫(beforeEach/beforeEnter/beforeRouteLeave)+ 命名路由 + meta」,React Router 是「声明式组件 + 渲染控制」——没有全局守卫(用布局路由或 loader 里 throw redirect 实现,后者能做到「渲染前校验」)、没有命名路由(用路径常量替代)、没有 meta(用自定义配置字段/包装组件)、没有 props: true(用 useParams);视图出口是 Outlet(对 <router-view />)、参数变化两边都不重建组件(都要显式处理或加 key);「离开前守卫」用 useBlocker(仅数据路由,且需配合 beforeunload);数据获取 React 多了 loader(渲染前就绪)这一路径。

赞(0) 打赏
未经允许不得转载:陌上寒 » 6.10 与 Vue Router 的对比

评论 抢沙发

觉得文章有用就打赏一下文章作者

非常感谢你的打赏,我们将继续给力更多优质内容,让我们一起创建更加美好的网络世界!

微信扫一扫

支付宝扫一扫