搭建现代前端项目:React vs Vue 的“双城记”——谁才是你的菜?

10 阅读6分钟

你有没有过这样的困惑:

  • 明明写了同样的功能,为什么 React 和 Vue 的写法看起来“天差地别”?
  • 用 Vite 创建项目后,main.jsxApp.jsxrouter.jsx 这些文件到底谁管谁?
  • 路由到底是怎么“接管一切”的?<Link><a> 真的不一样吗?

今天,我们就来以一个最简单的多页面应用为切入点,深入剖析 React 与 Vue 在现代前端工程化中的架构设计哲学。不讲虚的,直接上手实战 + 源码级解读,带你从“会用”走向“懂原理”。


🚀第一步:我们先造个轮子 —— 初始化项目

npm init vite@latest my-app -- --template react
# 或者
npm init vite@latest my-app -- --template vue

一句话,Vite 就给你搭好了一个现代化前端项目的骨架。它背后是尤雨溪对构建工具的深刻理解 —— 利用浏览器原生 ESM 实现极速冷启动热更新,开发体验拉满!

💡小知识:Vite ≠ Webpack。Vite 在开发阶段不打包,而是按需加载模块;生产环境才真正构建。


🧱项目结构一览:两个框架,两种美学

目录/文件React 项目Vue 项目
入口文件main.jsxmain.js
根组件App.jsxApp.vue
组件存放src/components/src/components/
页面级组件src/pages/Home.jsxsrc/views/Home.vue
路由配置src/router.jsxsrc/router/index.js
全局样式index.styl / cssstyle.css

虽然目录相似,但内核完全不同 —— 我们接下来就看看它们是如何“殊途同归”地实现路由跳转的。


🔗第二步:让页面动起来 —— 前端路由登场

❓什么是前端路由?

传统网页每次点击链接都会向服务器请求新页面。而前端路由是在不刷新页面的前提下,通过 JS 动态渲染不同内容,实现 SPA(单页应用)体验。

举个🌰:

用户访问 /about,不是真的去服务器拿一个叫 about.html 的文件,而是由前端自己决定:“哦,用户要点关于我,那我就把 About 组件显示出来。”

这就需要一个“总调度官”—— 路由管理器


🔄React 中的路由:声明式 + JSX 的极致优雅

✅ 使用 react-router-dom

安装命令:

npm install react-router-dom

核心组件三件套:

  • <Router>:路由顶层容器(一般用 BrowserRouter
  • <Routes>:路由匹配器
  • <Route>:具体路径映射规则
  • <Link>:替代 <a> 的路由跳转标签

📂代码结构拆解

App.jsx —— 路由入口 + 导航栏

import { BrowserRouter as Router, Link } from 'react-router-dom';
import AppRouters from './router';

function App() {
  return (
    <Router>
      <nav>
        <ul>
          <li><Link to="/">Home</Link></li>
          <li><Link to="/about">About</Link></li>
        </ul>
      </nav>
      <AppRouters />
    </Router>
  );
}

🔍 关键点解析:

  • 不再使用 <a href="/">
    因为原生 <a> 会触发页面跳转,破坏 SPA 特性。<Link> 内部阻止默认行为,并调用 history.pushState() 实现无刷新跳转。

  • <AppRouters /> 是独立封装的路由表
    高内聚低耦合,便于后期维护。

router.jsx —— 路由配置中心

import { Routes, Route } from 'react-router-dom';
import Home from '../pages/Home';
import About from '../pages/About';

export default function AppRouters() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
    </Routes>
  );
}

🎯 注意:React Router v6 开始使用 element 属性传入 JSX 元素,取代了之前的 component


🌈Vue 中的路由:配置式 + 模板语法的清晰之美

✅ 使用 vue-router

安装命令:

npm install vue-router@4

Vue 的路由更偏向“配置驱动”,有点像在写一份“路由说明书”。

router/index.js —— 路由注册中心

import { createRouter, createWebHashHistory } from 'vue-router'
import Home from '../views/Home.vue'
import About from '../views/About.vue'

const routes = [
  { path: '/', name: 'Home', component: Home },
  { path: '/about', name: 'About', component: About }
]

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

export default router;

🧠 思考:这里用了 createWebHashHistory(),意味着 URL 会带 #/about。如果想去掉 #,可以用 createWebHistory(),但需要后端配合支持 History Mode。

main.js —— 安装插件

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

createApp(App).use(router).mount('#app')

👉 .use(router) 是 Vue 插件机制的经典体现 —— 把路由能力“注入”到整个应用中。

App.vue —— 模板中使用全局组件

<template>
  <div>
    <nav>
      <ul>
        <li><router-link to="/">Home</router-link></li>
        <li><router-link to="/about">About</router-link></li>
      </ul>
    </nav>
    <main>
      <router-view></router-view>
    </main>
  </div>
</template>

