使用 React + Vite 构建现代前端应用:从零搭建 GitHub 仓库展示页

104 阅读6分钟

一、项目初始化:用 Vite 快速启动 React 应用

Vite 是新一代前端构建工具,以 极速冷启动按需编译 著称,非常适合现代 React 开发。

bash
编辑
# 初始化项目(选择 React + JavaScript 模板)
npm create vite@latest my-react-app -- --template react
cd my-react-app

# 安装依赖
npm install

# 安装路由和样式预处理器
npm install react-router-dom
npm install -D stylus  # -D 表示开发依赖(devDependencies)

为什么选 Vite?
相比 Create React App(CRA),Vite 启动快 10 倍以上,且原生支持 ESM(ES Module),无需打包即可运行,极大提升开发体验。


二、目录结构设计(推荐规范)

text
编辑
my-react-app/
├── src/
│   ├── components/     # 可复用组件(如 Header、Card)
│   ├── pages/          # 页面级组件(Home、About)
│   ├── router/         # 路由配置
│   ├── App.jsx         # 根组件
│   └── main.jsx        # 入口文件
├── public/
└── index.html

📌 约定优于配置:将 HomeAbout 放入 pages/ 目录,清晰区分“页面”与“组件”。


三、核心功能实现

1. 页面组件:Home(拉取 GitHub 仓库)

jsx
编辑
// src/pages/Home.jsx
import { useState, useEffect } from "react";

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

  // 组件挂载后发起请求(等效 Vue 的 onMounted)
  useEffect(() => {
    fetch('https://api.github.com/users/shunwuyu/repos')
      .then(res => res.json())
      .then(data => setRepos(data))
      .catch(err => console.error('Failed to fetch repos:', err));
  }, []); // 空依赖数组 = 仅执行一次

  return (
    <div>
      <h1>My GitHub Repos</h1>
      {repos.length ? (
        <ul>
          {repos.map(repo => (
            <li key={repo.id}>
              <a href={repo.html_url} target="_blank" rel="noreferrer">
                {repo.name}
              </a>
            </li>
          ))}
        </ul>
      ) : (
        <p>Loading repos...</p>
      )}
    </div>
  );
};

export default Home;

⚠️ 注意事项

  • rel="noreferrer" 防止安全漏洞(避免泄露 referrer 信息)
  • 添加 .catch() 处理网络错误,提升健壮性

2. About 页面(静态内容)

jsx
编辑
// src/pages/About.jsx
const About = () => {
  return (
    <div>
      <h1>About Me</h1>
      <p>This is a demo app built with React and Vite.</p>
    </div>
  );
};

export default About;

3. 路由配置:集中管理路由规则

🔑 关键理解: 不是“占位符”,而是“路由分发器”

在你的项目中:

jsx
编辑
// src/App.jsx
import AppRoutes from './router';

function App() {
  return (
    <Router>
      <nav>...</nav>
      <AppRoutes /> {/* ← 这不是空占位符! */}
    </Router>
  );
}

<AppRoutes /> 并非一个简单的 DOM 占位元素(如 <div></div>),而是一个自定义功能组件,其核心作用如下:


一、先明确:它不是 “空占位符”,是自定义组件的调用

<AppRoutes /> 是你从 ./router 文件导入的自定义组件(通常文件名为 router.jsxAppRoutes.jsx)。它的本质是把分散的路由规则抽离到独立文件中,让 App 组件保持简洁、专注 UI 结构。


二、核心作用(3 个关键)

1. 承载路由规则,实现 “路由表集中管理”
jsx
编辑
// src/router/index.jsx
import { Routes, Route } from 'react-router-dom';
import Home from '../pages/Home';
import About from '../pages/About';

export default function AppRoutes() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
    </Routes>
  );
}
  • 所有路径 ↔ 组件的映射关系都集中在此;
  • <Routes> 是 React Router 的路由容器,只渲染当前 URL 匹配的 <Route>
  • <AppRoutes /> 在 App 中被调用,相当于将整套路由逻辑“注入”到应用结构中。
2. 渲染 “当前 URL 匹配的页面组件”
  • 当 URL 为 / → 渲染 <Home />
  • 当 URL 为 /about → 渲染 <About />

👉 因此,<AppRoutes /> 是页面内容的“动态渲染区”
导航栏(<nav>)固定不变,而 <AppRoutes /> 所在区域会根据 URL 动态替换为对应的页面组件

3. 解耦路由规则,提升可维护性

若将所有 <Route> 写在 App.jsx 中,随着页面增多(如 /user, /detail, /settings),App 会迅速膨胀。
通过抽离为 AppRoutes

  • ✅ UI 与路由逻辑分离App 只管布局,AppRoutes 只管路由;
  • ✅ 路由表独立维护:新增页面只需修改 router/index.jsx,无需触碰 App
  • ✅ 团队协作更清晰:前端可并行开发页面与路由配置。

三、对比 “占位符”:它是 “功能组件” 而非 “空占位”

特征单纯占位符(如 <div></div><AppRoutes />
是否有逻辑❌ 无,仅占 DOM 位置✅ 有,包含路由匹配与渲染逻辑
是否动态渲染内容❌ 内容固定✅ 内容随 URL 变化
是否可复用 / 维护❌ 无复用价值✅ 路由规则集中,高内聚易维护

💡 一句话总结
<AppRoutes />“路由分发器” —— 占位符只占位置,而它会根据 URL 动态渲染不同内容


4. 根组件 App:集成导航与路由分发器

jsx
编辑
// src/App.jsx
import { BrowserRouter as Router, Link } from 'react-router-dom';
import './App.css';
import AppRoutes from './router'; // ← 导入路由分发器

function App() {
  return (
    <Router>
      <nav style={{ marginBottom: '20px' }}>
        <ul style={{ listStyle: 'none', display: 'flex', gap: '16px' }}>
          <li><Link to="/">Home</Link></li>
          <li><Link to="/about">About</Link></li>
        </ul>
      </nav>
      <AppRoutes /> {/* ← 路由内容在此动态渲染 */}
    </Router>
  );
}

export default App;

💡 为什么用 <Link> 而不是 <a>
<Link>react-router-dom 提供的组件,点击时不会刷新页面,而是通过 History API 实现前端路由跳转,保持 SPA 体验。


5. 入口文件:挂载到 DOM

jsx
编辑
// src/main.jsx
import { createRoot } from 'react-dom/client';
import './index.styl'; // 全局样式(Stylus)
import App from './App';

createRoot(document.getElementById('root')).render(<App />);

🎨 Stylus 示例(src/index.styl)

stylus
编辑
*
  margin 0
  padding 0
  box-sizing border-box

body
  font-family -apple-system, sans-serif

四、Vue 对比视角:<router-view> 同样不是占位符!

很多开发者会疑惑:在 Vue 中,类似 <AppRoutes /> 的东西是不是占位符?

答案是否定的。

Vue 中的对应物:<router-view />

vue
编辑
<!-- App.vue -->
<template>
  <nav>
    <router-link to="/">Home</router-link>
    <router-link to="/about">About</router-link>
  </nav>
  <router-view /> <!-- ← Vue 的“路由出口” -->
</template>
维度单纯占位符(如 <div></div>Vue 的 <router-view />
核心作用仅占 DOM 位置承载路由匹配逻辑,动态渲染组件
内容是否动态❌ 固定✅ 随 URL 变化
是否依赖路由表❌ 否✅ 完全依赖 routes 配置
扩展能力❌ 无✅ 支持嵌套路由、过渡动画、路由守卫等

结论
无论是 React 的 <AppRoutes />,还是 Vue 的 <router-view />,它们都是带核心路由功能的“动态渲染出口” ,绝非“占位符”。
真正的占位符:只有那些无逻辑、内容固定、仅用于布局的空标签(如 <div></div>)。

💡 记忆口诀
“凡承载路由动态渲染者,皆非占位符。”


五、启动与验证

bash
编辑
npm run dev

访问 http://localhost:5173(Vite 默认端口),你应该看到:

  • 顶部导航栏可点击切换 Home / About
  • Home 页自动加载 GitHub 仓库列表(需联网)
  • 点击仓库名在新标签页打开 GitHub 页面

六、总结要点 ✅

模块关键点
项目初始化npm create vite + 选 React 模板
状态管理useState 存数据,useEffect 发请求
路由分发器<AppRoutes /> 是路由规则容器 + 动态渲染出口
导航用 <Link> 实现无刷新跳转
样式Stylus 预处理器(需安装)
模块导出每个文件只能有一个 export default

七、拓展思考 🔍

Q1:路由方案对比

方案特点适用场景
BrowserRouterURL 美观(/about),需后端支持 history fallback现代浏览器,有后端配合
HashRouterURL 含 #/#/about),无需后端配置纯静态部署(如 GitHub Pages)

你的项目若部署在 GitHub Pages,建议改用 HashRouter

Q2:React 18+ 的 Strict Mode 为何执行两次?

这是 React 的开发模式行为,用于检测副作用是否被正确封装(如是否在 useEffect 中)。生产环境只执行一次,无需担心。


八、常见问题排查

  • 白屏无内容?  → 检查是否忘记在 App 中引入 AppRoutes
  • 路由不生效?  → 确保 Router 包裹了所有路由相关组件
  • Stylus 不编译?  → 确认已安装 stylus 并重启 Vite
  • GitHub API 限流?  → 未认证请求每小时最多 60 次,可加 token 提升限额