🔑 重点来了:

  • <router-link> 自动编译为 <a>,但点击时不刷新。
  • <router-view> 是“占位符”,根据当前路径动态插入对应的组件。

路由大战 —— Hash 还是 History?

你有没有注意到网址栏里的 #?比如 http://xxx.com/#/about

这是早期前端路由的“权宜之计”——Hash 路由。为什么用 #

因为 # 后面的内容不会发给服务器!前端可以随便玩,不怕 404。

但后来出现了更优雅的方式:HTML5 History API

类型示例优点缺点
Hash/#/about兼容老浏览器,稳如老狗丑,像打了补丁
History/about干净美观,像后端路由需要服务器配合

在 React 中你可以这样选:

  import { BrowserRouter as Router } from 'react-router-dom'; // History 模式
  // import { HashRouter as Router } from 'react-router-dom'; // Hash 模式

而在 Vue 中:

createRouter({
  history: createWebHistory(), // or createWebHashHistory()
})

✅ 建议:新项目一律上 History 模式,毕竟谁不想自己的网站看起来“高级一点”呢?

⚖️React vs Vue:路由设计理念大碰撞!

维度React (JSX + 函数式)Vue (模板 + 配置式)
编写方式声明式嵌套,组件即路由配置数组 + 全局指令
类型友好性TypeScript 天然融合同样支持 TS,但需额外类型定义
学习曲线需要理解 JSX、Hooks、函数组件模板语法易上手,适合初学者
灵活性极高,可自由组合逻辑较高,但受模板限制
工具链生态Remix、Next.js 强大 SSR 支持Nuxt.js 生态完善
开发者心智模型“一切皆 JS”“HTML 扩展 + 数据绑定”

🤔举个生活化的比喻:

如果说 React 像是乐高积木 —— 你可以自由拼接每一个 <Route> 成复杂的路由树;
那么 Vue 更像是宜家家具 —— 提供标准化零件(routes 数组),按照说明书组装即可。

两者没有优劣,只有适用场景。


🔍进阶思考:Home 页面的数据请求该放哪?

我们来看一段 React 的 Home.jsx

const Home = () => {
  const [repos, setRepos] = useState([]);

  useEffect(() => {
    fetch('https://api.github.com/users/.../repos')
      .then(res => res.json())
      .then(data => setRepos(data));
  }, []);

  return (
    <div>
      <h1>Home</h1>
      {repos.length ? (
        <ul>
          {repos.map(repo => (
            <li key={repo.id}>
              <a href={repo.html_url} target="_blank">{repo.name}</a>
            </li>
          ))}
        </ul>
      ) : <p>暂无仓库</p>}
    </div>
  );
};

📌关键知识点:

  • useEffect(() => {}, []) 相当于 Vue 的 onMounted
  • 空依赖数组确保只执行一次
  • 数据请求放在组件内部,属于“副作用管理”

而在 Vue 中,你会这样写(Composition API):

<script setup>
import { onMounted, ref } from 'vue'
const repos = ref([])

onMounted(async () => {
  const res = await fetch('https://api.github.com/users/.../repos')
  repos.value = await res.json()
})
</script>

<template>
  <div>
    <h1>Home</h1>
    <ul v-if="repos.length">
      <li v-for="repo in repos" :key="repo.id">
        <a :href="repo.html_url" target="_blank">{{ repo.name }}</a>
      </li>
    </ul>
    <p v-else>暂无仓库</p>
  </div>
</template>

🎯结论:无论是 React 的 useEffect 还是 Vue 的 onMounted,都在解决同一个问题 —— 组件挂载后的副作用处理


🛠️最佳实践建议

场景推荐方案
快速原型开发Vue + Volar 插件,模板直观
复杂状态管理、大型系统React + Redux Toolkit / Zustand
SEO 敏感项目React (Next.js) 或 Vue (Nuxt.js)
团队协作统一风格Vue 的模板约束更强,不易写出“千奇百怪”的 JSX
全栈一体化React 生态更成熟(如 TanStack Router、tRPC)

🎁总结:前端的终极目标是“用户体验”而非“技术炫技”

今天我们从一个简单的多页面应用出发,穿越了:

  • Vite 的构建哲学
  • React 与 Vue 的路由实现差异
  • 声明式 vs 配置式的编程范式之争
  • 组件生命周期与副作用管理

但请记住:

🎯技术只是手段,交付价值才是目的。

当你能熟练选择合适的工具链、清晰组织项目结构、写出可维护的路由系统时,你就已经超越了大多数“只会抄 demo”的开发者。


📣互动时间

你在实际项目中更偏爱 React 还是 Vue?为什么?
有没有遇到过路由懒加载、权限控制等进阶问题?欢迎评论区留言交流👇

如果你觉得这篇文章对你有帮助,别忘了点赞、收藏、转发三连!让更多人看到前端的魅力所在。